feat: voice command mapping — context-gated approve/reject over voice

While a tool-permission gate is held on the active tab, spoken confirm
phrases resolve it via the existing approve/reject WS channel — no server
change (byte-shuttle intact). Everything else stays ordinary dictation.

Safety: whole-utterance-exact matching + leading-negation guard + reject
precedence + confidence gate (approve only) + a cancellable 1.5s confirm
window whose commit re-validates gate identity/epoch/connectivity (TOCTOU
guard) + stale-gate epoch bound at PTT-start.

New pure modules public/voice-commands.ts and public/voice-confirm.ts at
100% coverage; wiring in voice/terminal-session/tabs/main. 1307 tests pass,
typecheck clean, build:web OK. Independent code + security reviews flagged a
confirm-window TOCTOU and an unwired cancel path — both fixed and covered.

Plan: docs/PLAN_VOICE_COMMANDS.md.
This commit is contained in:
Yaojia Wang
2026-07-01 10:34:51 +02:00
parent 03612323c0
commit e4c327e25e
13 changed files with 1193 additions and 19 deletions

View File

@@ -24,6 +24,9 @@
> 新会话读到的第一块。保持准确,只描述"此刻"。
### ✅ VC 语音命令映射 — 语音直接 approve/reject 权限门(完成 — 2026-07-01,分支 `v0.6-projects`,未提交)
上下文门控:仅当活动 tab 有 held 权限门(`pendingApproval===true``gate==='tool'`)时,语音「确认/批准/yes…」→ 复用现有 `{type:'approve'}` WS 通道、「拒绝/取消/no…」→ `{type:'reject'}`;其余一律口述不变。**后端零改动**。安全设计:整句精确匹配(否定/口水词攻不破)+ 前导否定守卫 + reject 优先 + 置信度门(仅 approve)+ **可撤销确认窗口(1.5s,提交时复检门身份/连接,防 TOCTOU)** + stale-gate epoch。新增 `public/voice-commands.ts`(纯匹配器 100% 覆盖)+ `public/voice-confirm.ts`(确认窗口 100%),接线 `tabs/voice/terminal-session/main.ts`。**1307 tests 绿**,双 review(code+security)判 CRITICAL 已全修复并补测。规划 `docs/PLAN_VOICE_COMMANDS.md`,详见 Detailed Log 首条。**待办**:confidence fail-open 取舍留给用户;tabs.ts 968 行待拆分。
### ✅ v0.6 Projects 命名空间自动分组 + 跨设备 prefs(完成 — 2026-07-01,分支 `v0.6-projects`,未提交)
Projects 页去杂乱:按点分隔命名空间自动折叠成 section(`Billo.Platform ·8` / `Billo.Infrastructure ·2` / `Other`)+ 置顶 "Active now" + 无前缀时退化为扁平网格。收藏/折叠态改**服务端持久化**(`GET/PUT /prefs``~/.web-terminal-prefs.json`,离线回退 localStorage 镜像 + 迁移旧 `proj-favs`)跨设备同步。1227 tests 绿,真浏览器验证折叠跨 reload 从服务端载回。详见 Detailed Log 首条。
@@ -149,7 +152,25 @@ Projects 页去杂乱:按点分隔命名空间自动折叠成 section(`Billo.Pla
- **commit**: <hash 或 N/A>
======================================================= -->
### 2026-07-01 · v0.6 Projects 页去杂乱 — 命名空间自动分组 + "Active now" + 跨设备 prefs
### 2026-07-01 · VC 语音命令映射 — 上下文门控 confirm/reject(多 agent 编排 + 双 review)
- **状态**: `[x]` DONE(未提交,分支 `v0.6-projects`)
- **背景**: 已有 v0.7 A2 push-to-talk 只做「语音→文字」口述;本次加**语音命令层**,让语音直接 approve/reject 权限门。规划见 `docs/PLAN_VOICE_COMMANDS.md`(多 agent:理解→前后端计划→4 路对抗 review→综合)。锁定决策:①上下文感知(仅当活动 tab `pendingApproval===true` 才当命令,否则口述不变)②只做 confirm 类,复用现有 `approve`/`reject` WS 通道(**后端零改动**)③危险键不接线,two-step 组件仅用于 approve 确认窗口。
- **改动**:
- 新增 `public/voice-commands.ts`(纯匹配器,**100% 覆盖**):`matchCommand` = 整句精确 + 前导否定守卫 + reject 优先 + 置信度门(仅 approve);中英确认/否定词表。
- 新增 `public/voice-confirm.ts`(确认窗口控制器,**100% 覆盖**):`createApproveConfirm(1500)` arm/cancel/isArmed,纯计时(注入 setTimeout,无 DOM)。
- `public/voice.ts`:`onTranscript` 多传 `confidence`(仍只转写)。`public/terminal-session.ts`:加 `pendingEpoch`(false→true 翻转自增,stale-gate nonce)。`public/tabs.ts`:`handleVoiceTranscript` 分派 + 确认窗口 arm + WS 断开闪「未连接」+ 单独 `#voice-confirm` overlay(避 hideInterim 清屏)。`public/main.ts`:接 `onVoiceTrigger` 点亮 🎤。`vitest.config.ts`:两新模块入 coverage.include(orchestrator 协调编辑)。
- 测试:`test/voice-commands.test.ts`/`test/voice-confirm.test.ts` 新建;`test/tabs.test.ts` 加 VC 集成(含 6 条 review-fix 用例);`test/voice.test.ts` 4 处旧断言随 `(text,confidence)` 新签名更新 + 补 confidence 转发断言。
- **验证**: `npm run typecheck` 干净;`npm test`**1307 passed / 43 files**;`build:web` OK;覆盖率 voice-commands/voice-confirm **100×4**、tabs.ts 94/84/89/97、TOTAL 91.3/84.6/91.7/93.2(80×4 通过)。`git diff src/` 空(后端 NONE 成立)。
- **决策 / 偏离 PLAN**:
- **实现 workflow 首跑失败**(review 阶段某 agent 6 次 stall)——但 Build V1/V2 已落文件;改为 orchestrator 亲自跑验证 + 用普通 agent 并行做 code/security review(避开 workflow 卡死)。
- **两份 review 独立收敛,判 CRITICAL/BLOCK**,均已修复并补测:①**确认窗口 TOCTOU**:`onCommit` 现复检 `pendingApproval && gate==='tool' && pendingEpoch===armedEpoch && connected`,否则丢弃;②**`cancel()` 从未接线**:接入新话语顶替(handler 顶部)、reject 分支、`#voice-confirm` 点击;③**WS 断开延迟提交错位**:提交前复检连接。
- **偏离 PLAN §4**:「松开麦克风取消」在 PTT 时序下不成立(松麦克风正是触发最终转录/arm 的动作),改为 **tap + 新话语顶替 + reject 取消** 三条真实取消路径。
- **`startsWithNegation` 重写**(review MEDIUM):原 `prefix+rest[0]` 实际只测 prefix[0](巧对但脆弱),改为显式测 `rest`
- **遗留 / 待办**:
- **confidence fail-open**(review MEDIUM,**保留**):`confidence===undefined` 时置信度门短路(符合 §D「有置信度时才门控」),靠确认窗口兜底误听;若要 fail-safe(undefined→不批准)是一行改动,但会让不报告置信度的引擎(部分非 Chromium)语音批准失效——留给用户定。
- **`public/tabs.ts` 已 968 行 > 800 上限**(既有债 + 本次 ~99 行):本次仅加最小 diff,**待后续抽 `voice-controller` 拆分**(decision F)。
- **commit**: N/A(未提交)
- **状态**: `[x]` DONE。双 typecheck 干净 · `build:web` OK · 全量 `vitest run` **1227 passed (41 files)** · 真浏览器端到端验证通过。
- **需求**(用户): Projects 页扁平网格太乱(50100 repo)。多 agent 探索(4 路:自动分组/手动文件夹/密度分段/竞品调研)结论一致 → **主方向=按点分隔命名空间自动分组**(repo 名如 `Billo.Platform.Payment` 天然带层级),配 **Active-now 置顶**;手动文件夹只是加分项非主线。用户选:**范围=自动分组+Active band**,**持久化=服务端**(收藏/折叠态跨设备同步,修掉 localStorage 每设备各一份的缺陷)。