@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.
Files changed (57) hide show
  1. package/README.md +17 -0
  2. package/client.js +126 -1
  3. package/cordis.patch.yml +51 -0
  4. package/dist-host/agents/pjm-agent.js +62 -0
  5. package/dist-host/agents/pm-agent.js +57 -0
  6. package/dist-host/agents/prompts.js +71 -0
  7. package/dist-host/agents/toolkit.js +64 -0
  8. package/dist-host/channel/abort-registry.js +93 -0
  9. package/dist-host/channel/protocol.js +114 -0
  10. package/dist-host/channel/server.js +349 -0
  11. package/dist-host/channel/stream-state.js +343 -0
  12. package/dist-host/channel/ws.js +199 -0
  13. package/dist-host/dual/documents.js +143 -0
  14. package/dist-host/dual/orchestrator.js +214 -0
  15. package/dist-host/dual/schema.js +367 -0
  16. package/dist-host/engine/atomic-switch.js +159 -0
  17. package/dist-host/engine/health-probe.js +86 -0
  18. package/dist-host/engine/index.js +18 -0
  19. package/dist-host/engine/llm-stream.js +215 -0
  20. package/dist-host/engine/plugin-updater.js +392 -0
  21. package/dist-host/engine/safe-update.js +150 -0
  22. package/dist-host/index.js +154 -3
  23. package/dist-host/kernel.js +27 -1
  24. package/dist-host/plugins/cost-optimizer.js +3 -3
  25. package/dist-host/plugins/dual-manager.js +374 -77
  26. package/docs/AGENTS-PM-PJM.md +207 -0
  27. package/docs/ARCHITECTURE.md +2 -0
  28. package/docs/CHANGELOG.md +74 -0
  29. package/docs/CHANNEL-WEBSOCKET.md +169 -0
  30. package/docs/DESIGN-command-center.md +147 -0
  31. package/docs/ENGINE-PRIMITIVES.md +215 -0
  32. package/docs/GAP-ANALYSIS-fang-session.md +75 -0
  33. package/docs/REVIEW-PM-PJM-3.3.4.md +179 -0
  34. package/docs/examples/command-center/README.md +167 -0
  35. package/package.json +5 -1
  36. package/src/agents/pjm-agent.ts +62 -0
  37. package/src/agents/pm-agent.ts +58 -0
  38. package/src/agents/prompts.ts +74 -0
  39. package/src/agents/toolkit.ts +64 -0
  40. package/src/channel/abort-registry.ts +85 -0
  41. package/src/channel/protocol.ts +110 -0
  42. package/src/channel/server.ts +289 -0
  43. package/src/channel/stream-state.ts +349 -0
  44. package/src/channel/ws.ts +182 -0
  45. package/src/dual/documents.ts +142 -0
  46. package/src/dual/orchestrator.ts +223 -0
  47. package/src/dual/schema.ts +355 -0
  48. package/src/engine/atomic-switch.ts +146 -0
  49. package/src/engine/health-probe.ts +84 -0
  50. package/src/engine/index.ts +20 -0
  51. package/src/engine/llm-stream.ts +189 -0
  52. package/src/engine/plugin-updater.ts +359 -0
  53. package/src/engine/safe-update.ts +148 -0
  54. package/src/index.ts +143 -4
  55. package/src/kernel.ts +28 -1
  56. package/src/plugins/cost-optimizer.ts +3 -3
  57. package/src/plugins/dual-manager.ts +337 -76
