duaer-spec 0.25.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/.claude/rules/ai-ui-copy.md +2 -2
  2. package/.cursor/rules/ai-ui-copy.mdc +3 -3
  3. package/.cursor/rules/fde-8787-only.mdc +7 -7
  4. package/.duaer/active-job.json +3 -3
  5. package/.duaer/specs/250-release-025/delivery.json +17 -0
  6. package/.duaer/specs/250-release-025/tasks.md +1 -1
  7. package/.duaer/specs/251-arch-claim-no-diagram/delivery.json +13 -0
  8. package/.duaer/specs/251-arch-claim-no-diagram/spec.md +23 -0
  9. package/.duaer/specs/251-arch-claim-no-diagram/tasks.md +5 -0
  10. package/.duaer/specs/252-release-0251/delivery.json +14 -0
  11. package/.duaer/specs/252-release-0251/spec.md +13 -0
  12. package/.duaer/specs/252-release-0251/tasks.md +3 -0
  13. package/.duaer/specs/253-docs-cursor-claude/delivery.json +9 -0
  14. package/.duaer/specs/253-docs-cursor-claude/spec.md +25 -0
  15. package/.duaer/specs/253-docs-cursor-claude/tasks.md +3 -0
  16. package/.duaer/specs/254-console-rename/delivery.json +13 -0
  17. package/.duaer/specs/254-console-rename/spec.md +27 -0
  18. package/.duaer/specs/254-console-rename/tasks.md +4 -0
  19. package/.duaer/specs/255-dispatch-arch-view/delivery.json +13 -0
  20. package/.duaer/specs/255-dispatch-arch-view/spec.md +16 -0
  21. package/.duaer/specs/255-dispatch-arch-view/tasks.md +4 -0
  22. package/.duaer/specs/256-chat-voice/delivery.json +13 -0
  23. package/.duaer/specs/256-chat-voice/spec.md +15 -0
  24. package/.duaer/specs/256-chat-voice/tasks.md +6 -0
  25. package/.duaer/specs/257-composer-voice-compact/delivery.json +9 -0
  26. package/.duaer/specs/257-composer-voice-compact/spec.md +13 -0
  27. package/.duaer/specs/257-composer-voice-compact/tasks.md +4 -0
  28. package/.duaer/specs/258-chat-stt/delivery.json +14 -0
  29. package/.duaer/specs/258-chat-stt/spec.md +16 -0
  30. package/.duaer/specs/258-chat-stt/tasks.md +5 -0
  31. package/.duaer/specs/259-voice-guide-stt/delivery.json +13 -0
  32. package/.duaer/specs/259-voice-guide-stt/spec.md +13 -0
  33. package/.duaer/specs/259-voice-guide-stt/tasks.md +4 -0
  34. package/.duaer/specs/260-release-026/delivery.json +18 -0
  35. package/.duaer/specs/260-release-026/spec.md +12 -0
  36. package/.duaer/specs/260-release-026/tasks.md +5 -0
  37. package/ADOPT.md +7 -4
  38. package/AGENTS.md +2 -2
  39. package/CHANGELOG.md +48 -0
  40. package/README.md +30 -26
  41. package/README.zh-CN.md +22 -22
  42. package/bin/duaer-live.mjs +186 -8
  43. package/bin/live-bug-context.mjs +1 -1
  44. package/bin/live-deliverables.mjs +1 -1
  45. package/bin/live-verify-gate.mjs +2 -2
  46. package/docs/agent/e2e-test-plan.md +10 -0
  47. package/docs/agent/live-desk.md +12 -6
  48. package/docs/agent/worker-models.md +7 -5
  49. package/docs/agent/worker-models.zh-CN.md +5 -3
  50. package/package.json +5 -5
  51. package/web/live-dev/app.js +445 -24
  52. package/web/live-dev/dispatch-center.html +25 -1
  53. package/web/live-dev/dispatch-center.js +113 -16
  54. package/web/live-dev/employee-catalog.mjs +2 -2
  55. package/web/live-dev/i18n-de.js +34 -10
  56. package/web/live-dev/i18n-es.js +34 -10
  57. package/web/live-dev/i18n-fr.js +34 -10
  58. package/web/live-dev/i18n-ja.js +34 -10
  59. package/web/live-dev/i18n-ko.js +34 -10
  60. package/web/live-dev/i18n-pt-BR.js +34 -10
  61. package/web/live-dev/i18n-ru.js +34 -10
  62. package/web/live-dev/i18n-vi.js +34 -10
  63. package/web/live-dev/i18n-zh-TW.js +36 -12
  64. package/web/live-dev/i18n.js +71 -23
  65. package/web/live-dev/index.html +55 -9
  66. package/web/live-dev/styles.css +59 -3
package/README.md CHANGED
@@ -10,10 +10,11 @@ into a checkable Brief (goal / boundary / acceptance), implements only that
10
10
  scope, and only claims done when acceptance is met.
11
11
 
12
12
  The product pitch is **norms → satisfactory delivery** — not teaching humans
13
- to operate Spec phases or slash commands.
13
+ to operate Spec phases, slash commands, or a professional delivery process.
14
14
 
15
- You should **not** have to operate the workflow. After a one-time install, you
16
- talk; the employee runs the process.
15
+ You do **not** need to understand that process. After a one-time install, talk
16
+ in **Cursor** or **Claude Code** in plain language; the employee runs the
17
+ workflow.
17
18
 
18
19
  ## One-time install
19
20
 
@@ -39,9 +40,12 @@ The CLI also prints a one-line hint when a newer npm release exists
39
40
  npx duaer-spec update
