# 0.6 Wearable Agent Shell — 架构与交互提案

研究日期：2026-10-04（Asia/Shanghai）。状态：**host spike；未接入固件或 Gateway**。
基线 `3b46835ffaf3783e00ecc9eda9f923d6481eb18a`，分支 `codex/wearable-shell-spike-0.6`。

设备的主要用途改为随身 Agent Console：看状态、说一句话、处理审批、读结果；Monitor 作为其中一个 app。首先做 0.6a，再做已有会话导航。New Workspace / New Session 留在 0.6c。

## 范围与隔离

本轮只增加 `docs/wearable-shell-0.6/` 和 `prototypes/wearable_shell/`。临时源码、SDK 探针和测试输出位于此 worktree 的 `.cache/`。没有修改 CMake、依赖锁、VERSION、生产配置、board.cpp、audio_backend.cpp、cloud_client.cpp 或任何 Speech 文件。没有发送 prompt、使用生产会话、重启服务、刷机、push 或 merge。

相关交付：[硬件与持久化](hardware-preferences.md)、[目录与工作区协议](catalog-workspaces.md)、[新建会话能力](sessions.md)、[验收记录](validation.md)、[22 张原型图库](artifacts/index.html)。

## 信息架构

```text
Boot -> DevicePreferences.home_screen -> Watch | Agent | Monitor
                                      B short -> HOME launcher
HOME launcher: Watch / Agent / Monitor (A next, B open)

Watch: Agent Clock -> A short -> Agent
Agent: Summary -> Activity -> Final result -> Summary (A short)
Monitor: Overview -> CPU -> Memory -> Disk -> Network (A short)

Long B -> SETTINGS (local, works offline)
    Agent   Project / Session / Model / Effort / Speech Mode / Back
    Device  Brightness / Volume / Vibration / Back
    Display Home Screen / Text Size / Theme / WatchFace / Back
    System  Battery / Wi-Fi / Gateway / Firmware / Back
    Back
```

Home 是启动偏好和 launcher，不再增加一个内容重复的 Overview。launcher 展示 Watch / Agent / Monitor 三个可进入的 app。`Home Screen=Agent` 直接启动到 Agent Summary；无 target 时显示“Choose a session”，离线时保留最后状态并明确标注，绝不退回服务器指标。

**显示哪个 app 与控制哪个 target 必须分开。** 看 Monitor 不执行 `target=server`，也不清掉 Agent target；长 A 仍对选中的 Agent 发声。现有 `board_select_server_target()` 会改页面/选择状态，不能拿它当新导航接口。外部 target 变更可更新 identity，不应强制把正在看的 Monitor 或 Settings 弹走。

## 两个实体按钮

| 上下文 | A short | B short | Long A | Long B |
|---|---|---|---|---|
| Watch | 打开 Agent | 打开 HOME | 现有 Voice 流程 | Settings |
| Agent | Summary / Activity / Final 翻页 | HOME | 现有 Voice 流程 | Settings |
| Monitor | 下一个指标，五页循环 | HOME | 对当前 Agent 发声 | Settings |
| HOME / 菜单 / Picker | 下一个选项，循环 | 打开或选择 | HOME 可发声；设置内不启动语音 | HOME 开设置；设置内关闭 |
| 数值/枚举编辑 | 下一个值 | Apply | 无动作 | 取消草稿并关闭设置 |
| Target 确认 | Cancel / Switch 切换 | 执行选中项 | 无动作 | 取消 |
| Approval / Voice | 完全交给已有 modal controller | 同左 | 保持现有规则 | 保持现有规则 |

这是一个**明确的短 B 语义变更**：旧主界面用 B 上一页，新 app 用 B 返回 HOME。原因是两个短按必须同时承担浏览和层级导航；保留 B 上一页又不加组合键，会让进入/退出 app 缺少清晰入口。没有改变 600ms 长按阈值、去抖、长按释放、Voice 的 A SEND / B CANCEL、Approval 的 A APPROVE / B REJECT。设置内继续现有 A NEXT / B SELECT。每页固定显示短按动作，长按提示在下一行；真机验收重点检查旧用户的肌肉记忆。

所有菜单有显式 Back 行，3 行可见窗口跟随焦点滚动；焦点含 Next page / Back 行，不需要向下滑动才能离开。Long B 在设置内关闭整个设置栈并回到进入前页面，保持现有“Hold B to close”。不引入双击、两键组合或自动确认。Target 确认默认焦点是 Cancel；展示 project、agent、缩短的 name 和 ID 尾段，名称相同也不会选错身份。