@@ -0,0 +1,143 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (c) 2026 dsh-Master contributors — see LICENSE-CORE.
3
+ // @ts-nocheck
4
+ import { ASSESSMENT_CATEGORIES, planProgress } from "./schema.js";
5
+ const lines = (items, render) => items.length ? items.map(render).join('\n') : '- (无)';
6
+ const bullets = (items) => lines(items, (item) => `- ${item}`);
7
+ const cell = (value) => String(value ?? '—').replaceAll('|', '\\|').replaceAll('\n', ' ');
8
+ const CATEGORY_TITLES = {
9
+ completed: '1. 已完成(进度与证据)',
10
+ unfinished: '2. 未完成(原因与后续)',
11
+ blocked: '3. 无法实现 / 受阻(卡点)',
12
+ planned: '4. 已计划待开发',
13
+ conflicts: '5. 相互矛盾 / 冲突(待重新判断)',
14
+ ambiguities: '6. 理解歧义(待确认)',
15
+ };
16
+ export function renderPrdDocument(prd, assessment = null) {
17
+ return [
18
+ `# PRD · ${prd.title}`, '',
19
+ `- 文档 ID:\`${prd.id}\``,
20
+ `- 状态:${prd.status}`,
21
+ `- 优先级:${prd.priority}`,
22
+ `- 请求方:${prd.requester}`, '',
23
+ '## WHY · 解决什么问题、达成什么目标', '',
24
+ prd.why || prd.goal, '',
25
+ `**目标**:${prd.goal}`, '',
26
+ '## WHAT · 功能、模块、架构与结果', '',
27
+ bullets(prd.what), '',
28
+ '### 架构', '', prd.architecture || '(待补充)', '',
29
+ '### 模块', '',
30
+ lines(prd.modules, (item) => `- **${item.name}**(${item.status}):${item.result || item.architecture || '待补充'}`), '',
31
+ '### 预期结果', '', bullets(prd.outcomes), '',
32
+ '## 约束与非目标', '',
33
+ '### 约束', '', bullets(prd.constraints), '',
34
+ '### 非目标', '', bullets(prd.nonGoals), '',
35
+ '## 验收标准', '', bullets(prd.acceptance), '',
36
+ '## 假设与待澄清问题', '',
37
+ '### 假设', '', bullets(prd.assumptions), '',
38
+ '### 待澄清', '', bullets(prd.openQuestions), '',
39
+ assessment ? `\n---\n\n${renderAssessmentDocument(assessment)}` : '',
40
+ ].join('\n').trim() + '\n';
41
+ }
42
+ export function renderAssessmentDocument(assessment) {
43
+ const out = [`# 产品现状盘点 · ${assessment.title}`, '', `- 盘点 ID:\`${assessment.id}\``, `- 关联 PRD:${assessment.prdId ?? '未关联'}`, ''];
44
+ if (assessment.why)
45
+ out.push('## WHY', '', assessment.why, '');
46
+ if (assessment.what.length)
47
+ out.push('## WHAT', '', bullets(assessment.what), '');
48
+ for (const category of ASSESSMENT_CATEGORIES) {
49
+ out.push(`## ${CATEGORY_TITLES[category]}`, '');
50
+ const items = assessment[category];
51
+ out.push(lines(items, (item) => {
52
+ if (category === 'completed')
53
+ return `- **${item.title}**(${item.progress}%)${item.detail ? `:${item.detail}` : ''}\n - 证据:${item.evidence.join(';')}`;
54
+ if (category === 'unfinished')
55
+ return `- **${item.title}**:${item.reason}\n - 后续:${item.nextStep || '待计划'}`;
56
+ if (category === 'blocked')
57
+ return `- **${item.title}**:${item.reason}\n - 卡点:${item.blocker}\n - 下一步:${item.nextStep || '待决策'}`;
58
+ if (category === 'planned')
59
+ return `- **${item.title}**(${item.priority}):${item.plan || '待拆解'}${item.estimateMinutes ? `\n - 估时:${item.estimateMinutes} 分钟` : ''}`;
60
+ if (category === 'conflicts')
61
+ return `- **${item.title}**:${item.question}\n - 选项:${item.options.map((option) => `${option.value}=${option.label}`).join(';') || '待补充'}`;
62
+ return `- **${item.title}**:${item.question}\n - 当前理解:${item.currentInterpretation || '未形成结论'}`;
63
+ }), '');
64
+ }
65
+ return out.join('\n').trim() + '\n';
66
+ }
67
+ export function renderProjectDocument(plan) {
68
+ const progress = planProgress(plan);
69
+ return [
70
+ `# 项目计划 · ${plan.title}`, '',
71
+ `- 计划 ID:\`${plan.id}\``,
72
+ `- 关联 PRD:\`${plan.prdId}\``,
73
+ `- 状态:${plan.status}`,
74
+ `- 进度:${progress.done}/${progress.total}(${progress.percent}%)`,
75
+ `- 总估时:${progress.estimatedMinutes} 分钟(估算,不是承诺工期)`, '',
76
+ '## 1. 项目会审', '', bullets(plan.projectReview), '',
77
+ '## 2. 项目计划(HOW / WHERE)', '', plan.how || '(待补充)', '', `**场景**:${plan.where}`, '',
78
+ '### WBS', '',
79
+ '| 步骤 | 内容 | Owner | 场景 | 估时(分钟) | 依赖 | 状态 |',
80
+ '|---|---|---|---|---:|---|---|',
81
+ ...plan.steps.map((step) => `| ${cell(step.id)} | ${cell(step.title)} | ${cell(step.owner)} | ${cell(step.where)} | ${step.estimateMinutes} | ${cell(step.dependsOn.join(', '))} | ${cell(step.status)} |`), '',
82
+ '## 3. 项目需求与内容', '', bullets(plan.requirements), '',
83
+ '## 4. 项目时间', '',
84
+ `- 开始时间:${plan.schedule.startAt ?? '待定'}`,
85
+ `- 总估时:${plan.schedule.totalEstimateMinutes} 分钟`, '',
86
+ lines(plan.schedule.milestones, (item) => `- **${item.title}**:${item.estimateMinutes} 分钟 · ${item.owner} · 依赖 ${item.dependsOn.join(', ') || '无'}`), '',
87
+ '## 5. 项目人员', '',
88
+ lines(plan.team, (item) => `- **${item.role}** · ${item.owner}:${item.responsibility || '待分配'}`), '',
89
+ '## 6. 测试、部署、交付与验收', '',
90
+ '### 测试', '', bullets(plan.quality.tests), '',
91
+ '### 部署', '', bullets(plan.quality.deployment), '',
92
+ '### 验收', '', bullets(plan.quality.acceptance), '',
93
+ '## 7. 项目结案', '',
94
+ plan.closure.status === 'closed' ? renderClosureDocument(plan) : '- 尚未结案;必须全部步骤完成后才能结案。',
95
+ ].join('\n').trim() + '\n';
96
+ }
97
+ export function renderClosureDocument(plan) {
98
+ const closure = plan.closure;
99
+ return [
100
+ `### 结案状态:${closure.status}`, '',
101
+ `- 结案时间:${closure.closedAt ?? '未结案'}`,
102
+ `- 总结:${closure.summary || '未填写'}`, '',
103
+ '#### 评审报告', '', closure.reviewReport || '(待补充)', '',
104
+ '#### 验收记录', '', bullets(closure.acceptance), '',
105
+ '#### 项目资料汇总', '', bullets(closure.artifacts), '',
106
+ ].join('\n');
107
+ }
108
+ /** PM 的复核 + 复盘 + 总结(闭环第三步)。 */
109
+ export function renderReviewDocument(review, { feedback = null, handoff = null } = {}) {
110
+ const checks = review.acceptanceChecks;
111
+ const failed = checks.filter((check) => check.met !== true);
112
+ const out = [
113
+ `# 复核与复盘 · ${review.id}`, '',
114
+ `- 结论:**${review.verdict === 'accepted' ? '通过' : '退回修改'}**`,
115
+ `- 关联计划:${review.planId}`,
116
+ `- 关联 PRD:${review.prdId ?? '未关联'}`,
117
+ `- 反馈 ID:${review.feedbackId}`, '',
118
+ '## 一、复核:验收逐条核对', '',
119
+ '| 验收标准 | 结果 | 证据 | 备注 |',
120
+ '|---|---|---|---|',
121
+ ...checks.map((check) => `| ${cell(check.criterion)} | ${check.met ? '通过' : '未通过'} | ${cell(check.evidence.join(';') || '—')} | ${cell(check.note || '—')} |`), '',
122
+ failed.length > 0 ? `**未通过项**:${failed.map((check) => check.criterion).join(';')}` : '**全部验收项通过。**', '',
123
+ ];
124
+ if (feedback) {
125
+ out.push('## 二、执行回执(PJM 自反馈)', '', feedback.summary, '');
126
+ out.push(`- 步骤:${feedback.metrics.done}/${feedback.metrics.steps}(${feedback.metrics.percent}%)`);
127
+ out.push(`- 估时/实际:${feedback.metrics.estimatedMinutes} / ${feedback.metrics.actualMinutes} 分钟`);
128
+ if (feedback.deviations.length)
129
+ out.push('- 偏差:', bullets(feedback.deviations));
130
+ if (feedback.openIssues.length)
131
+ out.push('- 遗留问题:', bullets(feedback.openIssues));
132
+ out.push('');
133
+ }
134
+ if (handoff) {
135
+ out.push('## 三、交接对照', '', `- 交接包:${handoff.id}(${handoff.status})`, `- 验收标准数:${handoff.acceptance.length}`, '');
136
+ }
137
+ out.push('## 四、复盘', '');
138
+ out.push('### 保持(做对了什么)', '', bullets(review.retrospective.kept), '');
139
+ out.push('### 改进(哪里不顺)', '', bullets(review.retrospective.improve), '');
140
+ out.push('### 经验(下次怎么做)', '', bullets(review.retrospective.lessons), '');
141
+ out.push('## 五、总结', '', review.summary, '');
142
+ return out.join('\n').trim() + '\n';
143
+ }
@@ -0,0 +1,214 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // Copyright (c) 2026 dsh-Master contributors — see LICENSE-CORE.
3
+ // @ts-nocheck
4
+ /**
5
+ * PM/PJM 编排策略(3.3.5)。
6
+ *
7
+ * 解决的问题:模型知道工具清单,但不知道“现在这一步该用哪个”。让它在 7 个工具里猜,
8
+ * 就会出现「PRD 还没写就交接」「还有未决问题就派发」这类错误。
9
+ *
10
+ * 这里把闭环状态机写成确定性函数:给定结构化状态,返回**唯一推荐动作**与理由。
11
+ * 模型仍然负责内容(写 PRD、拆 WBS、判定验收),编排不替它做判断,也不产生副作用。
12
+ *
13
+ * 约束:
14
+ * * 纯函数,不读全局、不写数据;
15
+ * * 每个 action 都必须对应真实存在的工具名(测试会校验,防止漂移);
16
+ * * 无法推进时返回 `blocked` + 具名原因,而不是编一个动作。
17
+ */
18
+ export const PM_ACTIONS = Object.freeze([
19
+ 'create_prd', 'create_assessment', 'clarify', 'handoff', 'review', 'done', 'blocked',
20
+ ]);
21
+ export const PJM_ACTIONS = Object.freeze([
22
+ 'take_handoff', 'return_to_pm', 'create_plan', 'dispatch', 'report', 'complete_feedback', 'close', 'done', 'blocked',
23
+ ]);
24
+ /** action → 真实工具名(agent preset 里注册过;测试比对)。 */
25
+ export const ACTION_TOOL = Object.freeze({
26
+ create_prd: 'pm_create_prd',
27
+ create_assessment: 'pm_create_assessment',
28
+ clarify: 'pm_create_prd',
29
+ handoff: 'pm_handoff_to_pjm',
30
+ review: 'pm_review_feedback',
31
+ take_handoff: 'pjm_take_handoff',
32
+ return_to_pm: 'pjm_take_handoff',
33
+ create_plan: 'pjm_create_plan',
34
+ dispatch: 'pjm_dispatch_step',
35
+ report: 'pjm_report',
36
+ complete_feedback: 'pjm_submit_feedback',
37
+ close: 'pjm_close_project',
38
+ done: null,
39
+ blocked: null,
40
+ });
41
+ /**
42
+ * PM 的下一步:PRD → 盘点 → 澄清 → 交接 → 复核。
43
+ * @param {{prd?: object|null, assessment?: object|null, handoff?: object|null, feedback?: object|null, review?: object|null}} state
44
+ */
45
+ export function nextPmAction(state = {}) {
46
+ const prd = state.prd ?? null;
47
+ const assessment = state.assessment ?? null;
48
+ const handoff = state.handoff ?? null;
49
+ const feedback = state.feedback ?? null;
50
+ const review = state.review ?? null;
51
+ if (!prd) {
52
+ return { action: 'create_prd', tool: ACTION_TOOL.create_prd, reason: 'no-prd-yet', detail: '先写结构化 PRD(title + goal)' };
53
+ }
54
+ if (!assessment) {
55
+ return { action: 'create_assessment', tool: ACTION_TOOL.create_assessment, reason: 'no-assessment-yet', detail: '六类盘点必须先落库,completed 项要有证据' };
56
+ }
57
+ const openQuestions = Array.isArray(prd.openQuestions) ? prd.openQuestions.length : 0;
58
+ const conflicts = Array.isArray(assessment.conflicts) ? assessment.conflicts.length : 0;
59
+ if (openQuestions > 0 || conflicts > 0) {
60
+ return {
61
+ action: 'clarify',
62
+ tool: null,
63
+ reason: openQuestions > 0 ? 'open-questions-unresolved' : 'conflicts-unresolved',
64
+ detail: openQuestions > 0
65
+ ? `PRD 还有 ${openQuestions} 个未决问题,必须用 ask_user_question 一次问一个决策点`
66
+ : `盘点里有 ${conflicts} 个冲突,必须让用户重新判断(回复:新需求新方案)`,
67
+ blocking: true,
68
+ };
69
+ }
70
+ if (!handoff) {
71
+ return {
72
+ action: 'handoff',
73
+ tool: ACTION_TOOL.handoff,
74
+ reason: 'ready-to-handoff',
75
+ detail: '验收标准齐全且无未决项,打交接包给 PJM',
76
+ argsHint: { prdId: prd.id, assessmentId: assessment.id },
77
+ };
78
+ }
79
+ if (handoff.status === 'needs-clarification') {
80
+ return {
81
+ action: 'blocked',
82
+ tool: null,
83
+ reason: handoff.reason || 'handoff-not-ready',
84
+ detail: '交接包未就绪,先解决未决问题再重新交接',
85
+ blocking: true,
86
+ };
87
+ }
88
+ if (feedback && !review) {
89
+ return {
90
+ action: 'review',
91
+ tool: ACTION_TOOL.review,
92
+ reason: 'feedback-awaiting-review',
93
+ detail: 'PJM 已交反馈:逐条核对验收(met + 证据),写复盘与总结;结论必须与验收一致',
94
+ argsHint: { feedbackId: feedback.id },
95
+ };
96
+ }
97
+ if (review) {
98
+ return {
99
+ action: 'done',
100
+ tool: null,
101
+ reason: review.verdict === 'accepted' ? 'loop-accepted' : 'loop-changes-requested',
102
+ detail: review.verdict === 'accepted' ? '本轮闭环已通过' : 'PJM 需按复核意见重做',
103
+ };
104
+ }
105
+ return {
106
+ action: 'blocked',
107
+ tool: null,
108
+ reason: 'waiting-for-pjm',
109
+ detail: '等 PJM 接单并提交反馈;PM 不替它派发',
110
+ blocking: false,
111
+ };
112
+ }
113
+ /**
114
+ * PJM 的下一步:接单 → 计划 → 派发 → 反馈 → 结案。
115
+ * @param {{handoff?: object|null, plan?: object|null, feedback?: object|null}} state
116
+ */
117
+ export function nextPjmAction(state = {}) {
118
+ const handoff = state.handoff ?? null;
119
+ const plan = state.plan ?? null;
120
+ const feedback = state.feedback ?? null;
121
+ if (handoff && handoff.status === 'needs-clarification') {
122
+ return {
123
+ action: 'return_to_pm',
124
+ tool: null,
125
+ reason: 'handoff-needs-clarification',
126
+ detail: '交接包未就绪:退回 PM 澄清,不得硬开工',
127
+ blocking: true,
128
+ };
129
+ }
130
+ if (handoff && handoff.status === 'ready' && !plan) {
131
+ return {
132
+ action: 'take_handoff',
133
+ tool: ACTION_TOOL.take_handoff,
134
+ reason: 'handoff-ready',
135
+ detail: '接单后再拆 WBS',
136
+ argsHint: { handoffId: handoff.id },
137
+ };
138
+ }
139
+ if (!plan) {
140
+ return {
141
+ action: 'create_plan',
142
+ tool: ACTION_TOOL.create_plan,
143
+ reason: 'no-plan-yet',
144
+ detail: '用 pjm_create_plan 写七类项目内容,步骤必须自己拆(交接包不含 WBS)',
145
+ };
146
+ }
147
+ const steps = Array.isArray(plan.steps) ? plan.steps : [];
148
+ const failed = steps.filter((step) => step.status === 'failed');
149
+ const running = steps.filter((step) => step.status === 'running' || step.status === 'dispatching');
150
+ const pending = steps.filter((step) => step.status === 'pending');
151
+ const blockedReport = Array.isArray(plan.reports) ? [...plan.reports].reverse().find((report) => report.status === 'blocked') : null;
152
+ if (blockedReport && (blockedReport.blocker || blockedReport.summary)) {
153
+ return {
154
+ action: 'report',
155
+ tool: ACTION_TOOL.report,
156
+ reason: 'blocked-reported',
157
+ detail: `最近一次节点报告为阻塞:${blockedReport.blocker || blockedReport.summary};需要 PM/用户决策后才能继续`,
158
+ blocking: true,
159
+ };
160
+ }
161
+ if (failed.length > 0) {
162
+ return {
163
+ action: 'report',
164
+ tool: ACTION_TOOL.report,
165
+ reason: 'step-failed',
166
+ detail: `${failed.length} 个步骤失败:如实汇报失败原因,不得写成完成`,
167
+ argsHint: { planId: plan.id, status: 'blocked' },
168
+ };
169
+ }
170
+ if (running.length > 0) {
171
+ return {
172
+ action: 'report',
173
+ tool: ACTION_TOOL.report,
174
+ reason: 'step-running',
175
+ detail: '有步骤在隔离执行体中运行:等结果并汇报节点,不重复派发',
176
+ argsHint: { planId: plan.id, status: 'progress' },
177
+ };
178
+ }
179
+ if (pending.length > 0) {
180
+ const next = pending[0];
181
+ return {
182
+ action: 'dispatch',
183
+ tool: ACTION_TOOL.dispatch,
184
+ reason: 'step-pending',
185
+ detail: '派发下一个待办步骤(走 fangForker.spawn;不可用即 fail-closed)',
186
+ argsHint: { planId: plan.id, stepId: next.id },
187
+ };
188
+ }
189
+ if (feedback) {
190
+ if (plan.status === 'closed') {
191
+ return { action: 'done', tool: null, reason: 'project-closed', detail: '项目已结案,等 PM 复核结论' };
192
+ }
193
+ return {
194
+ action: 'close',
195
+ tool: ACTION_TOOL.close,
196
+ reason: 'all-steps-done',
197
+ detail: '全部步骤完成且已提交反馈:结案需要总结、评审报告、验收与资料清单',
198
+ argsHint: { planId: plan.id },
199
+ };
200
+ }
201
+ return {
202
+ action: 'complete_feedback',
203
+ tool: ACTION_TOOL.complete_feedback,
204
+ reason: 'all-steps-done-no-feedback',
205
+ detail: '全部步骤完成:提交交付、偏差、遗留问题与测试证据,交给 PM 复核',
206
+ argsHint: { planId: plan.id },
207
+ };
208
+ }
209
+ /** 给 UI/测试用的紧凑摘要。 */
210
+ export function describeAction(result) {
211
+ if (!result || !result.action)
212
+ return 'blocked: unknown state';
213
+ return [result.action, result.reason, result.detail].filter(Boolean).join(' · ');
214
+ }