40
41
  ```
41
42
 
42
- Then use **Cursor**, **Claude Code**, **Codex**, **Copilot**, **Windsurf**, **Cline**, **Continue**, **Gemini**, or **Aider** — describe what you want.
43
+ Then use **Cursor** or **Claude Code** — describe what you want in plain
44
+ language. You do not need to learn Spec phases or slash commands.
43
45
 
44
- Install writes host adapters for each (see [`ADOPT.md`](ADOPT.md)).
46
+ `init` / `update` also write optional adapters for other editors (see
47
+ [`ADOPT.md`](ADOPT.md)). FDE console dispatch launches **Cursor Agent** or
48
+ **Claude Code** only.
45
49
 
46
50
  Chinese guide: [`README.zh-CN.md`](README.zh-CN.md)
47
51
 
@@ -54,20 +58,20 @@ After install, these are the capabilities you actually use:
54
58
  - **You talk; it runs the process.** No slash commands, no “you must run Spec phases.”
55
59
  - **Clarify before coding.** It turns your ask into: what to build, what not to build, how we know it’s done.
56
60
  - **Done means accepted.** It only claims finished when acceptance checks out — not when code merely exists.
57
- - **Works in many hosts.** Cursor, Claude Code, Codex, Copilot, Windsurf, Cline, Continue, Gemini, Aider.
61
+ - **Supported in Cursor and Claude Code.** Install once, then speak there — no professional Spec workflow required. (Optional adapters for other editors: [`ADOPT.md`](ADOPT.md).)
58
62
 
59
- ### In the browser: Duaer-spec FDE (live desk)
63
+ ### In the browser: Duaer-spec FDE (live console)
60
64
 
61
- A local web desk (`duaer live`) for “chat → confirm → dispatch → watch → open result”:
65
+ A local web console (`duaer live`) for “chat → confirm → dispatch → watch → open result”:
62
66
 
63
67
  - **Projects.** Create and switch projects; each keeps its own chat and progress.
64
- - **Chat until the ask is clear.** Left chat; center confirm cards (goal / out of scope / checkable acceptance). Vague acceptance cannot be confirmed.
68
+ - **Chat until the ask is clear.** Left chat; center confirm cards (goal / out of scope / checkable acceptance). Vague acceptance cannot be confirmed. Beside **Send**, tap **Record** to capture speech; with a **speech recognition (STT) model** in Settings (OpenAI-compatible `/audio/transcriptions`, e.g. whisper-1), the clip is decoded into the composer—edit, then send.
65
69
  - **Split big work into modules.** Confirm module by module; nothing starts until you kick off after the ones you care about are confirmed.
66
- - **Architecture diagram.** After requirements, review the system diagram; click it for fullscreen; kickoff waits until architecture is confirmed.
70
+ - **Architecture diagram.** After requirements, review the system diagram; click it for fullscreen; kickoff waits until architecture is confirmed. If chat claims the diagram is ready but the middle panel is empty, the same bubble offers Regenerate — it does not leave a false “ready” line.
67
71
  - **Dispatch digital employees to code.** Pick a product folder and Cursor Agent or Claude Code; work happens in an isolated worktree, not directly on your long-lived branches.
68
- - **Visible task progress.** Work is a checklist; each finished item is checked off and the desk refreshes; dependent tasks release in waves so agents don’t pile on blocked work.
72
+ - **Visible task progress.** Work is a checklist; each finished item is checked off and the console refreshes; dependent tasks release in waves so agents don’t pile on blocked work.
69
73
  - **Employee roles.** Top-bar **digital employee** directory (Implementer / Functional regression / Deployer). Optional parallel workers on the same CLI. See the next section.
70
- - **Locales.** Desk UI: zh-CN, zh-TW, en, ja, ko, es, pt-BR, fr, de, ru, vi.
74
+ - **Locales.** Console UI: zh-CN, zh-TW, en, ja, ko, es, pt-BR, fr, de, ru, vi.
71
75
  - **Deliverables and results.** Open a deliverables page (requirements timeline,
72
76
  architecture, tasks, delivery); when done, open the page or the project folder.
73
77
  - **Revise when it’s not right.** Continue in left chat → re-confirm → relaunch on
@@ -78,8 +82,8 @@ A local web desk (`duaer live`) for “chat → confirm → dispatch → watch
78
82
  facts autofill; defects join the same project timeline as revisions.
79
83
  - **Quick nav.** Chat-column edge control jumps to chat bottom · confirm card ·
80
84
  result · progress.
81
- - **Dispatch center.** Own page for the task graph with desk top nav; long chains
82
- wrap; nodes show live status.
85
+ - **Dispatch center.** Own page for the task graph and each project’s system
86
+ architecture, with console top nav; long chains wrap; nodes show live status.
83
87
 
84
88
  ### Digital employees: three roles
85
89
 
@@ -93,19 +97,19 @@ Open **Employees** in the top bar. Runtime is still Cursor Agent or Claude Code;
93
97
 
94
98
  With multiple workers, the **last lane** owns functional regression (and deploy tasks); implement work stays on earlier lanes. With one worker, the same CLI session runs roles in task order.
95
99
 
96
- Before a job can stay accepted, the desk runs the commands in the product `.duaer/memory/verify.json`. A non-zero exit, or a missing contract, reopens delivery. Docs-only work must set `{ "waiver": "docs-only" }` explicitly.
100
+ Before a job can stay accepted, the console runs the commands in the product `.duaer/memory/verify.json`. A non-zero exit, or a missing contract, reopens delivery. Docs-only work must set `{ "waiver": "docs-only" }` explicitly.
97
101
 
98
102
  ### Deploy: supported hosts
99
103
 
100
- Before kickoff, pick a **planned host** on the desk. Coding follows that platform; the **Deployer** employee publishes and writes the public URL into delivery preview.
104
+ Before kickoff, pick a **planned host** on the console. Coding follows that platform; the **Deployer** employee publishes and writes the public URL into delivery preview.
101
105
 
102
106
  | Option | Good for | You prepare |
103
107
  |---|---|---|
104
108
  | **No deploy yet** | Local delivery only | Nothing |
105
109
  | **GitHub Pages** | Static / docs sites; default when hosting is implied but no host was chosen | GitHub repo + `gh` auth |
106
- | **Cloudflare** | Pages for static; Workers for edge APIs | API Token + Account ID in desk Settings |
107
- | **Alibaba Cloud** | OSS (+ CDN) for static; Function Compute and similar for APIs | AccessKey in desk Settings |
108
- | **AWS** | S3 + CloudFront for static; Amplify / Lambda and similar when needed | Access keys in desk Settings |
110
+ | **Cloudflare** | Pages for static; Workers for edge APIs | API Token + Account ID in console Settings |
111
+ | **Alibaba Cloud** | OSS (+ CDN) for static; Function Compute and similar for APIs | AccessKey in console Settings |
112
+ | **AWS** | S3 + CloudFront for static; Amplify / Lambda and similar when needed | Access keys in console Settings |
109
113
 
110
114
  Detail: [`docs/agent/deploy-targets.md`](docs/agent/deploy-targets.md) · GitHub default path: [`docs/agent/deploy-github.md`](docs/agent/deploy-github.md)
111
115
 
@@ -126,13 +130,13 @@ The sections below are **how to run it**; this section is **what it can do**.
126
130
  Step skills and CLI checks exist for agents and power users — not as the
127
131
  everyday human UI.
128
132
 
129
- ## Duaer-spec FDE (live desk)
133
+ ## Duaer-spec FDE (live console)
130
134
 
131
135
  Isolated from product repos. Config and Briefs live under `~/.duaer/live/`.
132
136
  **Duaer-spec FDE** = Field Development Environment.
133
137
 
134
138
  ```bash
135
- # Start anytime (opens the desk even if the model is not configured yet)
139
+ # Start anytime (opens the console even if the model is not configured yet)
136
140
  duaer live
137
141
 
