@twinsearth/dsh-master 3.3.3 → 3.3.5
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/README.md +17 -0
- package/client.js +126 -1
- package/cordis.patch.yml +51 -0
- package/dist-host/agents/pjm-agent.js +62 -0
- package/dist-host/agents/pm-agent.js +57 -0
- package/dist-host/agents/prompts.js +71 -0
- package/dist-host/agents/toolkit.js +64 -0
- package/dist-host/channel/abort-registry.js +93 -0
- package/dist-host/channel/protocol.js +114 -0
- package/dist-host/channel/server.js +349 -0
- package/dist-host/channel/stream-state.js +343 -0
- package/dist-host/channel/ws.js +199 -0
- package/dist-host/dual/documents.js +143 -0
- package/dist-host/dual/orchestrator.js +214 -0
- package/dist-host/dual/schema.js +367 -0
- package/dist-host/engine/atomic-switch.js +159 -0
- package/dist-host/engine/health-probe.js +86 -0
- package/dist-host/engine/index.js +18 -0
- package/dist-host/engine/llm-stream.js +215 -0
- package/dist-host/engine/plugin-updater.js +392 -0
- package/dist-host/engine/safe-update.js +150 -0
- package/dist-host/index.js +154 -3
- package/dist-host/kernel.js +27 -1
- package/dist-host/plugins/cost-optimizer.js +3 -3
- package/dist-host/plugins/dual-manager.js +374 -77
- package/docs/AGENTS-PM-PJM.md +207 -0
- package/docs/ARCHITECTURE.md +2 -0
- package/docs/CHANGELOG.md +74 -0
- package/docs/CHANNEL-WEBSOCKET.md +169 -0
- package/docs/DESIGN-command-center.md +147 -0
- package/docs/ENGINE-PRIMITIVES.md +215 -0
- package/docs/GAP-ANALYSIS-fang-session.md +75 -0
- package/docs/REVIEW-PM-PJM-3.3.4.md +179 -0
- package/docs/examples/command-center/README.md +167 -0
- package/package.json +5 -1
- package/src/agents/pjm-agent.ts +62 -0
- package/src/agents/pm-agent.ts +58 -0
- package/src/agents/prompts.ts +74 -0
- package/src/agents/toolkit.ts +64 -0
- package/src/channel/abort-registry.ts +85 -0
- package/src/channel/protocol.ts +110 -0
- package/src/channel/server.ts +289 -0
- package/src/channel/stream-state.ts +349 -0
- package/src/channel/ws.ts +182 -0
- package/src/dual/documents.ts +142 -0
- package/src/dual/orchestrator.ts +223 -0
- package/src/dual/schema.ts +355 -0
- package/src/engine/atomic-switch.ts +146 -0
- package/src/engine/health-probe.ts +84 -0
- package/src/engine/index.ts +20 -0
- package/src/engine/llm-stream.ts +189 -0
- package/src/engine/plugin-updater.ts +359 -0
- package/src/engine/safe-update.ts +148 -0
- package/src/index.ts +143 -4
- package/src/kernel.ts +28 -1
- package/src/plugins/cost-optimizer.ts +3 -3
- package/src/plugins/dual-manager.ts +337 -76
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# 房主智能体:PM(产品经理)与 PJM(项目经理)
|
|
2
|
+
|
|
3
|
+
> 3.3.4 交付。两个**独立的 Agent preset**,可在新建会话时选择;共享 `dual-manager` 服务的结构化状态。
|
|
4
|
+
> 源码:`src/agents/prompts.ts`(角色 Prompt)、`src/agents/pm-agent.ts`、`src/agents/pjm-agent.ts`、
|
|
5
|
+
> `src/dual/schema.ts`(契约与校验)、`src/dual/documents.ts`(文档渲染)。
|
|
6
|
+
|
|
7
|
+
## 1. 它们是什么 / 不是什么
|
|
8
|
+
|
|
9
|
+
| | 是 | 不是 |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| PM | 一个可选择的 Agent preset;只读代码与文档、向用户出选择题、产出 PRD 与六类盘点 | 不是 UI 装饰卡片;不执行代码、不派发分身、不发布 |
|
|
12
|
+
| PJM | 一个可选择的 Agent preset;拆 WBS、经 `fangForker.spawn` 隔离派发、汇总节点、结案 | 不替用户决定需求;没有 forker 时不会本地降级执行 |
|
|
13
|
+
|
|
14
|
+
两者都**不授予额外权限**:preset 决定“这个会话是谁”,`ROLE_MATRIX` 决定“子插件在哪个角色装配”,
|
|
15
|
+
智能体工具边界写在 `cordis.patch.yml` 的 preset 行里(只读 fs + 提问/待办/交付工具,无 shell、无写文件)。
|
|
16
|
+
|
|
17
|
+
## 2. PM · 产品经理
|
|
18
|
+
|
|
19
|
+
**WHY**:把用户和开发者的口语需求变成可核对、可验收的文档,先消除歧义再动手,避免“做完了才发现理解错”。
|
|
20
|
+
**WHAT**:结构化 PRD(WHY/WHAT/模块/约束/非目标/验收)+ 证据驱动的六类现状盘点 + 文档落盘。
|
|
21
|
+
|
|
22
|
+
角色 Prompt(完整版见 `src/agents/prompts.ts`,摘要):
|
|
23
|
+
|
|
24
|
+
```text
|
|
25
|
+
Role 房主产品经理(PM, preset: master-pm)
|
|
26
|
+
Goals WHY:解决什么问题/达成什么目标;WHAT:功能、模块、架构、结果
|
|
27
|
+
Flow 先澄清(ask_user_question,一次一个决策点,2–4 选项带推荐)
|
|
28
|
+
→ 用 read/grep/glob 读真实代码与测试
|
|
29
|
+
→ pm_create_prd / pm_create_assessment 落结构
|
|
30
|
+
→ pm_write_prd 写 Markdown → present 交付
|
|
31
|
+
Rules 不臆测、不伪造证据、不把“能实现”写成“已完成”;
|
|
32
|
+
冲突必须列双方/影响/选项;歧义必须写当前理解并等确认;
|
|
33
|
+
PM 不写生产代码、不跑 shell、不发布、不安装、不调度分身
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**六类盘点(PM 的输出契约)**
|
|
37
|
+
|
|
38
|
+
| 类别 | 必填 | 门禁 |
|
|
39
|
+
|---|---|---|
|
|
40
|
+
| completed | `evidence[]`(文件/测试/报告) | **没有证据直接拒收**,不允许自称 100% |
|
|
41
|
+
| unfinished | `reason`、`nextStep`、`progress` | `progress` 上限 99,不能写成完成 |
|
|
42
|
+
| blocked | `reason`、`blocker`、`nextStep` | 卡点必须具名,不写“暂不可行” |
|
|
43
|
+
| planned | `plan`、`estimateMinutes`、`priority` | 估时以分钟给出 |
|
|
44
|
+
| conflicts | `question`、`options[{value,label,consequence}]` | 必须给出可重新判断的选项 |
|
|
45
|
+
| ambiguities | `question`、`currentInterpretation` | 默认 `needsConfirmation: true` |
|
|
46
|
+
|
|
47
|
+
**工具**:`pm_create_prd`、`pm_create_assessment`、`pm_write_prd`、`pm_list_prds`(外加 preset 挂载的只读 fs、搜索、`ask_user_question`、`present`)。
|
|
48
|
+
|
|
49
|
+
## 3. PJM · 项目经理
|
|
50
|
+
|
|
51
|
+
**HOW**:PRD → WBS(每步 prompt/owner/场景/估时/依赖/验收)。
|
|
52
|
+
**WHO**:每步必须有 owner,未分配写 `unassigned`,不虚构人员。
|
|
53
|
+
**WHERE**:明确执行场景(宿主 / fork / workspace / DSH Web);脏任务只进隔离执行体。
|
|
54
|
+
**WHEN**:里程碑 + 估时(计划,不是承诺工期)。
|
|
55
|
+
|
|
56
|
+
角色 Prompt(摘要):
|
|
57
|
+
|
|
58
|
+
```text
|
|
59
|
+
Role 房主项目经理(PJM, preset: master-pjm)
|
|
60
|
+
Goals HOW / WHO / WHERE / WHEN
|
|
61
|
+
Flow 项目会审 → pjm_create_plan(七类内容)
|
|
62
|
+
→ pjm_dispatch_step(仅经 fangForker.spawn,无则 fail-closed)
|
|
63
|
+
→ pjm_complete_step(status/summary/evidence/actualMinutes)
|
|
64
|
+
→ pjm_project_status → pjm_write_project → pjm_close_project
|
|
65
|
+
Rules 真实状态汇报,失败/阻塞/未测量不得包装成完成;
|
|
66
|
+
不发布、不部署、不改 profile、不碰凭据;
|
|
67
|
+
不代替 PM 决定需求,不替用户回答选择题
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**七类项目管理(PJM 的输出契约)**:项目会审、项目计划(WBS)、项目需求与内容、项目时间、项目人员、测试/部署/交付/验收、项目结案。
|
|
71
|
+
|
|
72
|
+
**结案门禁**:全部步骤 `done` 才允许结案;结案必须给 `summary`;结案后计划冻结(不能再派发/改步骤)。
|
|
73
|
+
结案文档包含评审报告、验收记录、资料清单。
|
|
74
|
+
|
|
75
|
+
**工具**:`pjm_create_plan`、`pjm_dispatch_step`、`pjm_complete_step`、`pjm_project_status`、`pjm_write_project`、`pjm_close_project`(外加只读 fs、搜索、`todo_write`、`present`)。
|
|
76
|
+
|
|
77
|
+
## 4. 协作流水线
|
|
78
|
+
|
|
79
|
+
```text
|
|
80
|
+
用户口语需求
|
|
81
|
+
│ (新会话选 preset: master-pm)
|
|
82
|
+
▼
|
|
83
|
+
PM: 澄清选择题 → PRD(prd_*) → 六类盘点(review_*) → docs/pm/*.md
|
|
84
|
+
│ pm_handoff_to_pjm:验收标准齐全 + 无未决问题/冲突 ⇒ ready
|
|
85
|
+
▼
|
|
86
|
+
PJM: pjm_take_handoff / pjm_create_plan({handoffId}) 接单
|
|
87
|
+
│ WBS → fangForker.spawn 派生分身 → pjm_report 节点自反馈
|
|
88
|
+
│ 每步记录 evidence / actualMinutes
|
|
89
|
+
▼
|
|
90
|
+
PJM: 全步 done → pjm_submit_feedback(交付/偏差/遗留/测试)
|
|
91
|
+
▼
|
|
92
|
+
PM: pm_review_feedback(逐条 met + 证据 + 复盘 + 总结)
|
|
93
|
+
│ verdict 必须与验收一致:有未达标 ⇒ changes-requested
|
|
94
|
+
▼
|
|
95
|
+
accepted ⇒ 计划标记 pmReview.accepted;changes-requested ⇒ 退回重做
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
**闭环门禁**(都写在 `src/dual/schema.ts`,有测试):
|
|
99
|
+
|
|
100
|
+
| 环节 | 门禁 |
|
|
101
|
+
|---|---|
|
|
102
|
+
| PM 交接 | 验收标准 ≥1;未决问题/冲突必须清零,否则 `needs-clarification`,PJM 不允许接单 |
|
|
103
|
+
| PJM 接单 | 只有 `ready` 能接;重复接单 `handoff-already-accepted` |
|
|
104
|
+
| 节点自反馈 | `blocked` 必须写 blocker;不等到最后才报 |
|
|
105
|
+
| 项目反馈 | 必须全部步骤 `done`;`summary` 必填;带交付/偏差/遗留/测试 |
|
|
106
|
+
| PM 复核 | 验收逐条 `met` 必须是布尔;`accepted` 不能带未达标项;`changes-requested` 不能带全达标项 |
|
|
107
|
+
| 闭环状态 | `pm_pipeline` 给出 drafted → handoff-ready/needs-clarification → planning → feedback → accepted/changes-requested |
|
|
108
|
+
|
|
109
|
+
**跨角色只传结构**:PRD/盘点/交接包/计划/反馈/复核都是显式记录(`handoff_*`、`feedback_*`、`pmreview_*`),
|
|
110
|
+
不共享隐式上下文;PM 不能替 PJM 编造执行结果,PJM 不能给自己复核。
|
|
111
|
+
|
|
112
|
+
### 4.3 工具清单与「草案 → 实际」对照
|
|
113
|
+
|
|
114
|
+
**PM(8 个)**:`pm_create_prd` · `pm_create_assessment` · `pm_write_prd` · `pm_list_prds` ·
|
|
115
|
+
`pm_handoff_to_pjm` · `pm_review_feedback` · `pm_pipeline` · `pm_next_action`
|
|
116
|
+
|
|
117
|
+
**PJM(11 个)**:`pjm_create_plan` · `pjm_take_handoff` · `pjm_dispatch_step` · `pjm_complete_step` ·
|
|
118
|
+
`pjm_project_status` · `pjm_check_step` · `pjm_report` · `pjm_submit_feedback` · `pjm_write_project` ·
|
|
119
|
+
`pjm_close_project` · `pjm_next_action`
|
|
120
|
+
|
|
121
|
+
| 常见草案名 | 房主实际 | 说明 |
|
|
122
|
+
|---|---|---|
|
|
123
|
+
| `ask_user_choice`(emit 事件后返回“请等待”) | **宿主工具 `ask_user_question`**(preset 已挂载) | 草案那样“发完就返回”不会真正阻塞,模型会继续瞎编;真实阻塞在宿主 `userQuestions` |
|
|
124
|
+
| `write_prd_doc` | `pm_write_prd` | 只写 Session workspace 下 `docs/pm`,越界拒写;PRD 结构由 `pm_create_prd` 先落库 |
|
|
125
|
+
| `spawn_dev_environment` | `pjm_take_handoff` + `pjm_dispatch_step` | 无 `umbrella.derivedManager`;派发只走 `fangForker.spawn`,无 forker 即 fail-closed |
|
|
126
|
+
| `execute_project_step` | `pjm_dispatch_step` + `pjm_complete_step` | 房主不直接跑命令;执行发生在隔离执行体,结果由 `complete_step` 记账 |
|
|
127
|
+
| `check_task_status`(`vm_job_status`) | `pjm_project_status` + `pjm_check_step` | 没有 VM 作业队列;状态来自计划、节点汇报与派发记录 |
|
|
128
|
+
|
|
129
|
+
**契约测试**(`test/agent-tools.test.mjs`,6 条)保证:
|
|
130
|
+
|
|
131
|
+
- 每个工具都有合法命名、`type:'object'` 参数、`output.schema/render` 封套;
|
|
132
|
+
- `AGENT_CATALOG` 声称的工具**确实注册了**(防止文档漂移);
|
|
133
|
+
- 任何工具的描述/参数里**不出现** `vm_create/vm_exec/vm_job_submit/vm_job_status/vm_snapshot/vm_restore/umbrella.`;
|
|
134
|
+
- 未知 id 一律返回具名 code(`prd-not-found` / `plan-not-found` / `feedback-not-found` / `handoff-not-found` / `step-not-found`);
|
|
135
|
+
- PM→PJM 闭环全部经工具跑通,且派发在无 forker 时返回 `fang-forker-unavailable`;
|
|
136
|
+
- 不存在任何工具去“假装阻塞提问”。
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
### 4.2 编排策略:下一步做什么由状态机决定(3.3.5)
|
|
141
|
+
|
|
142
|
+
模型知道工具清单,但未必知道"现在该用哪个"。所以把闭环状态机写成确定性函数
|
|
143
|
+
(`src/dual/orchestrator.ts`),并通过 `pm_next_action` / `pjm_next_action` 暴露给模型:
|
|
144
|
+
|
|
145
|
+
| 状态 | PM 推荐动作 | PJM 推荐动作 |
|
|
146
|
+
|---|---|---|
|
|
147
|
+
| 还没有 PRD | `create_prd` | — |
|
|
148
|
+
| 有 PRD 无盘点 | `create_assessment` | — |
|
|
149
|
+
| 有未决问题 / 冲突 | `clarify`(**blocking**,必须问用户) | — |
|
|
150
|
+
| 验收齐备、无未决 | `handoff` | — |
|
|
151
|
+
| 交接包未就绪 | `blocked`(原因) | `return_to_pm`(**blocking**,不得硬开工) |
|
|
152
|
+
| 交接包 ready | 等 PJM(非阻塞) | `take_handoff` |
|
|
153
|
+
| 有交接无计划 | — | `create_plan`(WBS 由模型拆) |
|
|
154
|
+
| 有 pending 步骤 | — | `dispatch`(下一个 stepId) |
|
|
155
|
+
| 有 running 步骤 | — | `report`(progress,不重复派发) |
|
|
156
|
+
| 有 failed 步骤 | — | `report`(blocked,不得写成完成) |
|
|
157
|
+
| 节点报告为 blocked | — | `report`(blocking,等决策) |
|
|
158
|
+
| 全部 done 无反馈 | — | `complete_feedback` |
|
|
159
|
+
| 有反馈未复核 | `review` | `close` / `done` |
|
|
160
|
+
| 已通过 / 已退回 | `done` | `done` |
|
|
161
|
+
|
|
162
|
+
**测试保证**:每个推荐动作都必须映射到 preset 里真实注册的工具
|
|
163
|
+
(`test/orchestrator.test.mjs` 交叉比对 `AGENT_CATALOG`),防止文档与工具漂移;
|
|
164
|
+
反向也检查工具清单没有"孤儿工具"。编排只给建议,不产生任何副作用。
|
|
165
|
+
|
|
166
|
+
### 4.1 「自动交接」到底自动了什么
|
|
167
|
+
|
|
168
|
+
| 自动(代码确定性完成) | 不自动(需要模型判断) |
|
|
169
|
+
|---|---|
|
|
170
|
+
| 门禁判定:验收标准 ≥1、未决问题/冲突清零 → `ready` | 把验收标准拆成 WBS 步骤,以及每步的 prompt/owner/场景/估时 |
|
|
171
|
+
| 接单绑定:`ready` 才能接,接单后写 `status=accepted`、`planId`、`plan.handoffId` | 判断某条验收是否真的满足(PM 复核时逐条 `met`) |
|
|
172
|
+
| 状态流转:`drafted → handoff-ready / needs-clarification → planning → feedback → accepted / changes-requested` | 写 PRD 的需求内容、六类盘点、复盘结论 |
|
|
173
|
+
| 防重:重复接单、未完成就提交反馈、结论与验收不一致,全部拒收 | 决定是否退回重做、退回理由是否成立 |
|
|
174
|
+
|
|
175
|
+
**为什么不做「自动拆 WBS」**:交接包只带验收标准与约束,**不含 `steps` 字段**;带 `handoffId` 调
|
|
176
|
+
`pjm_create_plan` 仍必须由 PJM 给步骤,否则返回 `steps-required`。这是刻意的——把验收翻译成步骤需要
|
|
177
|
+
理解语义,代码里硬编码只会产出看起来合理、实际不可执行的假 WBS(`test/pm-pjm-loop.test.mjs` 有断言)。
|
|
178
|
+
|
|
179
|
+
## 5. 如何启用
|
|
180
|
+
|
|
181
|
+
1. bundle 已声明两个 preset 行(`cordis.patch.yml`:`preset-master-pm` / `preset-master-pjm`);
|
|
182
|
+
2. 新会话创建时在 Agent preset 选择器里选「产品经理(PM)」或「项目经理(PJM)」;
|
|
183
|
+
3. 面板「房主」抽屉会多出两张智能体卡片 + 一张**智囊团 Command Center** 只读摘要
|
|
184
|
+
(PRD / 盘点 / 计划 / 已结案 / 决策中心 / 派发可用性),数据来自 `/dsh-master/api/status` 的 `dual` 段;
|
|
185
|
+
4. 没有 `agentPresets` 服务的宿主(非 DSH web bundle)会显示「本宿主不可用」,不会假装可以选择。
|
|
186
|
+
|
|
187
|
+
面板**只读**:PRD、盘点、计划、结案只能由 PM/PJM 会话里的工具创建;面板不提供写入口,也不直接调工具。
|
|
188
|
+
|
|
189
|
+
## 6. 已知限制(不隐藏)
|
|
190
|
+
|
|
191
|
+
- **未做宿主级验证**:本会话守则 §二 禁止把插件装进任何 profile,因此 preset 只做了源码/契约/工具注册测试,尚未在真实 Host 里点选验证;面板卡片的 `可选择` 也只反映 `agentPresets` 服务是否存在。
|
|
192
|
+
- **没有 VM 工具面**:当前 Host 与本仓库都没有 `vm_create/vm_exec` 那套 27 工具;PJM 的隔离派发走既有 `fangForker.spawn`。若要接 vm-sandbox,需要单独授权并新增适配层。
|
|
193
|
+
- **文档只写 workspace**:`docs/pm`、`docs/pjm` 之外拒绝写入;不提供任意路径写口。
|
|
194
|
+
- **PM 不能改代码、PJM 不能改需求**:这是刻意的边界,不是功能缺失。
|
|
195
|
+
|
|
196
|
+
## 7. 测试覆盖
|
|
197
|
+
|
|
198
|
+
`test/dual-agents.test.mjs`(10 条):
|
|
199
|
+
|
|
200
|
+
- PRD 校验;completed 无证据拒收;六类字段齐全;
|
|
201
|
+
- PRD/盘点/计划/结案的服务层门禁与冻结;
|
|
202
|
+
- 派发 fail-closed 与真实 fork 记录;
|
|
203
|
+
- 两个 preset 的 persona 与工具清单(PM 4 个、PJM 6 个,且无 shell/写文件);
|
|
204
|
+
- 端到端工具调用;文档只写 workspace。
|
|
205
|
+
|
|
206
|
+
另有 `test/kernel.test.mjs`(AGENT_CATALOG 边界)、`test/core.test.mjs`(status 分开报告 agents)、
|
|
207
|
+
`test/package-contract.test.mjs`(preset 行、exports、工具边界静态断言)。
|
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -134,5 +134,7 @@ master-cost-optimizer(行,fork/mother)= 编排器 + 服务 dshMasterCost
|
|
|
134
134
|
- A1 默认 **shadow**:计算「本可节省」但返回原文;只有 `enforce` 且紧凑结果更短才替换,且归因单独入账(不重复计消耗)。
|
|
135
135
|
- 瓮城回流(3.3.1 起):`src/cost/fang-relay.ts` 走 `@twinsearth/dsh-fang-contract` 1.1.x 的
|
|
136
136
|
`fang.relay.publish` / `fang.relay.status`;只有对端回执才算送达,不可用给具名原因。
|
|
137
|
+
- 智能体(3.3.4):`master-pm` / `master-pjm` 两个 Agent preset,共享 `dual-manager` 的结构化状态;
|
|
138
|
+
preset 决定“会话是谁”,角色矩阵决定“子插件在哪装配”,两者都不授予额外执行权限(见 `docs/AGENTS-PM-PJM.md`)。
|
|
137
139
|
- 详见 `docs/DESIGN-v3.3.0-cost-optimization.md`(§0 总体架构、§17 实施进度)、
|
|
138
140
|
`docs/EXPERIMENT-M5-real-ab.md`(真实 A/B)、`docs/EXPERIMENT-M5-fang-relay.md`(回流实测)。
|
package/docs/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,77 @@
|
|
|
1
|
+
## 3.3.5(2026-11-14 已发布)
|
|
2
|
+
|
|
3
|
+
> 三方发布:annotated tag `v3.3.5` + GitHub Release + npm `@twinsearth/dsh-master@3.3.5`。
|
|
4
|
+
> 接口包无变化(仍 `@twinsearth/dsh-fang-contract@1.1.1`)。
|
|
5
|
+
|
|
6
|
+
**主题:宿主侧引擎原语 + PM/PJM 闭环 + 面板/通道/流式管线。**
|
|
7
|
+
|
|
8
|
+
- **闭环第一步 · PM 交接**:`pm_handoff_to_pjm` 生成交接包;**验收标准为空、或仍有未决问题/冲突时不给 `ready`**(`needs-clarification` + 具名原因),PJM 不允许接单。
|
|
9
|
+
- **闭环第二步 · PJM 接单与自反馈**:`pjm_take_handoff` / `pjm_create_plan({handoffId})` 接单(重复接单被拒);`pjm_report` 支持执行中随时汇报(`blocked` 必须写 blocker);全部步骤完成后 `pjm_submit_feedback` 提交交付/偏差/遗留/测试与实测用时。
|
|
10
|
+
- **闭环第三步 · PM 复核/复盘/总结**:`pm_review_feedback` 逐条验收(`met` 必须是布尔 + 证据),写 `kept/improve/lessons` 复盘与总结;**结论必须与验收一致**(有未达标却判 accepted、或全达标却判 changes-requested,都直接拒收);`accepted` 把计划标记为 `pmReview.accepted`,`changes-requested` 保持闭环打开。
|
|
11
|
+
- **闭环可视**:`pm_pipeline` 给出 `drafted → handoff-ready / needs-clarification → planning → feedback → accepted / changes-requested`;`status().dual.loop` 与面板 Command Center 显示待复核/通过/退回计数。
|
|
12
|
+
- **文档**:复核文档 `docs/pm/REVIEW-<reviewId>.md`(复核表 + 执行回执 + 交接对照 + 复盘 + 总结)。
|
|
13
|
+
- **引擎原语**(上一提交):`src/engine/` —— 指针切换 / 健康检查 / 安全更新,真实平台行为 + 注入式执行器,导出 `./engine`。
|
|
14
|
+
- **插件安全更新流水线**(3.3.5):`src/engine/plugin-updater.ts` —— `createPluginUpdater({executor})`,
|
|
15
|
+
完整阶段机(snapshotting → updating → smoke-testing → success / failed)、**异步作业轮询**、整体与冒烟独立超时、
|
|
16
|
+
同 key 并发锁、`keepSnapshotOnSuccess`、`onProgress` 异常隔离;**回滚失败升级为 `fatal:true` + FATAL 日志**,
|
|
17
|
+
不让“更新失败且回滚失败”与普通失败同形。执行能力仍由调用方注入,不假设存在 `vm_*` 工具。
|
|
18
|
+
- **智囊团面板(3.3.5)**:新增只读端点 `GET /dsh-master/api/dual`(交接/计划/反馈/复核快照,无凭据、逐项截断);
|
|
19
|
+
面板新增「智囊团 · 闭环」五段看板(待澄清 / 可接单 / 项目进度 / 待复核 / 已复核),轮询 5s,失败显示「不可达」而不是旧数据;
|
|
20
|
+
**面板不提供聊天、不回答问题、不派发**(测试静态禁止 mock 定时器与 agent 工具调用);设计见 `docs/DESIGN-command-center.md`,
|
|
21
|
+
Tailwind/Lucide 的完整交互版放在 `docs/examples/command-center/`(types/组件/主面板/README)作为参考实现,**不随插件发布**。
|
|
22
|
+
- **智囊团 WebSocket 通道(3.3.5)**:`src/channel/` 自实现 RFC 6455 服务端子集(握手/掩码/分片/控制帧/心跳),
|
|
23
|
+
经 `webServer.registerUpgrade` 挂载 `ws://<host>/dsh-master/ws/command-center?token=…`;
|
|
24
|
+
鉴权 = 随机 token(`status().channel` 暴露给同源面板)+ **Origin 校验** + 连接数上限,fail-closed;
|
|
25
|
+
连接即推 `hello` 与 `dual` 快照,`ping/resync` 可用;**`chat`/`answer` 返回 `session-prompt-unavailable` /
|
|
26
|
+
`session-answer-unavailable`**——插件侧没有“向 preset 会话投递消息”的 Host API,不假装能聊天。
|
|
27
|
+
详见 `docs/CHANNEL-WEBSOCKET.md`(含威胁模型与缺失能力清单)。
|
|
28
|
+
- **中断机制(3.3.5)**:`src/channel/abort-registry.ts` 取消原语(begin/abort/end、同 id 覆盖旧流、
|
|
29
|
+
断连与卸载清理、幂等)+ 通道 `interrupt` 消息与 `interrupted` 确认;`stream_end` 支持 `interrupted`;
|
|
30
|
+
状态机收到中断后清光标、**保留半截文本**(`savedContent` 优先),`isGenerating(state)` 驱动“发送/停止”切换。
|
|
31
|
+
没有在跑的流时如实返回 `no-active-stream`。演示服务实测:3 chunk 后中断 → `interrupted(savedContent)` → 流程不再继续。
|
|
32
|
+
- **PluginUpdater 生产级增强(3.3.5)**:`dispose()` 生命周期中止(宿主卸载不再留僵尸轮询)、
|
|
33
|
+
外部 `signal` + 整体超时 + dispose 三者合并、**卡死的 `jobStatus` 也能被信号打断**(Promise.race)、
|
|
34
|
+
中止后回滚(快照前中止则不动环境)、冒烟期中止不会被当成功、`minProgressIntervalMs` 进度节流
|
|
35
|
+
(阶段切换与终态必发)、`onFatal()` 致命告警钩子、`shellArgv()`/`buildSmokeScript()` 命令构造
|
|
36
|
+
(脚本始终单 argv,不手写转义;端口不硬编码;重试次数与间隔 clamp)。
|
|
37
|
+
- **可中断 LLM 流(3.3.5)**:`src/engine/llm-stream.ts` —— `createLlmStreamer({streamFn})` 封装宿主真实的
|
|
38
|
+
`ctx.llm.stream(options)`(`signal` 原生支持取消,`finish` 可能是 `aborted`):
|
|
39
|
+
同 key 新流先中止旧流、中止保留半截文本(`savedContent`)、错误三分(`aborted` / `llm-error` / `stream-failed`)、
|
|
40
|
+
`dispose()` 中止全部、`finally` 关闭上游。文档明确标注「停止扣费」无法从插件侧验证。
|
|
41
|
+
- 测试:新增 `test/pm-pjm-loop.test.mjs`(9 条)与 `test/engine.test.mjs`(8 条);全量 **479 条**。
|
|
42
|
+
|
|
43
|
+
- **新增 `src/engine/`**(导出 `./engine`):
|
|
44
|
+
- `switchPointer` / `readPointer` / `switchWithRollback`:目录指针原子切换;
|
|
45
|
+
做法是「临时链接 + rename」;**Windows 上 rename 无法覆盖已存在 junction(EPERM)**,
|
|
46
|
+
退化为「先移开再放」,结果里明确 `atomic:false / replaced:'move-aside'`,不假装原子;
|
|
47
|
+
- `probeHealth`:有总超时与最大次数的健康检查,只有期望状态码才算健康,连不上不算健康;
|
|
48
|
+
- `runSafeUpdate`:**snapshot → update → smoke → 失败 restore(+restart)**;
|
|
49
|
+
执行能力由调用方注入 `executor`,房主不自带 VM,缺执行器直接返回 `executor-required`;
|
|
50
|
+
- `switchAndVerify`:切指针后做健康检查,失败切回**切换前**的旧目标。
|
|
51
|
+
- **与草案的差异**:草案的 `AtomicSwitcher` / `EssenceUpdater` / `PluginSafeUpdater` 依赖
|
|
52
|
+
`VmManager` 与 `vm_create/vm_exec/vm_snapshot`,房主与当前 Host 都没有该工具面;
|
|
53
|
+
本版只实现可验证的宿主原语,VM 对接留作适配层(不在本版)。
|
|
54
|
+
- 测试:新增 `test/engine.test.mjs`(8 条,真实临时目录 + 真实本地 HTTP + 注入执行器),
|
|
55
|
+
覆盖:两次切换、拒绝覆盖真实目录、目标缺失、自引用、健康成功/503/不可达、快照失败阻断、
|
|
56
|
+
冒烟失败回滚并重启、健康失败回滚到旧槽。
|
|
57
|
+
- 文档:`docs/ENGINE-PRIMITIVES.md`。
|
|
58
|
+
|
|
59
|
+
## 3.3.4(2026-11-14 已发布)
|
|
60
|
+
|
|
61
|
+
> 三方发布:annotated tag `v3.3.4` + GitHub Release + npm `@twinsearth/dsh-master@3.3.4`。
|
|
62
|
+
> 接口包无变化(仍 `@twinsearth/dsh-fang-contract@1.1.1`)。
|
|
63
|
+
|
|
64
|
+
**主题:房主新增两个可选择的智能体 —— 产品经理(PM)与项目经理(PJM)。**
|
|
65
|
+
|
|
66
|
+
- **两个 Agent preset**:`master-pm`、`master-pjm`(`cordis.patch.yml` 的 `preset-master-pm` / `preset-master-pjm` 行),可在新会话创建时选择;已存在的会话不受影响。
|
|
67
|
+
- **角色 Prompt 落进代码**:`src/agents/prompts.ts` 给出 PM 的 WHY/WHAT/六类盘点契约与 PJM 的 HOW/WHO/WHERE/WHEN/七类项目管理契约,均写死边界(PM 不执行、PJM 无 forker 即 fail-closed)。
|
|
68
|
+
- **结构化契约与门禁**:`src/dual/schema.ts` —— PRD 必填 title/goal;**completed 项没有 evidence 直接拒收**(不允许自称 100%);plan 步骤必须有 title/prompt,估时缺省显式给 30 分钟;**全部步骤 done 才允许结案**,结案必须给 summary,结案后计划冻结。
|
|
69
|
+
- **`dual-manager` 升级为共享服务**:保留原有 `pm/prd`、`pjm/plan/dispatch` 兼容面,新增 `createAssessment`(六类盘点)、`writePrd`/`writeAssessment`/`writeProject`/`writeClosure`(只写 Session workspace 下的 `docs/pm`、`docs/pjm`)、`progress`(进度按步骤实测)、`close`(结案)。
|
|
70
|
+
- **两个智能体插件**:`src/agents/pm-agent.ts`(4 个工具)、`src/agents/pjm-agent.ts`(6 个工具),通过 `ctx.systemPrompt.section` 挂 persona、`ctx.tools.register` 挂 scoped tools;缺少 `systemPrompt`/`tools`/`diskMasterDual` 时返回具名错误,不静默失败。
|
|
71
|
+
- **面板与状态**:`status().agents` 单独报告两个智能体(与子插件行分开),`status().dual` 给出智囊团只读摘要(PRD/盘点/计划/已结案/协作可用性;未装配时为 `null`,不造空壳);面板新增两张智能体卡片与「智囊团 · Command Center」只读卡片,**无写入口**;`AGENT_CATALOG` 写死工具边界。
|
|
72
|
+
- **边界**:两个 preset 都不挂 shell、不授予写文件(文档只经 agent 工具写入 workspace);PJM 的隔离派发仍只走 `fangForker.spawn`,没有 `vm_create/vm_exec` 适配层。
|
|
73
|
+
- 测试:新增 `test/dual-agents.test.mjs`(10 条),并扩充 kernel/core/package-contract 断言;全量 **459 条**。
|
|
74
|
+
|
|
1
75
|
## 3.3.3(2026-11-13 已发布)
|
|
2
76
|
|
|
3
77
|
> 三方发布:annotated tag `v3.3.3` + GitHub Release + npm `@twinsearth/dsh-master@3.3.3`。
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# 智囊团 WebSocket 通道(3.3.5)
|
|
2
|
+
|
|
3
|
+
> 宿主侧实现:`src/channel/`
|
|
4
|
+
> 一句话:**管道是真的,聊天口还没开。**
|
|
5
|
+
|
|
6
|
+
## 1. 为什么是 WebSocket,以及为什么不用 `ws`
|
|
7
|
+
|
|
8
|
+
- `webServer.registerUpgrade({ path, handler(req, socket, head) })` 是 DSH 原生能力(已用 Inspect 核实),
|
|
9
|
+
所以 WebSocket 不需要额外服务、也不需要 `ws` 依赖;
|
|
10
|
+
- 帧编解码在 `src/channel/ws.ts` 里自己实现(RFC 6455 服务端子集),保持房主**零运行时依赖**;
|
|
11
|
+
- 握手 accept 值用 RFC 官方测试向量验证:`dGhlIHNhbXBsZSBub25jZQ==` → `s3pPLMBiTxaQ9kYGzzhZRbK+xOo=`。
|
|
12
|
+
|
|
13
|
+
## 2. 端点与鉴权
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
ws://<host>/dsh-master/ws/command-center?token=<channelToken>
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
| 检查 | 失败行为 |
|
|
20
|
+
|---|---|
|
|
21
|
+
| 升级握手合法(Upgrade/Connection/Key 16B/Version 13) | `400` + 具名 code |
|
|
22
|
+
| `Origin` 与宿主 origin 一致(浏览器一定带 Origin) | `403 channel-origin-rejected` |
|
|
23
|
+
| `?token=` 或 `Authorization: Bearer` 等于通道 token | `401 channel-unauthorized` |
|
|
24
|
+
| 连接数 ≤ 8 | `403 channel-full` |
|
|
25
|
+
| 没有配置 token(fail-closed) | 通道不挂载,状态里报 `channel-token-missing` |
|
|
26
|
+
|
|
27
|
+
token 每次插件加载随机生成(`randomBytes(24)`),通过只读状态接口 `status().channel` 暴露给同源面板。
|
|
28
|
+
|
|
29
|
+
**威胁模型(如实说)**:本地进程读 status 也能拿到 token,因此 token 是纵深防御,真正的门是 **Origin 校验**;
|
|
30
|
+
对「本机任意进程」这一层没有硬防护——那需要宿主级的 peer/principal 鉴权,当前插件拿不到。
|
|
31
|
+
|
|
32
|
+
## 3. 协议
|
|
33
|
+
|
|
34
|
+
客户端 → 服务端:
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{ "type": "ping" }
|
|
38
|
+
{ "type": "resync" }
|
|
39
|
+
{ "type": "chat", "content": "…", "agent": "pm" }
|
|
40
|
+
{ "type": "answer", "callId": "call_1", "value": "A" }
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
服务端 → 客户端:
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{ "type": "hello", "protocol": 1, "capabilities": { "dualSnapshot": true, "chat": false, "answer": false },
|
|
47
|
+
"limitations": { "chat": "…", "answer": "…" }, "tokenRequired": true, "serverTime": 0 }
|
|
48
|
+
{ "type": "dual", "snapshot": { "handoffs": [], "plans": [], "feedbacks": [], "reviews": [], "loop": {} } }
|
|
49
|
+
{ "type": "pong", "at": 0 }
|
|
50
|
+
{ "type": "error", "code": "…", "message": "…", "detail": {} }
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
校验全部具名返回:`invalid-json` / `unknown-type` / `content-required` / `content-too-long` /
|
|
54
|
+
`agent-invalid` / `answer-incomplete` / `message-too-large`(64 KiB)。
|
|
55
|
+
|
|
56
|
+
## 4. `chat` 为什么返回错误(不假装)
|
|
57
|
+
|
|
58
|
+
`chat` 的语义是「把用户输入提交给 PM/PJM 会话」。当前插件**没有**这个能力:
|
|
59
|
+
|
|
60
|
+
| 需要的 Host API | 现状 |
|
|
61
|
+
|---|---|
|
|
62
|
+
| 以 `master-pm`/`master-pjm` preset 新建或选中一个会话 | 未核实到插件可用的“按 preset 建会话并拿到 Agent”入口 |
|
|
63
|
+
| 向该会话提交一条 user message 并触发一轮 turn | `sessions` 只有 store(create/prepare/enter/announce/get/list/fork),**没有投递消息** |
|
|
64
|
+
| 订阅该会话的流式事件 | 会话事件可通过 store 观察,但没有面向“外部通道”的订阅口 |
|
|
65
|
+
| 回答该会话里的 user question | `userQuestions.answer(agent, callId, answer)` 需要 **Agent** 句柄,插件侧拿不到 |
|
|
66
|
+
|
|
67
|
+
所以通道对 `chat` 返回:
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{ "type": "error", "code": "session-prompt-unavailable",
|
|
71
|
+
"message": "this host has no API to submit a user message into a preset agent session; use a PM/PJM session to chat",
|
|
72
|
+
"detail": { "agent": "pm", "protocol": 1 } }
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`answer` 同理返回 `session-answer-unavailable`。**面板不会因为 WS 通了就能聊天**——这一点写在 `hello.capabilities` 里,
|
|
76
|
+
客户端可以据此禁用输入框(当前 `client.js` 只看板,不提供聊天入口)。
|
|
77
|
+
|
|
78
|
+
## 5. 流式渲染状态机(`stream-state.ts`)
|
|
79
|
+
|
|
80
|
+
UI 侧的拼接规则抽成了纯模块(`src/channel/stream-state.ts`,包导出 `./channel/stream-state`):
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
const chat = createChatStream()
|
|
84
|
+
chat.apply(serverMessage) // 返回新 state,React 直接渲染
|
|
85
|
+
chat.getState() // { messages, capabilities, snapshot, orphanChunks, lastError }
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
| 输入 | 行为 |
|
|
89
|
+
|---|---|
|
|
90
|
+
| `stream_start` | 新建空气泡并标记 `streaming`;**重复 start 幂等**(不新建第二条,保留已收文本) |
|
|
91
|
+
| `stream_chunk` | 追加到对应气泡;**孤儿帧只计数**(`orphanChunks++`),绝不凭空造消息 |
|
|
92
|
+
| `stream_end` | 清除光标;`fullContent` 比已收文本长时以其为准 |
|
|
93
|
+
| `card_choice` / `card_prd` / `card_status` | 收尾正在流式的文本,插入卡片;`card_status` 同 id 原地更新(进度条不重复插卡) |
|
|
94
|
+
| `answerChoice(id, value, {echo})` | **本地乐观**回答选择题:卡片置 `answered` 并写 `answer`,追加 `local:true` 用户气泡;重复回答被拒(`choice-already-answered`) |
|
|
95
|
+
| `error` | 变成 `system/error` 消息并记入 `lastError`(UI 可据此禁用输入) |
|
|
96
|
+
| `hello` | 记录 `capabilities`,`chat:false` 时 UI 禁用输入 |
|
|
97
|
+
| `dual` | 只更新 `snapshot`,**不产生聊天消息** |
|
|
98
|
+
| 未知类型 | 计数忽略,不抛 |
|
|
99
|
+
|
|
100
|
+
**渲染管线(极简 + 引用稳定)**:
|
|
101
|
+
|
|
102
|
+
| API | 作用 |
|
|
103
|
+
|---|---|
|
|
104
|
+
| `apply(message)` | 单条应用;每次通知订阅者 |
|
|
105
|
+
| `applyBatch(messages)` | 批量应用,**只通知一次**(UI 按帧 flush) |
|
|
106
|
+
| `subscribe(listener)` | 外部 store 订阅,配合 `useSyncExternalStore` |
|
|
107
|
+
| `getState().activeStreamId` / `activeMessage(state)` | 当前正在流式的那一条,避免全量比较 |
|
|
108
|
+
| `isGenerating(state)` | 发送键 ↔ 停止键切换 |
|
|
109
|
+
|
|
110
|
+
性能关键:状态机**只替换变化的那条消息对象**,历史消息引用保持不变,`React.memo` 才能生效。
|
|
111
|
+
测试直接断言「50 个 chunk 后历史消息 `===` 原对象」(`test/stream-state.test.mjs`)。
|
|
112
|
+
|
|
113
|
+
有界:200 条消息、单条 20 万字符、选项 ≤20、快照 ≤256 KiB(超限置空)。
|
|
114
|
+
|
|
115
|
+
**演示**:`docs/examples/command-center/demo-server.mjs` 复用房主的 RFC 6455 编解码起一个本地流式服务,
|
|
116
|
+
实际观测到的帧序列为 `hello → dual → stream_start → stream_chunk → stream_end → card_choice`。
|
|
117
|
+
|
|
118
|
+
## 6. 中断(Interrupt)
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
客户端 → 服务端:{ "type": "interrupt", "messageId": "msg-1" } // messageId 可省略 = 停本连接全部
|
|
122
|
+
服务端 → 客户端:{ "type": "interrupted", "messageId": "msg-1", "savedContent": "半截文本", "aborted": true }
|
|
123
|
+
或:{ "type": "error", "code": "no-active-stream" } // 没有流在跑,如实说
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
语义(`src/channel/abort-registry.ts`,全部有测试):
|
|
127
|
+
|
|
128
|
+
| 场景 | 行为 |
|
|
129
|
+
|---|---|
|
|
130
|
+
| 生产者登记流 | `channel.openStream(messageId)` → `{ signal, end() }`;生产者必须在循环里检查 `signal.aborted` |
|
|
131
|
+
| 用户中断 | 按 messageId 中止;没有正在跑的流 → `no-active-stream`(不假装中止成功) |
|
|
132
|
+
| 同一 messageId 重复登记 | **旧流立即中止**(reason=`superseded`),避免两路流写同一条消息 |
|
|
133
|
+
| 连接关闭 / 拔网线 | 中止该连接拥有的全部流(`connection-closed`),不留僵尸生成 |
|
|
134
|
+
| 心跳超时(2×heartbeat 无任何数据) | 判定僵尸连接 → 先中止其流 → `destroy()` socket;**不等对端回 close 帧** |
|
|
135
|
+
| 插件卸载 | `dispose()` 中止所有流(`server-shutdown`)并清理心跳定时器 |
|
|
136
|
+
| 重复 abort | 第二次返回 false(不会重复计数/重复扣费) |
|
|
137
|
+
| 收尾 | `end()` 幂等清理登记;中止后登记保留到 `end()`,便于观察“已中止未收尾” |
|
|
138
|
+
|
|
139
|
+
前端(参考 UI):`isGenerating(state)` 为真时发送键变红色停止键,点击发 `interrupt`;
|
|
140
|
+
收到 `interrupted` 或 `stream_end{interrupted}` 后光标消失、输入框恢复、**半截文本保留**(`savedContent` 更长时以其为准)。
|
|
141
|
+
|
|
142
|
+
实测(`demo-server.mjs` + Node 内置 WebSocket 客户端):3 个 chunk 后发送 interrupt,得到
|
|
143
|
+
`interrupted(savedContent="收到需求。我")` → `stream_end{interrupted:true}`,且**后续流程不再继续**。
|
|
144
|
+
|
|
145
|
+
## 7. 现在能做什么
|
|
146
|
+
|
|
147
|
+
| 能力 | 状态 |
|
|
148
|
+
|---|---|
|
|
149
|
+
| 同源面板建立 WS 连接、心跳、断线重连(客户端侧) | 通道已就绪;面板目前仍用轮询,可后续切换 |
|
|
150
|
+
| 推送交接/计划/反馈/复核快照(`dual`) | ✅ 连接时推一次,`resync` 可再拉 |
|
|
151
|
+
| `ping`/`pong` 保活与超时断连 | ✅ 2× heartbeat 无数据即断开 |
|
|
152
|
+
| 协议违规断连(未加掩码、超大帧、非法 JSON 不断连只回错误) | ✅ |
|
|
153
|
+
| 向 PM/PJM 会话聊天、回答问题 | ❌ 缺 Host API,见 §4 |
|
|
154
|
+
|
|
155
|
+
## 6. 测试
|
|
156
|
+
|
|
157
|
+
`test/channel.test.mjs`(10 条,全部离线、真实帧字节):
|
|
158
|
+
|
|
159
|
+
- RFC 6455 accept 向量与非法握手;
|
|
160
|
+
- 掩码帧解析、未加掩码拒绝、分片续帧、16 位长度;
|
|
161
|
+
- ping/pong/close 控制帧;
|
|
162
|
+
- 协议校验的每条具名错误;
|
|
163
|
+
- token 解析(query 与 Bearer);
|
|
164
|
+
- 无 token / 无 webServer 时拒绝挂载;
|
|
165
|
+
- 错误 token、错误 Origin、正常连接(hello + dual);
|
|
166
|
+
- `chat`/`answer` 返回具名缺口、`ping`/`resync` 正常、`dispose` 断开;
|
|
167
|
+
- 协议违规关闭连接。
|
|
168
|
+
|
|
169
|
+
另有 `test/core.test.mjs` 断言:`status().channel` 存在、token 长度 ≥ 32、升级路由确实注册到 `webServer.registerUpgrade`。
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# 智囊团面板(Command Center)· 规划与设计
|
|
2
|
+
|
|
3
|
+
> 版本:3.3.5-dev · 范围:房主 `E:\GPT\dsh-master` 客户端面板
|
|
4
|
+
> 状态:设计定稿 → 代码收尾 → 测试 → 完善
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 1. 目标
|
|
9
|
+
|
|
10
|
+
在房主侧边栏「房主」抽屉里,给 PM/PJM 双智能体一个**可读的闭环视图**:
|
|
11
|
+
能看出「谁在等谁、卡在哪、做到哪一步」,而不是只能看到两个计数。
|
|
12
|
+
|
|
13
|
+
## 2. 非目标(明确不做,避免做出假象)
|
|
14
|
+
|
|
15
|
+
| 不做 | 原因 |
|
|
16
|
+
|---|---|
|
|
17
|
+
| 面板内直接和 PM/PJM 聊天 | 没有“面板↔指定 Agent 会话”的 Host 通道;PM/PJM 是 Agent preset,聊天发生在会话里 |
|
|
18
|
+
| 面板内回答问题 / 替用户做选择 | 决策必须发生在 PM 会话(并带权限与审计);面板点击无法落账 |
|
|
19
|
+
| 面板内派发 / 结案 / 生成 PRD | 会绕过 `fangForker` fail-closed、复核门禁与 Mandate 边界 |
|
|
20
|
+
| 用 Tailwind / Lucide / TSX 上线 | DSH 客户端插件是纯 JS + `h()`,无构建链路;参考 TSX 只放 `docs/examples` |
|
|
21
|
+
| 用 setTimeout 模拟对话 | 假聊天会让人误以为智能体在工作(已在测试里静态禁止) |
|
|
22
|
+
|
|
23
|
+
## 3. 真实约束(来自仓库与宿主)
|
|
24
|
+
|
|
25
|
+
1. 客户端插件:`client.js`,React 由宿主注入,`h()` 渲染,内联 style;
|
|
26
|
+
2. 面板已有的数据源:`GET /dsh-master/api/status`(`agents` / `dual` 摘要);
|
|
27
|
+
3. **摘要不够**:要看交接/计划/反馈/复核的明细,需要新的只读端点;
|
|
28
|
+
4. 所有 Agent 状态都在 `dual-manager`(内存 Service)里,没有持久化;
|
|
29
|
+
5. 守则 §二:不安装、不发布到 profile,因此面板无法在真实宿主里点选验证。
|
|
30
|
+
|
|
31
|
+
## 4. 数据契约
|
|
32
|
+
|
|
33
|
+
### 4.1 新增只读端点
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
GET /dsh-master/api/dual
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
响应(有 dual 服务):
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"ok": true,
|
|
44
|
+
"available": true,
|
|
45
|
+
"updatedAt": 1794580000000,
|
|
46
|
+
"agents": { "pm": "master-pm", "pjm": "master-pjm" },
|
|
47
|
+
"loop": { "pendingHandoffs": 1, "acceptedHandoffs": 0, "awaitingReview": 0, "accepted": 0, "changesRequested": 0 },
|
|
48
|
+
"handoffs": [{ "id": "handoff_1", "prdId": "prd_1", "title": "…", "status": "ready|needs-clarification|accepted",
|
|
49
|
+
"reason": null, "openQuestions": ["…"], "acceptance": ["…"], "planId": null, "preparedAt": 0 }],
|
|
50
|
+
"plans": [{ "id": "plan_1", "title": "…", "status": "planned|running|done|closed",
|
|
51
|
+
"handoffId": "handoff_1", "progress": { "total": 3, "done": 1, "percent": 33, "canClose": false },
|
|
52
|
+
"lastReport": { "status": "progress|blocked|done", "summary": "…", "blocker": null, "at": 0 },
|
|
53
|
+
"pmReview": { "verdict": "accepted|changes-requested", "accepted": true },
|
|
54
|
+
"feedbackId": "feedback_1" }],
|
|
55
|
+
"feedbacks": [{ "id": "feedback_1", "planId": "plan_1", "summary": "…", "metrics": { }, "openIssues": [] }],
|
|
56
|
+
"reviews": [{ "id": "pmreview_1", "feedbackId": "feedback_1", "planId": "plan_1",
|
|
57
|
+
"verdict": "accepted|changes-requested", "failed": ["未达标验收项"] }]
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
没有 dual 服务时:
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{ "ok": true, "available": false, "reason": "dual-service-unavailable",
|
|
65
|
+
"handoffs": [], "plans": [], "feedbacks": [], "reviews": [], "loop": null }
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
**约束**:只读、无写口;不含 token/secret/authorization;列表逐项截断(问题/验收各 ≤10,摘要 ≤500 字符)。
|
|
69
|
+
|
|
70
|
+
### 4.2 与已有端点的分工
|
|
71
|
+
|
|
72
|
+
| 端点 | 用途 | 频率 |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| `/dsh-master/api/status` | 角色、子插件、agents、dual 摘要、成本 | 5s |
|
|
75
|
+
| `/dsh-master/api/dual` | 交接/计划/反馈/复核明细 | 5s |
|
|
76
|
+
|
|
77
|
+
## 5. 界面结构
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
房主抽屉
|
|
81
|
+
├─ 主 Agent 控制面(角色/版本/运行时长)
|
|
82
|
+
├─ 子插件卡片 ×5(Router/Worker/Loop/cost/dual)
|
|
83
|
+
├─ 智能体卡片 ×2(master-pm / master-pjm,可选状态与工具边界)
|
|
84
|
+
├─ 智囊团 · Command Center(摘要:PRD/盘点/交接/复核、计划/结案/反馈、闭环计数、协作可用性)
|
|
85
|
+
├─ 智囊团 · 闭环看板(本设计新增,五段)
|
|
86
|
+
│ ① 需要 PM 澄清(needs-clarification + 未决问题)
|
|
87
|
+
│ ② 交接已就绪(等 PJM 接单,显示验收条数)
|
|
88
|
+
│ ③ 项目进度(done/total + 百分比 + 最近节点 + blocked 原因)
|
|
89
|
+
│ ④ 等 PM 复核(反馈摘要 + 遗留问题数)
|
|
90
|
+
│ ⑤ 已复核(通过/退回 + 未达标项)
|
|
91
|
+
├─ 成本(只读)
|
|
92
|
+
├─ 与瓮城协作
|
|
93
|
+
└─ 实时事件流(SSE)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## 6. 状态机与显示规则
|
|
97
|
+
|
|
98
|
+
| 状态 | 显示 |
|
|
99
|
+
|---|---|
|
|
100
|
+
| 读取中 | “正在读取交接/计划/反馈/复核…” |
|
|
101
|
+
| 不可达 | 红字错误,不显示旧数据为“正常” |
|
|
102
|
+
| `available:false` | “dual-manager 未启用…” + 指明工具会返回 `dual-service-unavailable` |
|
|
103
|
+
| 空 | 各段不渲染,只留计数行,不编造条目 |
|
|
104
|
+
| 有数据 | 每段最多 3 条,按状态过滤;其余只给计数 |
|
|
105
|
+
|
|
106
|
+
统一规则:
|
|
107
|
+
- **只读**:所有条目无 onClick、无输入框、无“发送”;
|
|
108
|
+
- **不许假装**:①段明确写“不能在面板里回答”;
|
|
109
|
+
- 长文本截断(标题不截、摘要 60 字、未决问题 10 条)。
|
|
110
|
+
|
|
111
|
+
## 7. 失败与降级
|
|
112
|
+
|
|
113
|
+
| 故障 | 行为 |
|
|
114
|
+
|---|---|
|
|
115
|
+
| `/api/dual` 404 / 非 JSON | 显示“智囊团数据不可达”,保留其它卡片 |
|
|
116
|
+
| dual 抛异常 | 服务端 `safe()` 兜底返回空数组,面板显示空而不崩 |
|
|
117
|
+
| 轮询重叠 | 单次 `load` 无状态累积,天然幂等 |
|
|
118
|
+
| 组件卸载 | `alive` 标记 + 清定时器,避免 setState 泄漏 |
|
|
119
|
+
|
|
120
|
+
## 8. 安全
|
|
121
|
+
|
|
122
|
+
- 端点只读(GET),不接入 `bridgeToken`,不暴露 dispatch 配置;
|
|
123
|
+
- 快照只含 id/标题/状态/计数/摘要;
|
|
124
|
+
- 测试断言:快照文本不得出现 `token|secret|authorization|apikey`;
|
|
125
|
+
- 面板不调用任何 `pm_*` / `pjm_*` 工具(静态断言),也不出现模拟对话定时器。
|
|
126
|
+
|
|
127
|
+
## 9. 测试计划
|
|
128
|
+
|
|
129
|
+
| 层 | 用例 |
|
|
130
|
+
|---|---|
|
|
131
|
+
| 服务端 | `/api/dual` 已注册;无 dual 时 `available:false`;有 dual 时 handoffs/plans 形状与计数;无凭据字段 |
|
|
132
|
+
| 客户端契约 | `client.js` 引用 `/dsh-master/api/dual`、有 `dual-board` 标记、含“不能在面板里回答”、无 mock 定时器、不调 agent 工具 |
|
|
133
|
+
| 回归 | 既有 `command-center` 断言仍通过;全量 `check/build/test:clean` |
|
|
134
|
+
|
|
135
|
+
## 10. 落地步骤
|
|
136
|
+
|
|
137
|
+
1. **P1 端点**:`src/index.ts` 增加 `DUAL_ROUTE` + `dualSnapshot()`(只读、截断、无凭据);
|
|
138
|
+
2. **P2 面板**:`client.js` 增加 `DualBoard`(五段、状态机、轮询);
|
|
139
|
+
3. **P3 测试**:`core.test.mjs` 路由形状 + 凭据断言;`package-contract.test.mjs` 面板契约;
|
|
140
|
+
4. **P4 参考实现**:`docs/examples/command-center/`(Tailwind/Lucide 版:`types.ts` + `MessageComponents.tsx` + `AgentCommandCenter.tsx` + `README.md`,标明不可直接上线 + 接真后端要补的 4 项 Host 能力);
|
|
141
|
+
5. **P5 验证**:`npm run check` / `build` / `test:clean`,确认全绿后提交。
|
|
142
|
+
|
|
143
|
+
## 11. 未决问题(需要你定)
|
|
144
|
+
|
|
145
|
+
1. **是否要“从面板新建 PM/PJM 会话”**?这需要新增 Host 能力(创建 preset 会话 + 提交消息 + 回答提问),范围明显更大;
|
|
146
|
+
2. **是否要持久化闭环数据**?当前全在内存,面板重启即空;持久化要设计落盘格式与清理策略;
|
|
147
|
+
3. **是否要面板只显示“我的”数据**?现在显示全部手头的交接/计划,单用户场景够用,多用户需要过滤维度。
|