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.
- package/.claude/rules/ai-ui-copy.md +2 -2
- package/.cursor/rules/ai-ui-copy.mdc +3 -3
- package/.cursor/rules/fde-8787-only.mdc +7 -7
- package/.duaer/active-job.json +3 -3
- package/.duaer/specs/250-release-025/delivery.json +17 -0
- package/.duaer/specs/250-release-025/tasks.md +1 -1
- package/.duaer/specs/251-arch-claim-no-diagram/delivery.json +13 -0
- package/.duaer/specs/251-arch-claim-no-diagram/spec.md +23 -0
- package/.duaer/specs/251-arch-claim-no-diagram/tasks.md +5 -0
- package/.duaer/specs/252-release-0251/delivery.json +14 -0
- package/.duaer/specs/252-release-0251/spec.md +13 -0
- package/.duaer/specs/252-release-0251/tasks.md +3 -0
- package/.duaer/specs/253-docs-cursor-claude/delivery.json +9 -0
- package/.duaer/specs/253-docs-cursor-claude/spec.md +25 -0
- package/.duaer/specs/253-docs-cursor-claude/tasks.md +3 -0
- package/.duaer/specs/254-console-rename/delivery.json +13 -0
- package/.duaer/specs/254-console-rename/spec.md +27 -0
- package/.duaer/specs/254-console-rename/tasks.md +4 -0
- package/.duaer/specs/255-dispatch-arch-view/delivery.json +13 -0
- package/.duaer/specs/255-dispatch-arch-view/spec.md +16 -0
- package/.duaer/specs/255-dispatch-arch-view/tasks.md +4 -0
- package/.duaer/specs/256-chat-voice/delivery.json +13 -0
- package/.duaer/specs/256-chat-voice/spec.md +15 -0
- package/.duaer/specs/256-chat-voice/tasks.md +6 -0
- package/.duaer/specs/257-composer-voice-compact/delivery.json +9 -0
- package/.duaer/specs/257-composer-voice-compact/spec.md +13 -0
- package/.duaer/specs/257-composer-voice-compact/tasks.md +4 -0
- package/.duaer/specs/258-chat-stt/delivery.json +14 -0
- package/.duaer/specs/258-chat-stt/spec.md +16 -0
- package/.duaer/specs/258-chat-stt/tasks.md +5 -0
- package/.duaer/specs/259-voice-guide-stt/delivery.json +13 -0
- package/.duaer/specs/259-voice-guide-stt/spec.md +13 -0
- package/.duaer/specs/259-voice-guide-stt/tasks.md +4 -0
- package/.duaer/specs/260-release-026/delivery.json +18 -0
- package/.duaer/specs/260-release-026/spec.md +12 -0
- package/.duaer/specs/260-release-026/tasks.md +5 -0
- package/ADOPT.md +7 -4
- package/AGENTS.md +2 -2
- package/CHANGELOG.md +48 -0
- package/README.md +30 -26
- package/README.zh-CN.md +22 -22
- package/bin/duaer-live.mjs +186 -8
- package/bin/live-bug-context.mjs +1 -1
- package/bin/live-deliverables.mjs +1 -1
- package/bin/live-verify-gate.mjs +2 -2
- package/docs/agent/e2e-test-plan.md +10 -0
- package/docs/agent/live-desk.md +12 -6
- package/docs/agent/worker-models.md +7 -5
- package/docs/agent/worker-models.zh-CN.md +5 -3
- package/package.json +5 -5
- package/web/live-dev/app.js +445 -24
- package/web/live-dev/dispatch-center.html +25 -1
- package/web/live-dev/dispatch-center.js +113 -16
- package/web/live-dev/employee-catalog.mjs +2 -2
- package/web/live-dev/i18n-de.js +34 -10
- package/web/live-dev/i18n-es.js +34 -10
- package/web/live-dev/i18n-fr.js +34 -10
- package/web/live-dev/i18n-ja.js +34 -10
- package/web/live-dev/i18n-ko.js +34 -10
- package/web/live-dev/i18n-pt-BR.js +34 -10
- package/web/live-dev/i18n-ru.js +34 -10
- package/web/live-dev/i18n-vi.js +34 -10
- package/web/live-dev/i18n-zh-TW.js +36 -12
- package/web/live-dev/i18n.js +71 -23
- package/web/live-dev/index.html +55 -9
- 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
|
|
13
|
+
to operate Spec phases, slash commands, or a professional delivery process.
|
|
14
14
|
|
|
15
|
-
You
|
|
16
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
- **
|
|
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
|
|
63
|
+
### In the browser: Duaer-spec FDE (live console)
|
|
60
64
|
|
|
61
|
-
A local web
|
|
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
|
|
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.**
|
|
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
|
|
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
|
|
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
|
|
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
|
|
107
|
-
| **Alibaba Cloud** | OSS (+ CDN) for static; Function Compute and similar for APIs | AccessKey in
|
|
108
|
-
| **AWS** | S3 + CloudFront for static; Amplify / Lambda and similar when needed | Access keys in
|
|
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
|
|
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
|
|
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
|
|
146
|
-
If the model is missing, use **Settings** in the page. The
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
主打的是 **规范 →
|
|
9
|
+
主打的是 **规范 → 满意交付**——不是让人学会 Spec 阶段、slash 命令,或一整套专业交付流程。
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
你**不必懂**那些流程。装一次之后,在 **Cursor** 或 **Claude Code** 里用平常话交代即可;流程由数字员工自己跑。
|
|
12
12
|
|
|
13
|
-
`init` / `update`
|
|
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
|
-
-
|
|
24
|
+
- **支持 Cursor 与 Claude Code。** 装一次后直接在里面说话,不必学专业交付流程。(其它编辑器可选适配见 [`ADOPT.md`](ADOPT.md)。)
|
|
25
25
|
|
|
26
|
-
### 在浏览器里:Duaer-spec FDE
|
|
26
|
+
### 在浏览器里:Duaer-spec FDE(现场控制台)
|
|
27
27
|
|
|
28
|
-
|
|
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
|
-
|
|
46
|
+
控制台顶部点「数字员工」可看目录。运行时仍是 Cursor Agent 或 Claude Code;差别在**任务角色**——谁做实现、谁做回归、谁做部署。
|
|
47
47
|
|
|
48
48
|
| 角色 | 干什么 | 不干什么 |
|
|
49
49
|
|---|---|---|
|
|
@@ -53,19 +53,19 @@
|
|
|
53
53
|
|
|
54
54
|
多人并行时:默认末位泳道承担**功能回归**(以及部署任务);实现任务在前面的泳道。一个人时:同一 CLI 会话按任务角色依次做完。
|
|
55
55
|
|
|
56
|
-
|
|
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 |
|
|
67
|
-
| **阿里云** | 静态多用 OSS(+ CDN);接口可用函数计算等 |
|
|
68
|
-
| **AWS** | 静态多用 S3 + CloudFront;也可用 Amplify / Lambda 等 |
|
|
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
|
|
142
|
+
`npm run test:live`(L3 冒烟:控制台结构标记 + `/api/validate`,内置 mock 模型,
|
|
143
143
|
不产生付费 API 调用)。
|
|
144
144
|
|
|
145
145
|
**流程**
|
|
146
146
|
|
|
147
|
-
1.
|
|
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. 若需要上线 /
|
|
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
|
|
package/bin/duaer-live.mjs
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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}`);
|
package/bin/live-bug-context.mjs
CHANGED
|
@@ -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) {
|
package/bin/live-verify-gate.mjs
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|