138
142
  # In the page Settings, or via CLI:
@@ -142,21 +146,21 @@ duaer live config --provider deepseek --api-key sk-...
142
146
  duaer live repo add
143
147
  ```
144
148
 
145
- `duaer live` opens the browser to the desk URL (default `http://127.0.0.1:8787`).
146
- If the model is missing, use **Settings** in the page. The desk is **full-width**
149
+ `duaer live` opens the browser to the console URL (default `http://127.0.0.1:8787`).
150
+ If the model is missing, use **Settings** in the page. The console is **full-width**
147
151
  with three columns: **chat** | **modules / confirm / architecture / kickoff /
148
152
  revise** | **task progress**. Detail:
149
153
  [`docs/agent/live-desk.md`](docs/agent/live-desk.md).
150
154
 
151
155
  **Verify (maintainers / agents):** after Duaer-spec FDE UI or validate-gate changes,
152
- run `npm run test:live` (L3 smoke: desk shell markers + `/api/validate` with a
156
+ run `npm run test:live` (L3 smoke: console shell markers + `/api/validate` with a
153
157
  mock LLM — no paid API).
154
158
 
155
159
  **Flow**
156
160
 
157
161
  1. Open or create a **project**, then chat. The **Projects** drawer shows each
158
162
  project’s **delivery status**; open **Deliverables** when the session has
159
- content. Messy multi-topic talk is fine: the desk evolves **`modules[]`**
163
+ content. Messy multi-topic talk is fine: the console evolves **`modules[]`**
160
164
  (tabs). Chat bubbles render **Markdown** (`**bold**`, code, links).
161
165
  2. Per module: fill the confirm card (goal / out-of-scope / **checkable**
162
166
  acceptance / assumptions). Vague acceptance fails the gate; validate must
@@ -190,7 +194,7 @@ mock LLM — no paid API).
190
194
  → relaunch on the same worktree (preempt leftover Agents before enqueue).
191
195
  Progress tracks this revision’s `R{n}-*` tasks. Failed launch rolls back the
192
196
  Brief Revision; status shows Terminal busy/queue per lane.
193
- 9. If the job needs hosting / a public URL, pick a **planned host** on the desk
197
+ 9. If the job needs hosting / a public URL, pick a **planned host** on the console
194
198
  (Cloudflare / Alibaba Cloud / AWS / GitHub Pages) so code matches that
195
199
  platform ([`docs/agent/deploy-targets.md`](docs/agent/deploy-targets.md)).
196
200
  Default when unspecified: **GitHub CLI (`gh`) + Actions**
package/README.zh-CN.md CHANGED
@@ -6,11 +6,11 @@
6
6
 
7
7
  **Duaer** 把 AI 编程助手变成能**满意交付**的**数字员工**:你用自然语言说意图,它先把需求规范成可检查的 Brief(目标 / 边界 / 验收),再按范围实现,对照验收通过才算完成。
8
8
 
9
- 主打的是 **规范 → 满意交付**——不是让人学会操作 Spec 阶段或 slash 命令。
9
+ 主打的是 **规范 → 满意交付**——不是让人学会 Spec 阶段、slash 命令,或一整套专业交付流程。
10
10
 
11
- 你**不用**操作流程。装一次之后,在 **Cursor** / **Claude Code** / **Codex** / **Copilot** / **Windsurf** / **Cline** / **Continue** / **Gemini** / **Aider** 里说话即可。
11
+ 你**不必懂**那些流程。装一次之后,在 **Cursor** 或 **Claude Code** 里用平常话交代即可;流程由数字员工自己跑。
12
12
 
13
- `init` / `update` 会写入各宿主适配文件(详见 [`ADOPT.md`](ADOPT.md))。
13
+ `init` / `update` 还会给其它编辑器写可选适配(见 [`ADOPT.md`](ADOPT.md));控制台派工只启动 **Cursor Agent** 或 **Claude Code**。
14
14
 
15
15
  ## 我们有什么
16
16
 
@@ -21,29 +21,29 @@
21
21
  - **你平常怎么说,就怎么交代。** 不用记 slash 命令、不用自己点「写 Spec / 拆任务」。
22
22
  - **先谈清楚再动手。** 它会把你的想法整理成:要做什么、不做什么、怎样算做完。
23
23
  - **做完才算完。** 对照验收通过才会说「交付好了」;半成品不会假装完成。
24
- - **多宿主可用。** Cursor、Claude Code、Codex、Copilot、Windsurf、Cline、Continue、Gemini、Aider 都行。
24
+ - **支持 Cursor 与 Claude Code。** 装一次后直接在里面说话,不必学专业交付流程。(其它编辑器可选适配见 [`ADOPT.md`](ADOPT.md)。)
25
25
 
26
- ### 在浏览器里:Duaer-spec FDE(现场台面)
26
+ ### 在浏览器里:Duaer-spec FDE(现场控制台)
27
27
 
28
- 本机开一个网页台面(`duaer live`),专门管「从聊需求到派工、看进度、看结果」:
28
+ 本机开一个网页控制台(`duaer live`),专门管「从聊需求到派工、看进度、看结果」:
29
29
 
30
30
  - **项目管理。** 建项目、切换项目;每个项目自己的对话和进度,互不搅在一起。
31
- - **聊着把需求聊清楚。** 左边聊天;中间把目标、边界、验收写成确认卡;空话验收过不了,得能检查才给确认。
31
+ - **聊着把需求聊清楚。** 左边聊天;中间把目标、边界、验收写成确认卡;空话验收过不了,得能检查才给确认。发送旁可点 **录音**;在设置里配置 **语音识别模型**(兼容 OpenAI `/audio/transcriptions`,如 whisper-1)后,录完会解码成文字填入输入框,改完再发送。
32
32
  - **大需求可拆成多个模块。** 一块一块确认,确认完一块不会立刻开工,全部谈妥再一起派。
33
- - **系统架构图。** 确认需求后一起看架构;点图可全屏查看;架构确认后才能派工。
33
+ - **系统架构图。** 确认需求后一起看架构;点图可全屏查看;架构确认后才能派工。若对话称已生成但中间栏没有图,同一条气泡会改成提示并提供「重新生成架构图」,不会留下假的「已生成」。
34
34
  - **一键派数字员工写代码。** 选产品目录、选 Cursor Agent 或 Claude Code,点开工;它在隔离目录里改代码,不直接乱动你的主分支。
35
- - **任务进度看得见。** 任务拆成一条条可勾选项;做完勾一项,台面实时刷新;有依赖的任务会排队放行,不会一窝蜂乱做。
35
+ - **任务进度看得见。** 任务拆成一条条可勾选项;做完勾一项,控制台实时刷新;有依赖的任务会排队放行,不会一窝蜂乱做。
36
36
  - **数字员工分工。** 顶部有「数字员工」目录(实现 / 功能回归 / 部署);可开多个并行(同一套 CLI)。详见下一节。