典型流程：Long B → Agent → Project → 选择 esp32-dev → Session → Speech debugging → Cancel / Switch → Switch → 等待 ACK → Agent Home。选择 Project 只改变浏览范围，不改变 target；B 确认 session 后也不乐观切换。Loading / 空目录 / 过期 / Offline 都保留 Back；重试产生新 request_id。模型切换必须空闲，只有当前模型实际支持 Effort 才显示候选值，Pi 当前 Gateway 的 Effort 显示 Unavailable。

Device 流程：Long B → A 到 Device → B → Brightness → B → A 循环 10..100% → B Apply；Volume 为 0..100%，Vibration 为 On/Off。编辑期间只改草稿，本 spike 不预览真实设备输出。未来 Apply 先经硬件 owner 应用，再持久化；失败不能显示“Saved”。Display 同样 A 循环 / B Apply。WatchFace 第一版只有 Agent Clock，保留字段供版本迁移，不制造无价值主题列表。

System 是只读信息：电池电压及估算标识/读取错误、Wi-Fi 连接/RSSI、Gateway 连接及版本、固件版本。该页不包含 Wi-Fi PS、音频诊断或网络调优开关。设备偏好重置置于未来 System 的二级维护项，两步确认；不把全设备擦除混成 Reset。

## 独立状态与边界

```text
existing ButtonGesture + modal priority
              | normalized event + ui epoch
              v
UiState reducer ------------------------> renderer (read-only ViewModel)
  UiPage / menu index / draft settings       468x466 / round safe area
  AgentPickerState / request context
              |
              +--> Effect::Voice -> existing voice path
              +--> preferences effect -> DeviceSettingsController -> owners
              +--> picker effect -> CatalogClient -> Gateway
              +--> settings effect -> existing model/effort/speech settings

DeviceStatusProvider -> RTC/Battery/Wi-Fi optional values -> Watch/Agent
existing agent snapshot/activity/final_text -> view data (no alternate event stream)
```

`core.hpp` 是 host-only、无 ESP-IDF/网络依赖的 C++ 状态和偏好编码提案。UiState 实测 48 bytes；没有堆分配。`catalog.py` 是独立协议参考模型，不是 Gateway 的新 HTTP/WebSocket handler。它检验有界 page、UTF-8、snapshot、CAS 和旧响应处理。两者都不在任何固件/server import path 中。

未来 C++ CatalogClient 必须在整帧 byte cap 通过后用有界 token parser 校验深度/字段类型/数量，再复制进固定数组；不能仅靠 cJSON 和 4KB wire cap 宣称 heap 有界。解析成功后一次替换 page；不保留整份目录，也不按任意 server count 分配数组。

优先级：已有 approval → voice / speech 控制与原有抢占规则 → 设置/选择 → app。Shell 收到 overlay=true 时不消费任何按钮；进入、离开 overlay 由现有 owner 推进 epoch，丢弃旧按键。request_id / connection / target generation 属于协议控制器，不能用一个页面 index 代替。core 的 `catalog_arrived` / `target_ack` 只能在这些校验之后调用。不同 project 请求不会沿用旧 session page。

## WatchFace 与 Agent Home

WatchFace 常驻时间和当前 Agent 身份：IDLE 为时间优先；RUNNING 显示状态、经过时间、最新活动；DONE 显示耗时和下一次对话提示，A 进入 Final。WAITING_APPROVAL 始终交给已有审批模态，不压缩成一个小点；FAILED 红色文字；OFFLINE 灰色文字并显示最后更新时间。所有颜色均配状态词，色觉差异不会隐藏含义。

RTC 时间无效时显示 `--:-- / Time unavailable`，不把编译时间或样例 18:42 当真实时间。elapsed 使用单调时钟，不因 RTC 校时倒退。电池未知显示 `BAT --`，初期用电压；模拟图的 3.91V 不代表读取结果。Wi-Fi connected 与 Gateway online 分开，连上 AP 不能声称 Agent 可用。没有 target 时保持 Watch，可以从 Settings 选会话。

Agent Home 顶部放 agent + project；正文是状态/活动/结果；底部常驻 Long A 入口与当前 speech mode/state。运行时显示 elapsed 和最新 activity；完成后使用既有 final_text；结果长时继续已有自动分页/按 A 页切换，不删掉尾部结果。Audio 播放、取消、自动播报和 Voice 确认不重新实现。图中的 speech Manual 只是演示状态，不改变现有 OFF 默认值。

## Design system