37
- - **中 / 繁中 / 英 / 日 / 韩 / 西 / 葡 / 法 / 德 / 俄 / 越界面。** 台面文案可切换语种。
37
+ - **中 / 繁中 / 英 / 日 / 韩 / 西 / 葡 / 法 / 德 / 俄 / 越界面。** 控制台文案可切换语种。
38
38
  - **看交付物和结果。** 可打开交付物页(需求时间线、架构、任务、交付记录);做完了可打开页面或项目文件夹。
39
39
  - **不满意就继续改。** 左侧继续聊改进 → 再确认 → 同一次任务上续派,不用从头建仓。部署后结果栏仍可「再改一版」和「修 bug」。
40
40
  - **修 bug。** 走缺陷卡(不拆多模块),默认跳过架构,短任务池,开 `fix/` 分支(可选从 `main` 出紧急修复)。交付过的地址/启动信息会自动带入;缺陷和迭代排在同一条项目时间线上。
41
41
  - **快速导航。** 对话栏右缘「导航」可跳到:对话底部 · 确认卡 · 结果 · 进度。
42
- - **调度中心。** 独立页看派工图,顶栏与台面一致;长任务链会换行,节点显示实时状态。
42
+ - **调度中心。** 独立页看派工图与各项目的系统架构,顶栏与控制台一致;长任务链会换行,节点显示实时状态。
43
43
 
44
44
  ### 数字员工:三种角色
45
45
 
46
- 台面顶部点「数字员工」可看目录。运行时仍是 Cursor Agent 或 Claude Code;差别在**任务角色**——谁做实现、谁做回归、谁做部署。
46
+ 控制台顶部点「数字员工」可看目录。运行时仍是 Cursor Agent 或 Claude Code;差别在**任务角色**——谁做实现、谁做回归、谁做部署。
47
47
 
48
48
  | 角色 | 干什么 | 不干什么 |
49
49
  |---|---|---|
@@ -53,19 +53,19 @@
53
53
 
54
54
  多人并行时:默认末位泳道承担**功能回归**(以及部署任务);实现任务在前面的泳道。一个人时:同一 CLI 会话按任务角色依次做完。
55
55
 
56
- 台面在放行前会自己跑产品仓 `.duaer/memory/verify.json` 里的命令。退出码不是 0,或没有这份契约,就不会保持「已交付」。纯文档任务要显式写 `{ "waiver": "docs-only" }`。
56
+ 控制台在放行前会自己跑产品仓 `.duaer/memory/verify.json` 里的命令。退出码不是 0,或没有这份契约,就不会保持「已交付」。纯文档任务要显式写 `{ "waiver": "docs-only" }`。
57
57
 
58
58
  ### 部署:支持的几种方式
59
59
 
60
- 开工前在台面选 **计划托管平台**。选好后,数字员工按该平台约束写代码,并由**部署员工**负责上线(把公网地址写进交付预览)。
60
+ 开工前在控制台选 **计划托管平台**。选好后,数字员工按该平台约束写代码,并由**部署员工**负责上线(把公网地址写进交付预览)。
61
61
 
62
62
  | 方式 | 适合做什么 | 你要准备什么 |
63
63
  |---|---|---|
64
64
  | **暂不部署** | 只在本地做完、先不发公网 | 无 |
65
65
  | **GitHub Pages** | 静态站 / 文档站;未选平台但文案要上线时的默认 | GitHub 仓库与 `gh` 登录 |
66
- | **Cloudflare** | 静态站用 Pages;接口 / 边缘逻辑用 Workers | 在台面设置里保存 API Token 与 Account ID |
67
- | **阿里云** | 静态多用 OSS(+ CDN);接口可用函数计算等 | 在台面设置里保存 AccessKey |
68
- | **AWS** | 静态多用 S3 + CloudFront;也可用 Amplify / Lambda 等 | 在台面设置里保存 Access Key |
66
+ | **Cloudflare** | 静态站用 Pages;接口 / 边缘逻辑用 Workers | 在控制台设置里保存 API Token 与 Account ID |
67
+ | **阿里云** | 静态多用 OSS(+ CDN);接口可用函数计算等 | 在控制台设置里保存 AccessKey |
68
+ | **AWS** | 静态多用 S3 + CloudFront;也可用 Amplify / Lambda 等 | 在控制台设置里保存 Access Key |
69
69
 
70
70
  细则:[`docs/agent/deploy-targets.md`](docs/agent/deploy-targets.md) · GitHub 默认路径:[`docs/agent/deploy-github.md`](docs/agent/deploy-github.md)
71
71
 
@@ -120,7 +120,7 @@ npx duaer-spec update
120
120
  **Duaer-spec FDE** = Field Development Environment(现场开发)。
121
121
 
122
122
  ```bash
123
- # 启动(任意目录都行,不会写入当前项目;未配模型也会打开台面)
123
+ # 启动(任意目录都行,不会写入当前项目;未配模型也会打开控制台)
124
124
  duaer live
125
125
 
126
126
  # 在打开的页面「设置」里填模型,或用 CLI:
@@ -134,17 +134,17 @@ duaer live config --base-url https://api.openai.com/v1 --api-key sk-... --model
134
134
  ```
135
135
 
136
136
  启动后会自动打开浏览器(默认 `http://127.0.0.1:8787`)。未配置模型时请在页面
137
- **设置**里填写并保存。台面**全屏宽**,三列:
137
+ **设置**里填写并保存。控制台**全屏宽**,三列:
138
138
  **对话** | **模块 / 确认 / 架构 / 开工 / 改进** | **任务进度**。细则见
139
139
  [`docs/agent/live-desk.md`](docs/agent/live-desk.md)。
140
140
 
141
141
  **验收(维护者 / 数字员工):** 改 Duaer-spec FDE UI 或确认校验门禁后,跑