| Token | Dark | Dim | 用途 |
|---|---|---|---|
| Background | #000000 | #000000 | AMOLED true black |
| Primary | #F3F5F7 | #C9CDD2 | 核心文字 |
| Secondary / Offline | #A4ABB5 | #90959D | 辅助信息 |
| Accent / Running | #5EDBF2 | #8EB8C1 | 焦点、运行 |
| Done | #6DDE91 | #97BB9E | 完成 |
| Failed | #FF7777 | #CE9393 | 失败 |
| Approval | #FFBB66 | #C7AA85 | 待决策 |
| Selected row | #12171C | #101214 | 克制的选中背景 |

Dim 只改变调色板，不暗中改亮度设置。不给用户增加字体 family。固件保持 TranscriptFont（CN 优先、TW 缺字回退），数字也复用已有字形缩放。正文 Compact/Normal/Large 建议 16/24/32px（1/1.5/2 倍），行高 24/32/40px；固定 chrome 16px，次级长按提示 12px 的可读性需真机判定，必要时将两行 hint 统一 16px。不从字体资产重新生成 CJK 字库。

布局以项目实际 framebuffer **468×466** 为准；官方面板标称 466×466，M5GFX autodetect 另有 468×468，均不替代当前已验证配置。中心 (234,233)，关键文字和选中矩形的四角都在半径 217 内，物理圆边再留约 16px。header y=57/88，主体 y≈122..345，footer y=382/406。名单显示三项，名称与 state 分两行，长名按测量宽度截断，确认页显示更多身份信息；正文按 Unicode 边界换行。UTF-8 字节安全不等于 grapheme 完整，emoji/组合字符不承诺覆盖，非 BMP 字符需明确替换提示。

Host 图片使用本机 Noto CJK 作为度量替身，不能称为固件像素一致；真实布局要以 `textWidth/getFontHeight` 重测。现有 ELF CN+TW 共 910,304 bytes，64 个原型中文字符通过原字体 coverage 检查，两个字库行度量一致。新增字体 Flash = 0；这不等于未来整份 0.6 固件体积不变。

## 实施阶段

| 阶段 | 依赖与推荐范围 | 预计代码区域（未来） | 风险 | 测试与真机门槛 |
|---|---|---|---|---|
| 0.6a Wearable Shell | Speech 收口后固定新基线；统一 DevicePreferences/NVS、亮度80%、Vibration、Home、Agent Clock、Dark/Dim、三字号、一级导航。Volume80% 最后接 audio owner | 新 ui_state / preferences / device_status helpers；极薄 board adapter；NVS adapter；一个受控音量 setter | 亮度低风险；导航中等；Volume 与设备状态 I²C 需谨慎；RTC 缺驱动 | host reducer/存储故障/布局；真机低亮可恢复、重启存储、声音/震动、离线启动、审批/语音抢占、字体、耗电 |
| 0.6b Agent Navigator | 有稳定 Shell 和 Gateway bounded catalog；选择已有 Project/Session；target ACK/CAS；复用 Model/Effort/Speech Mode | Gateway catalog controller、protocol validator、device CatalogClient、Settings view；只在最后添加薄接线 | target/approval/voice 竞态和旧响应是主风险；现有 adapter list 非有界 | 假 adapter、假设备、分页/Unicode/跨连接/取消；真机长名、网络抖动、跨 Pi/Codex、切换后语音与结果归属 |
| 0.6c Workspace Control | roots 管理与认证授权；可读 workspace discovery；先明确 durable draft 合约，再 New Session；最后 New Workspace | 新 workspace service、registry transaction、adapter create extension、Gateway draft store | 文件系统 mutation/权限/重试重复创建/SDK 空会话持久化差异 | tmpdir 隔离 symlink/TOCTOU/碰撞/宕机测试；两种 adapter 重启恢复；手表只能选择 allowlisted 项目 |

0.6a 建议顺序：①偏好 codec/namespace + 本地 Settings，②亮度/震动/Home，③Watch/Agent/Monitor Shell 与视觉层次，④text/theme，⑤只读 RTC/电压状态，⑥在 Speech 稳定基线上补 Volume 并回归。RTC 未 ready 可以明确显示未知，不阻塞 Agent Home 演示；不能用假时间或假百分比补齐画面。每步可独立 review/commit。

**如果 Speech 今晚收口，先做 0.6a 的本地偏好与 Home=Agent，再完成 Agent Clock/基本导航。** 它们直接改变开机演示和日常可用性，硬件基础已存在，且不需要扩展 AgentAdapter 或触碰工作区。随后以已有会话 picker 打通 0.6b。Touch、复杂 NTP、RTC alarm 唤醒、百分比校准、New Workspace 和跨后端 durable new session 暂时保留设计或单独 spike。