142
- `npm run test:live`(L3 冒烟:台面结构标记 + `/api/validate`,内置 mock 模型,
142
+ `npm run test:live`(L3 冒烟:控制台结构标记 + `/api/validate`,内置 mock 模型,
143
143
  不产生付费 API 调用)。
144
144
 
145
145
  **流程**
146
146
 
147
- 1. 打开或新建**项目**,再聊天。右上角「项目」列表会显示每个项目的**交付状态**;有内容时可直接打开**交付物**。话题可以乱跳:台面会演进 **`modules[]`**(模块页签)。气泡支持 **Markdown**(加粗、代码、链接)。
147
+ 1. 打开或新建**项目**,再聊天。右上角「项目」列表会显示每个项目的**交付状态**;有内容时可直接打开**交付物**。话题可以乱跳:控制台会演进 **`modules[]`**(模块页签)。气泡支持 **Markdown**(加粗、代码、链接)。
148
148
  2. **按模块**填确认卡(目标 / 不做 / **可检查验收** / 假设)。空泛验收过不了门禁;须校验通过才能点确认。确认只**锁住该模块卡**,**不会**立刻写 Brief 或开工。模块之间可随时切换。
149
149
  3. 关心的模块都确认完 → 审 / 确认**架构**(Archify)→ **开工(kickoff)**。开工才写 Brief,建带 **`dependsOn` 的任务池**,并分派。默认 **1** 个数字员工;可选 **N** 个同 CLI 并行。共享任务给 1 号;模块任务轮询。每个 worker 独占一条 **Terminal 队列泳道**(`live-terminal` / `live-terminal/w2`…)。
150
150
  4. 浏览 / 扫描 / 点选产品仓,或粘贴路径 / 项目名。路径不存在会自动创建;可先设**产品父目录**,再填短名在其下新建。非 git 目录会 `git init -b develop`;没有 develop/main/master 会建本地 `develop`。
@@ -155,7 +155,7 @@ duaer live config --base-url https://api.openai.com/v1 --api-key sk-... --model
155
155
  6. 编辑「启动命令」(须以 `Duaer` 开头)→ 开工建 `.worktree/feat-*`,并为每条 worker 泳道打开 **Terminal**。
156
156
  7. 右侧进度列先提示**下一步**(阶段条),并轮询 `tasks.md`。进度上方 **查看交付物** 会打开生成的 HTML 页(需求文档时间线、确认书、架构、任务池、交付等)。交付时数字员工先更新产品仓 **README**,再把 `delivery.json` 标为 `accepted`。然后出现 **结果**(有页面时 **查看结果**;否则 **打开项目目录**)。
157
157
  8. 成品不满意:点 **继续改进(左侧对话)** → 确认改进卡(同样须校验)→ 再确认架构(保留或重画)→ 同 worktree 续派(先清残留 Agent 再入队)。进度跟本轮 `R{n}-*`。启动失败会回滚本轮 Brief Revision;状态行按泳道显示 Terminal 忙闲/队列。
158
- 9. 若需要上线 / 公网:在台面选 **计划托管平台**(Cloudflare / 阿里云 / AWS / GitHub Pages),说明见 [`docs/agent/deploy-targets.md`](docs/agent/deploy-targets.md)。未指定时默认 **GitHub CLI(`gh`)+ Actions**([`docs/agent/deploy-github.md`](docs/agent/deploy-github.md))
158
+ 9. 若需要上线 / 公网:在控制台选 **计划托管平台**(Cloudflare / 阿里云 / AWS / GitHub Pages),说明见 [`docs/agent/deploy-targets.md`](docs/agent/deploy-targets.md)。未指定时默认 **GitHub CLI(`gh`)+ Actions**([`docs/agent/deploy-github.md`](docs/agent/deploy-github.md))
159
159
 
160
160
  ### 文档语言
161
161
 
@@ -354,6 +354,9 @@ function emptyLiveConfig() {
354
354
  baseUrl: "",
355
355
  apiKey: "",
356
356
  model: "",
357
+ sttBaseUrl: "",
358
+ sttApiKey: "",
359
+ sttModel: "",
357
360
  preferredAgentId: "",
358
361
  projectsRoot: "",
359
362
  activeProjectPath: "",
@@ -383,6 +386,15 @@ function readConfig() {
383
386
  baseUrl: String(raw.baseUrl || process.env.DUAER_LIVE_BASE_URL || "").trim(),
384
387
  apiKey: String(raw.apiKey || process.env.DUAER_LIVE_API_KEY || "").trim(),
385
388
  model: String(raw.model || process.env.DUAER_LIVE_MODEL || "").trim(),
389
+ sttBaseUrl: String(
390
+ raw.sttBaseUrl || process.env.DUAER_LIVE_STT_BASE_URL || "",
391
+ ).trim(),
392
+ sttApiKey: String(
393
+ raw.sttApiKey || process.env.DUAER_LIVE_STT_API_KEY || "",
394
+ ).trim(),
395
+ sttModel: String(
396
+ raw.sttModel || process.env.DUAER_LIVE_STT_MODEL || "",
397
+ ).trim(),
386
398
  preferredAgentId: String(raw.preferredAgentId || "").trim(),
387
399
  projectsRoot: String(raw.projectsRoot || "").trim(),
388
400
  activeProjectPath: String(raw.activeProjectPath || "").trim(),
@@ -427,6 +439,9 @@ function writeConfig(partial) {
427
439
  baseUrl: pick("baseUrl"),
428
440
  apiKey: pick("apiKey"),
429
441
  model: pick("model"),
442
+ sttBaseUrl: pick("sttBaseUrl"),
443
+ sttApiKey: pick("sttApiKey"),
444
+ sttModel: pick("sttModel"),
430
445
  preferredAgentId: pick("preferredAgentId"),
431
446
  projectsRoot:
432
447
  partial.projectsRoot !== undefined
@@ -452,6 +467,21 @@ function configReady(cfg = readConfig()) {
452
467
  return Boolean(cfg.baseUrl && cfg.apiKey && cfg.model);
453
468
  }
454
469
 
470
+ function sttConfigReady(cfg = readConfig()) {
471
+ return Boolean(cfg.sttBaseUrl && cfg.sttApiKey && cfg.sttModel);
472
+ }
473
+
474
+ /** OpenAI-compatible audio transcriptions URL from a Base URL. */
475
+ function sttTranscriptionsUrl(baseUrl) {
476
+ const base = String(baseUrl || "")
477
+ .trim()
478
+ .replace(/\/$/, "");
479
+ if (!base) return "";
480
+ if (/\/audio\/transcriptions$/i.test(base)) return base;
481
+ if (/\/v1$/i.test(base)) return `${base}/audio/transcriptions`;
482
+ return `${base}/v1/audio/transcriptions`;
483
+ }
484
+
455
485
  /** Both AccessKey ID and Secret present → 阿里云 may appear in deploy UI. */
456
486
  function hasAliyunCredentials(cfg = readConfig()) {
457
487
  return Boolean(
@@ -560,6 +590,10 @@ function publicConfig(cfg = readConfig()) {
560
590
  baseUrl: cfg.baseUrl || "",
561
591
  model: cfg.model || "",
562
592
  hasApiKey: Boolean(cfg.apiKey),
593
+ sttReady: sttConfigReady(cfg),
594
+ sttBaseUrl: cfg.sttBaseUrl || "",
595
+ sttModel: cfg.sttModel || "",
596
+ hasSttApiKey: Boolean(cfg.sttApiKey),
563
597
  hasAliyunCredentials: hasAliyunCredentials(cfg),
564
598
  hasCloudflareCredentials: hasCloudflareCredentials(cfg),
565
599
  hasAwsCredentials: hasAwsCredentials(cfg),
@@ -718,7 +752,7 @@ const BUG_CHAT_PROMPT = `你是「Duaer-spec FDE」缺陷助手。用户要修 b
718
752
  - outOfScope = 本次不改什么
719
753
  - acceptance = 怎么算修好(复现关闭 + 可核对结果/命令)
720
754
  - assumptions = 环境 / 疑似原因 / 是否线上紧急(优先写入系统给的「项目交付上下文」)
721
- 4. **禁止向用户追问**网页/成品 URL、服务启动命令或脚本、机器/端口/依赖等运行环境、疑似原因、是否线上紧急——这些由台面从项目交付自动补齐。疑似原因未知时写「待复现定位」;线上紧急默认「否」(从 develop 出 fix/),仅当用户明确说线上/生产/紧急才标「是」。
755
+ 4. **禁止向用户追问**网页/成品 URL、服务启动命令或脚本、机器/端口/依赖等运行环境、疑似原因、是否线上紧急——这些由控制台从项目交付自动补齐。疑似原因未知时写「待复现定位」;线上紧急默认「否」(从 develop 出 fix/),仅当用户明确说线上/生产/紧急才标「是」。
722
756
  5. ready=true 表示缺陷卡可确认。不要催派工。不要写代码。不要假设用户没给的仓库路径(上下文里有则直接用)。
723
757
  6. 只要问题是让用户做选择,必须在 options 填 2~5 个短选项(≤20字)。
724
758
  7. 输出格式(严格):
@@ -795,7 +829,7 @@ const BUG_ACCEPT_PROMPT = `你是「Duaer-spec FDE」缺陷验收官。用户即
795
829
  1. goal 是否写清现象与复现(可执行,不要堆无关功能)
796
830
  2. acceptance 是否可客观检查:复现关闭 + 打开/看到/命令通过等;禁止仅「修好了/更好用」
797
831
  3. outOfScope 是否划清本次不改什么(可简短)
798
- 4. assumptions:环境/URL/启动/紧急/疑似原因由台面交付上下文补齐——**禁止**因缺少网页地址、启动命令、运行环境、疑似原因、是否线上紧急而 failed
832
+ 4. assumptions:环境/URL/启动/紧急/疑似原因由控制台交付上下文补齐——**禁止**因缺少网页地址、启动命令、运行环境、疑似原因、是否线上紧急而 failed
799
833
 
800
834
  规则:
801
835
  - 若小改即可通过:修订四块,passed=true;assumptions 空缺时写入合理默认(疑似原因=待复现定位;线上紧急=否)
@@ -823,6 +857,69 @@ function llmTimeoutError(ms) {
823
857
  );
824
858
  }
825
859
 
860
+ /**
861
+ * POST audio to OpenAI-compatible /audio/transcriptions.
862
+ * @param {object} cfg
863
+ * @param {{ buffer: Buffer, mimeType?: string, filename?: string, language?: string }} audio
864
+ */
865
+ async function callSttTranscribe(cfg, audio) {
866
+ const url = sttTranscriptionsUrl(cfg.sttBaseUrl);
867
+ if (!url) throw new Error("STT baseUrl missing");
868
+ const mime = String(audio.mimeType || "audio/webm").trim() || "audio/webm";
869
+ const filename =
870
+ String(audio.filename || "").trim() ||
871
+ (mime.includes("mp4") || mime.includes("m4a")
872
+ ? "voice.m4a"
873
+ : mime.includes("mpeg") || mime.includes("mp3")
874
+ ? "voice.mp3"
875
+ : mime.includes("wav")
876
+ ? "voice.wav"
877
+ : mime.includes("ogg")
878
+ ? "voice.ogg"
879
+ : "voice.webm");
880
+ const form = new FormData();
881
+ form.append("file", new Blob([audio.buffer], { type: mime }), filename);
882
+ form.append("model", String(cfg.sttModel).trim());
883
+ const lang = String(audio.language || "").trim();
884
+ if (lang) form.append("language", lang);
885
+ const ac = new AbortController();
886
+ const timer = setTimeout(() => ac.abort(), LLM_TIMEOUT_MS);
887
+ try {
888
+ const res = await fetch(url, {
889
+ method: "POST",
890
+ headers: {
891
+ authorization: `Bearer ${cfg.sttApiKey}`,
892
+ },
893
+ body: form,
894
+ signal: ac.signal,
895
+ });
896
+ const text = await res.text();
897
+ let data = null;
898
+ try {
899
+ data = text ? JSON.parse(text) : null;
900
+ } catch {
901
+ data = null;
902
+ }
903
+ if (!res.ok) {
904
+ const msg =
905
+ (data && (data.error?.message || data.error || data.message)) ||
906
+ text.slice(0, 240) ||
907
+ `HTTP ${res.status}`;
908
+ throw new Error(String(msg));
909
+ }
910
+ const transcript = String(
911
+ data?.text || data?.transcript || data?.result || "",
912
+ ).trim();
913
+ if (!transcript) throw new Error("empty transcript");
914
+ return transcript;
915
+ } catch (err) {
916
+ if (err?.name === "AbortError") throw llmTimeoutError(LLM_TIMEOUT_MS);
917
+ throw err;
918
+ } finally {
919
+ clearTimeout(timer);
920
+ }
921
+ }
922
+
826
923
  async function callChatModel(cfg, messages, systemPrompt = SYSTEM_PROMPT) {
827
924
  const base = cfg.baseUrl.replace(/\/$/, "");
828
925
  const url = `${base}/chat/completions`;
@@ -3817,7 +3914,7 @@ Brief: ${featureDir}
3817
3914
  4. 每完成 tasks.md 中的一步,立刻把该行改成 - [x](Duaer-spec FDE 靠此显示细粒度进度与编排放行)
3818
3915
  4a. 只做当前编排波次已放行的任务;未满足 dependsOn / 未放行的任务不要开工;本波勾完后:若还有后续波次则输出「本波完成,退出等编排器」并立刻结束本次 CLI 会话(不要挂起;编排器用 --continue 放行);若已无后续波次则继续完成 stamp accepted
3819
3916
  4b. 拆任务(强制):必须拆到原子任务且每条可独立验证;每个勾选项只覆盖一个验收点;进度只能用 tasks.md 的 - [ ]/- [x] 监控(立刻勾选,不要攒到最后);不要把多项验收揉进同一条;不要人为限制条数。若仍偏粗,先按 Acceptance 扩成「一条验收一勾选」(仍用 T00x),保存后再做
3820
- 5. 对照 Acceptance 全部满足后,才 stamp ${path.join(featureDir, "delivery.json")} 为 accepted——tasks.md 全部勾完还不够,必须 stamp;禁止停在 Job not accepted yet。台面会按产品仓 .duaer/memory/verify.json 自己跑 commands;退出码非 0 或缺少契约时会把 accepted 打回 open。不要把已有 commands 改成 waiver 来跳过
3917
+ 5. 对照 Acceptance 全部满足后,才 stamp ${path.join(featureDir, "delivery.json")} 为 accepted——tasks.md 全部勾完还不够,必须 stamp;禁止停在 Job not accepted yet。控制台会按产品仓 .duaer/memory/verify.json 自己跑 commands;退出码非 0 或缺少契约时会把 accepted 打回 open。不要把已有 commands 改成 waiver 来跳过
3821
3918
  5b. 交付前必须更新产品仓 README(说明文档):与本次交付一致——做什么、模块/验收要点、如何运行或打开;需求变了就改 README,不要只改代码。英文 README 不得出现中文;若项目是中文说明则用 README.zh-CN.md(或项目既有约定),可夹英文术语
3822
3919
  6. 必须在 delivery.json 写入 preview.url(满意交付的必填证据):必须是可打开的成品入口——HTTP 服务用 http://localhost:…;静态页用 index.html 等 HTML。禁止把 docs/**/*.md 等说明文档当作 preview.url——不要因「没有页面」而省略
3823
3920
  6b. 若交付是 HTTP 服务:验收前必须先把服务跑起来(如 npm start),确认能打开 preview.url 后再 stamp accepted;不要只写地址却不启动
@@ -5884,7 +5981,9 @@ async function handleApi(req, res) {
5884
5981
  "awsRegion",
5885
5982
  "clearAwsCredentials",
5886
5983
  ];
5984
+ const sttKeys = ["sttBaseUrl", "sttApiKey", "sttModel", "clearStt"];
5887
5985
  const noHostCreds = hostCredKeys.every((k) => body[k] === undefined);
5986
+ const noStt = sttKeys.every((k) => body[k] === undefined);
5888
5987
  const onlyProjectsRoot =
5889
5988
  body.projectsRoot !== undefined &&
5890
5989
  body.baseUrl === undefined &&
@@ -5892,7 +5991,8 @@ async function handleApi(req, res) {
5892
5991
  body.model === undefined &&
5893
5992
  body.preferredAgentId === undefined &&
5894
5993
  body.activeProjectPath === undefined &&
5895
- noHostCreds;
5994
+ noHostCreds &&
5995
+ noStt;
5896
5996
  const onlyActiveProject =
5897
5997
  body.activeProjectPath !== undefined &&
5898
5998
  body.baseUrl === undefined &&
@@ -5900,7 +6000,8 @@ async function handleApi(req, res) {
5900
6000
  body.model === undefined &&
5901
6001
  body.preferredAgentId === undefined &&
5902
6002
  body.projectsRoot === undefined &&
5903
- noHostCreds;
6003
+ noHostCreds &&
6004
+ noStt;
5904
6005
  const onlyHostCreds =
5905
6006
  !noHostCreds &&
5906
6007
  body.baseUrl === undefined &&
@@ -5908,7 +6009,17 @@ async function handleApi(req, res) {
5908
6009
  body.model === undefined &&
5909
6010
  body.preferredAgentId === undefined &&
5910
6011
  body.projectsRoot === undefined &&
5911
- body.activeProjectPath === undefined;
6012
+ body.activeProjectPath === undefined &&
6013
+ noStt;
6014
+ const onlyStt =
6015
+ !noStt &&
6016
+ body.baseUrl === undefined &&
6017
+ body.apiKey === undefined &&
6018
+ body.model === undefined &&
6019
+ body.preferredAgentId === undefined &&
6020
+ body.projectsRoot === undefined &&
6021
+ body.activeProjectPath === undefined &&
6022
+ noHostCreds;
5912
6023
  let projectsRoot = body.projectsRoot;
5913
6024
  if (projectsRoot !== undefined) {
5914
6025
  const raw = String(projectsRoot || "").trim();
@@ -5966,6 +6077,23 @@ async function handleApi(req, res) {
5966
6077
  hostPartial.awsRegion = body.awsRegion;
5967
6078
  }
5968
6079
  }
6080
+ const sttPartial = {};
6081
+ if (body.clearStt) {
6082
+ sttPartial.sttBaseUrl = "";
6083
+ sttPartial.sttApiKey = "";
6084
+ sttPartial.sttModel = "";
6085
+ } else {
6086
+ if (body.sttBaseUrl !== undefined) {
6087
+ sttPartial.sttBaseUrl = body.sttBaseUrl;
6088
+ }
6089
+ if (body.sttModel !== undefined) {
6090
+ sttPartial.sttModel = body.sttModel;
6091
+ }
6092
+ if (body.sttApiKey !== undefined) {
6093
+ const sttKey = String(body.sttApiKey || "").trim();
6094
+ if (sttKey) sttPartial.sttApiKey = sttKey;
6095
+ }
6096
+ }
5969
6097
  const next = writeConfig({
5970
6098
  baseUrl: body.baseUrl,
5971
6099
  apiKey: body.apiKey,
@@ -5974,8 +6102,9 @@ async function handleApi(req, res) {
5974
6102
  projectsRoot,
5975
6103
  activeProjectPath,
5976
6104
  ...hostPartial,
6105
+ ...sttPartial,
5977
6106
  });
5978
- if (onlyProjectsRoot || onlyActiveProject || onlyHostCreds) {
6107
+ if (onlyProjectsRoot || onlyActiveProject || onlyHostCreds || onlyStt) {
5979
6108
  send(res, 200, publicConfig(next));
5980
6109
  return;
5981
6110
  }
@@ -5995,6 +6124,55 @@ async function handleApi(req, res) {
5995
6124
  return;
5996
6125
  }
5997
6126
 
6127
+ if (req.method === "POST" && url.pathname === "/api/transcribe") {
6128
+ const cfg = readConfig();
6129
+ if (!sttConfigReady(cfg)) {
6130
+ send(res, 400, {
6131
+ error: "请先在设置中配置语音识别模型(STT Base URL / API Key / Model)",
6132
+ code: "NEED_STT",
6133
+ ...publicConfig(cfg),
6134
+ });
6135
+ return;
6136
+ }
6137
+ try {
6138
+ const body = await readJson(req);
6139
+ const b64 = String(body.audioBase64 || body.audio || "").trim();
6140
+ if (!b64) {
6141
+ send(res, 400, { error: "audioBase64 required" });
6142
+ return;
6143
+ }
6144
+ const raw = b64.replace(/^data:[^;]+;base64,/, "");
6145
+ let buffer;
6146
+ try {
6147
+ buffer = Buffer.from(raw, "base64");
6148
+ } catch {
6149
+ send(res, 400, { error: "invalid audioBase64" });
6150
+ return;
6151
+ }
6152
+ if (!buffer.length) {
6153
+ send(res, 400, { error: "empty audio" });
6154
+ return;
6155
+ }
6156
+ if (buffer.length > 12 * 1024 * 1024) {
6157
+ send(res, 400, { error: "audio too large (max 12MB)" });
6158
+ return;
6159
+ }
6160
+ const text = await callSttTranscribe(cfg, {
6161
+ buffer,
6162
+ mimeType: String(body.mimeType || body.type || "").trim(),
6163
+ filename: String(body.filename || "").trim(),
6164
+ language: String(body.language || "").trim(),
6165
+ });
6166
+ send(res, 200, { text, model: cfg.sttModel });
6167
+ } catch (err) {
6168
+ send(res, 502, {
6169
+ error: err instanceof Error ? err.message : "transcribe failed",
6170
+ code: "STT_FAILED",
6171
+ });
6172
+ }
6173
+ return;
6174
+ }
6175
+
5998
6176
  if (req.method === "POST" && url.pathname === "/api/chat") {
5999
6177
  const cfg = readConfig();
6000
6178
  if (!configReady(cfg)) {
@@ -7358,7 +7536,7 @@ function serve(port) {
7358
7536
  console.log(`Brief 写入 ${jobsRoot()} (不会写入你当前业务仓库)`);
7359
7537
  if (!cfg.ready) {
7360
7538
  console.log(
7361
- `模型尚未配置 — 已打开台面,请在页面「设置」里填写模型后保存。`,
7539
+ `模型尚未配置 — 已打开控制台,请在页面「设置」里填写模型后保存。`,
7362
7540
  );
7363
7541
  } else {
7364
7542
  console.log(`模型 ${cfg.model} @ ${cfg.baseUrl}`);
@@ -127,7 +127,7 @@ export function collectBugProjectContext(input = {}) {
127
127
  */
128
128
  export function formatBugProjectContextBlock(ctx) {
129
129
  const c = ctx && typeof ctx === "object" ? ctx : {};
130
- const lines = ["【项目交付上下文——已从台面/交付得知,禁止再向用户追问这些项】"];
130
+ const lines = ["【项目交付上下文——已从控制台/交付得知,禁止再向用户追问这些项】"];
131
131
  if (c.projectPath) lines.push(`- 仓库路径: ${c.projectPath}`);
132
132
  if (c.previewUrl) lines.push(`- 成品/预览地址: ${c.previewUrl}`);
133
133
  else if (c.inferredPreviewUrl) {
@@ -419,7 +419,7 @@ export function buildDeliverablesModel(session, opts = {}) {
419
419
  ? pickLang(lang, {
420
420
  en: "Architecture confirmed on the desk.",
421
421
  ja: "デスクでアーキテクチャを確認済みです。",
422
- zh: "已在台面确认架构。",
422
+ zh: "已在控制台确认架构。",
423
423
  })
424
424
  : "",
425
425
  },
@@ -360,7 +360,7 @@ Brief: ${featureDir || ""}
360
360
  产品仓需要 \`.duaer/memory/verify.json\`。
361
361
  - 有可跑测试时写入 { "commands": ["<真实命令>"] }(例如 npm test)
362
362
  - 只有纯文档、没有可跑命令时才允许 { "waiver": "docs-only" }
363
- 不要 stamp delivery.json 为 accepted。台面会自己复跑;失败会把 accepted 打回 open。
363
+ 不要 stamp delivery.json 为 accepted。控制台会自己复跑;失败会把 accepted 打回 open。
364
364
  `;
365
365
  }
366
366
  return `Duaer
@@ -373,6 +373,6 @@ Brief: ${featureDir || ""}
373
373
  输出尾部:
374
374
  ${nudge.tail || "(无)"}
375
375
 
376
- 修好并使上述命令退出码为 0。不要把已有 commands 改成 waiver 来跳过。不要 stamp accepted;台面会复跑,通过后才允许 accepted。
376
+ 修好并使上述命令退出码为 0。不要把已有 commands 改成 waiver 来跳过。不要 stamp accepted;控制台会复跑,通过后才允许 accepted。
377
377
  `;
378
378
  }
@@ -164,6 +164,11 @@ the table for their app.
164
164
  | E2E-192 | Project timeline merges initial/revise/bug by time; chat quick-nav jumps to 4 anchors | `npm test` + `npm run test:live` + manual |
165
165
  | E2E-193 | Chat quick-nav stays visible inside chat column (not clipped/covered) | `npm run test:live` + manual |
166
166
  | E2E-194 | After deploy, result bar still offers 再改一版 and 修 bug | `npm run test:live` + manual |
167
+ | E2E-195 | Architecture claim-ready without IR rewrites the same chat bubble (no leftover false “diagram ready”) + regenerate | `npm run test:live` + manual |
168
+ | E2E-196 | Dispatch center per-project view switch: Dispatch graph | Architecture (saved architecture.url) | `npm test` + manual |
169
+ | E2E-197 | Composer Record + Send in compact `.composer-actions`; with STT configured, stop → `/api/transcribe` fills `#input`; without STT, Record opens Settings speech-model block | `npm run test:live` + manual |
170
+ | E2E-198 | Settings speech recognition model block saves `sttBaseUrl` / `sttApiKey` / `sttModel`; publicConfig exposes `sttReady` | `npm test` + `npm run test:live` |
171
+ | E2E-199 | Record click with no STT shows bot guide and opens Settings → speech model | `npm run test:live` + manual |
167
172
 
168
173
  | E2E-156 | Accepted chat bubble preview URL is a clickable `<a>` (http(s) or /api/…) | `npm test` + `npm run test:live` |
169
174
  | E2E-157 | `http://host/api/result/…` chat links render as one clean `<a>` (no nested HTML garble) | `npm test` |
@@ -393,4 +398,9 @@ the table for their app.
393
398
  | E2E-192 | `247-timeline-quicknav` | timeline sort + chat quick nav |
394
399
  | E2E-193 | `248-quicknav-visible` | quick-nav not clipped by overflow |
395
400
  | E2E-194 | `249-result-cta-after-deploy` | result CTAs remain after deploy |
401
+ | E2E-195 | `251-arch-claim-no-diagram` | rewrite claim-ready bubble when IR missing |
402
+ | E2E-196 | `255-dispatch-arch-view` | dispatch center per-project architecture view |
403
+ | E2E-197 | `256-chat-voice` / `257-composer-voice-compact` | composer voice / Record beside Send |
404
+ | E2E-198 | `258-chat-stt` | STT model settings + /api/transcribe |
405
+ | E2E-199 | `259-voice-guide-stt` | Record without STT opens Settings |
396
406