@joekytc/dsh-swarm 0.2.0 → 0.3.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 (103) hide show
  1. package/README.md +176 -139
  2. package/README.zh-CN.md +172 -125
  3. package/client/ConfigSection.tsx +96 -5
  4. package/client/ConfigSelect.tsx +3 -1
  5. package/client/TaskDrawer.tsx +2 -0
  6. package/client/config-store.ts +48 -7
  7. package/client/kanban.css +69 -0
  8. package/client/timeline-model.ts +2 -0
  9. package/lib/client.js +172 -14
  10. package/lib/config.d.ts +18 -1
  11. package/lib/config.js +14 -0
  12. package/lib/dispatcher/agent-runner.d.ts +13 -5
  13. package/lib/dispatcher/agent-runner.js +78 -38
  14. package/lib/dispatcher/chain-auditor.d.ts +4 -4
  15. package/lib/dispatcher/chain-auditor.js +10 -8
  16. package/lib/dispatcher/dispatcher.d.ts +18 -18
  17. package/lib/dispatcher/dispatcher.js +59 -43
  18. package/lib/dispatcher/event-waker.d.ts +1 -1
  19. package/lib/dispatcher/event-waker.js +2 -2
  20. package/lib/dispatcher/git-credentials.d.ts +2 -2
  21. package/lib/dispatcher/git-credentials.js +2 -2
  22. package/lib/dispatcher/model-candidates.d.ts +1 -1
  23. package/lib/dispatcher/model-candidates.js +1 -1
  24. package/lib/dispatcher/session-events.d.ts +1 -1
  25. package/lib/dispatcher/session-events.js +1 -1
  26. package/lib/dispatcher/target-repo.d.ts +2 -2
  27. package/lib/dispatcher/target-repo.js +3 -3
  28. package/lib/dispatcher/v-orchestrator.d.ts +8 -8
  29. package/lib/dispatcher/v-orchestrator.js +41 -41
  30. package/lib/dispatcher/watchdog.d.ts +1 -1
  31. package/lib/dispatcher/watchdog.js +2 -2
  32. package/lib/domain/config-override.d.ts +14 -0
  33. package/lib/domain/config-override.js +38 -1
  34. package/lib/domain/delivery-contract.d.ts +2 -2
  35. package/lib/domain/delivery-contract.js +5 -5
  36. package/lib/domain/delivery-evidence.d.ts +1 -1
  37. package/lib/domain/delivery-evidence.js +1 -1
  38. package/lib/domain/event-store.js +1 -1
  39. package/lib/domain/im-message.d.ts +15 -0
  40. package/lib/domain/im-message.js +149 -0
  41. package/lib/domain/kanban-service.d.ts +13 -10
  42. package/lib/domain/kanban-service.js +36 -28
  43. package/lib/domain/memory.js +1 -1
  44. package/lib/domain/ocr-review.d.ts +47 -0
  45. package/lib/domain/ocr-review.js +105 -0
  46. package/lib/domain/permissions.js +5 -5
  47. package/lib/domain/projection.js +6 -6
  48. package/lib/domain/state-machine.js +1 -1
  49. package/lib/domain/task-parents.js +1 -1
  50. package/lib/domain/types.d.ts +4 -4
  51. package/lib/index.js +14 -9
  52. package/lib/roles/clean-fs-tools.js +3 -4
  53. package/lib/roles/preset-installer.js +3 -3
  54. package/lib/roles/skill-installer.js +1 -1
  55. package/lib/roles/toolsets.d.ts +60 -16
  56. package/lib/roles/toolsets.js +265 -47
  57. package/lib/roles/wiki-worker.js +3 -3
  58. package/lib/routes/kanban-http.d.ts +25 -2
  59. package/lib/routes/kanban-http.js +171 -11
  60. package/lib/routes/kanban-sse.d.ts +1 -1
  61. package/lib/routes/kanban-sse.js +1 -1
  62. package/lib/routes/planning-driver.d.ts +4 -2
  63. package/lib/routes/planning-driver.js +7 -3
  64. package/lib/routes/prefix-router.d.ts +3 -3
  65. package/lib/routes/prefix-router.js +3 -1
  66. package/lib/services/config-provider.d.ts +0 -4
  67. package/lib/services/config-provider.js +7 -9
  68. package/lib/services/im-delivery.d.ts +94 -0
  69. package/lib/services/im-delivery.js +263 -0
  70. package/lib/services/kanban-provider.d.ts +1 -1
  71. package/lib/services/kanban-provider.js +2 -2
  72. package/lib/services/ocr-cli.d.ts +35 -0
  73. package/lib/services/ocr-cli.js +128 -0
  74. package/lib/tools/kanban-tools.d.ts +2 -2
  75. package/lib/tools/kanban-tools.js +1 -1
  76. package/lib/tools/main-session-tools.d.ts +6 -2
  77. package/lib/tools/main-session-tools.js +58 -20
  78. package/lib/tools/ocr-review-tools.d.ts +11 -0
  79. package/lib/tools/ocr-review-tools.js +63 -0
  80. package/lib/tools/planning-tools.d.ts +5 -2
  81. package/lib/tools/planning-tools.js +11 -9
  82. package/lib/tools/wiki-tools.js +2 -2
  83. package/lib/wiki/kb-linkage.d.ts +1 -1
  84. package/lib/wiki/local-kb-client.js +3 -3
  85. package/lib/wiki/local-kb.d.ts +1 -1
  86. package/lib/wiki/local-kb.js +2 -2
  87. package/lib/wiki/page-path.d.ts +1 -1
  88. package/lib/wiki/page-path.js +6 -3
  89. package/lib/wiki/wiki-vault-client.d.ts +1 -1
  90. package/lib/wiki/wiki-vault-client.js +1 -1
  91. package/package.json +1 -1
  92. package/personas/kanban-d/agent.cordis.yml +5 -5
  93. package/personas/kanban-dt/agent.cordis.yml +23 -5
  94. package/personas/kanban-p/agent.cordis.yml +3 -3
  95. package/personas/kanban-pt/agent.cordis.yml +1 -1
  96. package/personas/kanban-v/agent.cordis.yml +3 -3
  97. package/personas/kanban-w/agent.cordis.yml +5 -5
  98. package/personas/persona-d.md +1 -1
  99. package/personas/persona-dt.md +12 -4
  100. package/personas/persona-p.md +1 -1
  101. package/personas/persona-v.md +2 -2
  102. package/personas/swarm/agent.cordis.yml +43 -0
  103. package/personas/swarm/preset.yml +5 -0
@@ -11,12 +11,12 @@ export const R20_PHASE_EXPECT = {
11
11
  p: { assignee: 'p', mode: 'openspec' },
12
12
  pt: { assignee: 'pt', mode: 'review-plan' }, // 由 P 交付 pt_decision.needed=true 触发(需计划评审才进入)
13
13
  w2: { assignee: 'w', mode: 'kb' },
14
- d: { assignee: 'd', mode: 'execute' }, // R20:D=执行者(实际写代码/git 提交推送),非只读对齐/校验
14
+ d: { assignee: 'd', mode: 'execute' }, // D=执行者(实际写代码/git 提交推送),非只读对齐/校验
15
15
  dt: { assignee: 'dt', mode: 'review-impl' }, // 固定:D 之后必经实现校验+评审
16
16
  w3: { assignee: 'w', mode: 'kb' },
17
17
  summary: null,
18
18
  };
19
- /** M5:每阶段建卡的 body 生成指令(角色定位确定性模板,消除 V 自由发挥导致的角色漂移)。
19
+ /** 每阶段建卡的 body 生成指令(角色定位确定性模板,消除 V 自由发挥导致的角色漂移)。
20
20
  * P=计划者(绝不执行)、D=唯一执行者(TARGET_REPO 必须取自规格卡 file-prefetch 附件 ref,禁止回退/猜测)、
21
21
  * W=KB 同步(绝不执行代码)。V 把对应模板写入 kanban_create 的 body。 */
22
22
  export const PHASE_INSTRUCTIONS = {
@@ -57,7 +57,7 @@ export const PHASE_INSTRUCTIONS = {
57
57
  ].join('\n'),
58
58
  dt: [
59
59
  '## DT 阶段任务体要求(实现校验+评审,只读护栏)',
60
- 'body 写入实现校验指令:对 D 产物实证校验(test/build/typecheck/diff/git 证据 + open-code-review 评审),输出 verdict+issues 入交接 metadata.review_evidence。评审目标为 D 交接 metadata.branch 指向的 feature 分支(ocr review --from <TARGET_BRANCH> --to <branch>),而非 TARGET_BRANCH。',
60
+ 'body 写入实现校验指令:对 D 产物实证校验(test/build/typecheck/diff/git 证据 + open-code-review 评审),输出 verdict+issues 入交接 metadata.review_evidence。评审目标为 D 交接 metadata.branch 指向的 feature 分支,而非 TARGET_BRANCH。评审引擎:用 ocr_review 工具按配置引擎评审——委托 = preview→rule→git diff 自评;托管 = managed 一次出 findings;托管未配置时按工具指引静默改走委托;ocr 未安装则 kanban_block(\'review-tool-unavailable\') 并注明可在 GUI 配置面板安装。',
61
61
  '铁律:DT 是只读校验+评审角色,绝不修改源码/产物;校验经 ToolGuard 硬性只读护栏;不注入 git 凭据。',
62
62
  ].join('\n'),
63
63
  w3: [
@@ -65,7 +65,7 @@ export const PHASE_INSTRUCTIONS = {
65
65
  'body 写入 KB 收尾同步指令:读 D 交接 → wiki_write 同步(pagePath 必须逐字等于下方「KB 页路径规则」给出的路径)→ complete(kb_url)。禁止任何 git/代码操作。',
66
66
  ].join('\n'),
67
67
  };
68
- /** D9(W 角色知识库双模式):按 kbMode 构建各阶段建卡 body 指令(phase 键控——W2/W3 同为
68
+ /** W 角色知识库双模式:按 kbMode 构建各阶段建卡 body 指令(phase 键控——W2/W3 同为
69
69
  * assignee='w'+mode='kb',assignee+mode 签名无法区分两相文案;系统状态本就按 phase 键控)。
70
70
  * remote → PHASE_INSTRUCTIONS 原文(护栏测试零改动前提);local → 覆盖 w2/w3/d/dt 四相
71
71
  * (skill 工具加载 llm-wiki + 本地库 fs 读写,禁 wiki_write/wiki_read),p/pt/summary 原样回落。
@@ -82,7 +82,7 @@ export function buildPhaseInstruction(phase, ctx, kbMode) {
82
82
  case 'd':
83
83
  return '先读父任务交接(W2)里的 page_path(本地库 wiki/sources/ 下相对路径,库根见运行时上下文注入——Task 8 已注入 D),直接 fs 读实施计划原文;如需 KB 经验召回,经 skill 工具加载 llm-wiki query(本地库;kanban-d preset 已挂 tool-skill)——再按计划执行规格卡 solution/testing —— git worktree/branch → 改代码/README → git commit → git push(仅 feature 分支,可选)→ 自检并附产物证据(changed_files/commit_hash)。其余 TDD/TARGET_REPO/TARGET_BRANCH 等要求与 remote 版一致。';
84
84
  case 'dt':
85
- return `对 D 产物实证校验(test/build/typecheck/diff/git 证据 + open-code-review 评审),输出 verdict+issues 入交接 metadata.review_evidence。评审页经 fs 写本地库 <库根>/wiki/queries/${ctx.chainId}/review/<name>.md(库根见运行时上下文注入——Task 8 已注入 DT);库根外一律只读,其余铁律与 remote 版一致。`;
85
+ return `对 D 产物实证校验(test/build/typecheck/diff/git 证据 + open-code-review 评审),输出 verdict+issues 入交接 metadata.review_evidence。评审引擎:用 ocr_review 工具按配置引擎评审(委托 = preview→rule→git diff 自评;托管 = managed 一次出 findings;托管未配置时按工具指引静默改走委托;ocr 未安装则 kanban_block('review-tool-unavailable') 并注明可在 GUI 配置面板安装)。评审页经 fs 写本地库 <库根>/wiki/queries/${ctx.chainId}/review/<name>.md(库根已由系统注入 DT 运行时上下文);库根外一律只读,其余铁律与 remote 版一致。`;
86
86
  default:
87
87
  return PHASE_INSTRUCTIONS[phase] ?? '';
88
88
  }
@@ -111,7 +111,7 @@ function extractPtPassSuggestions(state, chainId) {
111
111
  return Array.isArray(ev?.issues) ? ev.issues : [];
112
112
  }
113
113
  /** 仅评审卡:archived 且从未处理过 verdict = 作废(void)。human 归档评审卡即作废,编排器视其不存在。
114
- * 非评审卡(p/d/w…)一律返回 false——避免 B6 误判"该阶段无卡"而重复建卡。 */
114
+ * 非评审卡(p/d/w…)一律返回 false——避免误判"该阶段无卡"而重复建卡。 */
115
115
  const REVIEW_MODES = ['review-plan', 'review-impl'];
116
116
  function isVoidReview(task, events) {
117
117
  if (task.status !== 'archived')
@@ -120,11 +120,11 @@ function isVoidReview(task, events) {
120
120
  return false; // 非评审卡不判 void
121
121
  return !events.some((e) => e.taskId === task.id && (e.kind === 'review/passed' || e.kind === 'review/failed' || e.kind === 'review/gave-up'));
122
122
  }
123
- /** Task 4(V 会话注入加固):V 会话身份标记——setup 完整成功(kanban-v preset mount + 角色工具面)后
123
+ /** V 会话注入加固:V 会话身份标记——setup 完整成功(kanban-v preset mount + 角色工具面)后
124
124
  * 写入,live 复用前校验。与 agent-runner.ts roleCompositions 同款机制(进程内 WeakMap 按 incarnation
125
125
  * 键控:GUI 重开同名会话/宿主自建 incarnation 查不到标记 → 拒绝盲复用;实例回收标记随 GC 消失)。
126
126
  * 不直接复用其 WeakMap:其 marker 结构 {role,taskId} 面向任务会话(V 无 taskId),读取端未导出,
127
- * 导出需改 agent-runner.ts(本任务硬约束禁止)。 */
127
+ * 导出需改 agent-runner.ts(硬约束禁止)。 */
128
128
  const vSessionCompositions = new WeakMap();
129
129
  const V_SESSION_PRESET_ID = 'kanban-v';
130
130
  /** 宿主形态防御:dsh 0.1.2-rc.1 移除 Session.events(DSH-0.1.2-A4-03)→ 优先 snapshotEvents();
@@ -149,15 +149,15 @@ export class VOrchestrator {
149
149
  }
150
150
  return o;
151
151
  }
152
- /** Fix D:stall 自动再唤醒上限(同一阶段连续零产出 → 自动重试 ≤3 次,间隔递增)后放弃并显形。 */
152
+ /** stall 自动再唤醒上限(同一阶段连续零产出 → 自动重试 ≤3 次,间隔递增)后放弃并显形。 */
153
153
  static STALL_REWAKE_LIMIT = 3;
154
- /** Fix D:stall 再唤醒基础延迟(按 stallCount 倍增:5s/10s/15s),给瞬时故障/采样波动恢复窗口。 */
154
+ /** stall 再唤醒基础延迟(按 stallCount 倍增:5s/10s/15s),给瞬时故障/采样波动恢复窗口。 */
155
155
  static STALL_REWAKE_DELAY_MS = 5_000;
156
156
  rewakeTimers = new Map();
157
157
  /** 同链 wakeV 并发防护:在途时后续唤醒合并为 pending,完成后补跑一次(事件不丢)。 */
158
158
  waking = new Set();
159
159
  pendingWake = new Set();
160
- /** Fix D:orchestration 变更回调(dispatcher 注入 saveOrchs)——stall re-wake 路径绕过
160
+ /** orchestration 变更回调(dispatcher 注入 saveOrchs)——stall re-wake 路径绕过
161
161
  * EventWaker,其 stallCount/phase 变化需自行落盘,防重启丢重试进度。 */
162
162
  onOrchChange;
163
163
  async wakeV(chainId) {
@@ -176,7 +176,7 @@ export class VOrchestrator {
176
176
  }
177
177
  }
178
178
  }
179
- /** Fix D:stall 自动再唤醒(≤3 次)。同链 pending 幂等;建卡成功(stallCount=0)后到期的
179
+ /** stall 自动再唤醒(≤3 次)。同链 pending 幂等;建卡成功(stallCount=0)后到期的
180
180
  * re-wake 自动作废(回调内按 stallCount 判空跳过)。 */
181
181
  scheduleRewake(chainId, stallCount) {
182
182
  if (this.rewakeTimers.has(chainId))
@@ -191,7 +191,7 @@ export class VOrchestrator {
191
191
  }, delay);
192
192
  this.rewakeTimers.set(chainId, timer);
193
193
  }
194
- /** Fix D:清理待触发的 re-wake 定时器(插件 dispose 时调用)。 */
194
+ /** 清理待触发的 re-wake 定时器(插件 dispose 时调用)。 */
195
195
  dispose() {
196
196
  for (const t of this.rewakeTimers.values())
197
197
  clearTimeout(t);
@@ -224,7 +224,7 @@ export class VOrchestrator {
224
224
  // → 向 V 发一轮阻塞复核(本轮 V 唯一动作),V 用 kanban_comment 以 [blocked-review] 开头逐一
225
225
  // 评论给方向后待命;无此类任务则正常推进。扩展为所有 blocked(含 kb-insufficient 等),
226
226
  // 幂等由 hasBlockReview 保证(每次阻塞至多一轮复核评论,blocked→unblocked→再 blocked
227
- // 因新 block 事件 seq 更新才重新评论)。置于 B4 门控之前:即使规格卡未批准(planning/draft),
227
+ // 因新 block 事件 seq 更新才重新评论)。置于阶段门控之前:即使规格卡未批准(planning/draft),
228
228
  // 阻塞也可复核(恢复不依赖批准)。
229
229
  const blockedReview = [...state.tasks.values()].filter((t) => {
230
230
  if (t.chainId !== chainId || t.status !== 'blocked')
@@ -251,15 +251,15 @@ export class VOrchestrator {
251
251
  await agent.whenIdle();
252
252
  return; // 本轮 V 唯一动作是阻塞复核,不再推进阶段
253
253
  }
254
- // B4 阶段门控:V 仅规格卡 approved(链 executing)后才行动(从 p 起跑建执行链卡);
254
+ // 阶段门控:V 仅规格卡 approved(链 executing)后才行动(从 p 起跑建执行链卡);
255
255
  // draft/planning 时 V 待命,等 spec-card/approved 事件唤醒(event-waker 已订阅)。
256
256
  const approved = chain.status === 'executing' && specCard?.status === 'approved';
257
257
  if (!approved)
258
258
  return;
259
- // 阶段推进循环(修复轮 6,举一反三):跳过已完成(终态卡)阶段,
259
+ // 阶段推进循环(举一反三):跳过已完成(终态卡)阶段,
260
260
  // 停在需要建卡的阶段建卡并推进;避免「推进 phase 后不建下一卡」造成的流水线停滞。
261
261
  for (;;) {
262
- // B4 阶段门控(每轮按当前 phase 重查):V 仅 approved 后行动(chain/executing 已确认)。
262
+ // 阶段门控(每轮按当前 phase 重查):V 仅 approved 后行动(chain/executing 已确认)。
263
263
  const approvedHere = chain.status === 'executing' && specCard?.status === 'approved';
264
264
  if (!approvedHere)
265
265
  return;
@@ -319,12 +319,12 @@ export class VOrchestrator {
319
319
  }
320
320
  // 无评审卡 → 建卡(走下方通用建卡路径,但建卡后不推进 phase)
321
321
  }
322
- // B6 幂等:当前 phase 期望卡(R20_PHASE_EXPECT 匹配 assignee+mode)已存在且未终态 → 不重复建卡直接待命
322
+ // 幂等:当前 phase 期望卡(R20_PHASE_EXPECT 匹配 assignee+mode)已存在且未终态 → 不重复建卡直接待命
323
323
  // (防插件重启后重复建卡、V 一轮连发多卡)。
324
324
  const existing = chainTasks.find((t) => t.assignee === expect.assignee && t.mode === expect.mode && !isVoidReview(t, state.events));
325
325
  if (existing && !terminal.includes(existing.status))
326
326
  return;
327
- // 仅当当前 phase 的 (assignee,mode) 在整个 R20 序列唯一时,"已终态 existing 卡 = 本阶段自己的卡已完成"
327
+ // 仅当当前 phase 的 (assignee,mode) 在整个阶段序列唯一时,"已终态 existing 卡 = 本阶段自己的卡已完成"
328
328
  // 才成立;w2/w3 同为 (w,kb) 时 existing 可能是上一阶段(w2)的卡,不能据此推进(否则 w3 被误跳过)。
329
329
  const sameExpectCount = R20_PHASE_ORDER.filter((p) => {
330
330
  const e = R20_PHASE_EXPECT[p];
@@ -344,7 +344,7 @@ export class VOrchestrator {
344
344
  // 交付契约闸已对所有 actor(含 human 强制收尾)在完成时拦截(缺交付物会先被标 blocked,
345
345
  // 不会被 resolve 成父卡),故此处仅剩 legacy(改闸前已落盘的 done-but-missing)。done 不可变、
346
346
  // 不能重标 blocked,发 system 评论记录断裂并停住:不建下游卡、不推进 phase,避免拖到下游执行时才报错。
347
- // D9:透传当前 baseUrl(local 模式为 '' → strict local 分支认可 kb_url="" 交付);
347
+ // 透传当前 baseUrl(local 模式为 '' → strict local 分支认可 kb_url="" 交付);
348
348
  // wikiVault?. 防护测试 stub(getEffective() 返回 {} 时回退 undefined=宽松,行为同旧)。
349
349
  const missingParents = missingParentDelivery(state, parents, this.configProvider.getEffective().wikiVault?.baseUrl);
350
350
  if (missingParents.length > 0) {
@@ -357,15 +357,15 @@ export class VOrchestrator {
357
357
  const ptReason = orch.phase === 'pt' ? extractPtReason(state, chainId) : '';
358
358
  // 2026-09-03 P/PT 定位决议:D 卡创建上下文注入 PT pass 留档的非阻塞建议(评审遗留建议随卡传递)。
359
359
  const ptSuggestions = orch.phase === 'd' ? extractPtPassSuggestions(state, chainId) : [];
360
- // Task 7:KB 页路径规则(W2/W3 wiki_write pagePath 确定性下发)——repoSlug 由链 workspaceDir 派生,
360
+ // KB 页路径规则(W2/W3 wiki_write pagePath 确定性下发)——repoSlug 由链 workspaceDir 派生,
361
361
  // V 不需计算,直接把路径模板写入建卡 context;链无 workspaceDir 时注入空串(不拦截建卡)。
362
362
  const kbPageRoot = chain?.workspaceDir ? `projects/${buildRepoSlug(chain.workspaceDir)}` : null;
363
363
  const context = [
364
- '# V 编排轮次(R20 逐阶段创建)',
364
+ '# V 编排轮次(逐阶段创建)',
365
365
  `chain=${chainId} phase=${orch.phase}`,
366
366
  `NEXT_TASK_ASSIGNEE=${expect.assignee} MODE=${expect.mode}`,
367
367
  `PARENT_DEPS=${parents.length > 0 ? parents.join(',') : '(无)'}`,
368
- // M5:D 阶段额外注入 testing 段(执行指令依赖 solution/testing);附件 ref 供 V 取真实仓库路径写 TARGET_REPO
368
+ // D 阶段额外注入 testing 段(执行指令依赖 solution/testing);附件 ref 供 V 取真实仓库路径写 TARGET_REPO
369
369
  specCard ? `## 规格卡\n${specCard.sections.problem}\n${specCard.sections.solution}${orch.phase === 'd' ? '\n' + specCard.sections.testing : ''}\n附件:${specCard.attachments.map((a) => `${a.kind}:${a.ref}`).join(' | ') || '(无)'}` : '',
370
370
  '## 当前任务\n' + [...state.tasks.values()].filter((t) => t.chainId === chainId)
371
371
  .map((t) => {
@@ -397,17 +397,17 @@ export class VOrchestrator {
397
397
  }
398
398
  catch (err) {
399
399
  // 防线④:异常收场 = 本轮零产出的一种形态(2026-09-04 mtmgp81q:异常逃出本函数
400
- // 被 dispatcher 吞掉,Fix D stall 计数整段被跳过 → 链静默死锁)。在此并入 stall 计数。
401
- // Task 4:getVAgent 也纳入——preset mount fail-fast / resume live 冲突等「获取会话失败」
400
+ // 被 dispatcher 吞掉,stall 计数整段被跳过 → 链静默死锁)。在此并入 stall 计数。
401
+ // getVAgent 也纳入——preset mount fail-fast / resume live 冲突等「获取会话失败」
402
402
  // 与 turn 执行异常同收场(计 stall → scheduleRewake → 超限 [create-failed] + blockChain)。
403
403
  turnError = err;
404
404
  console.error('[dsh-swarm][debug] V turn error (treated as zero-output) chain=' + chainId + ' phase=' + orch.phase + ': ' + String(err));
405
405
  }
406
- // R4 建卡数量硬闸:本轮只允许一张期望匹配卡推进 phase——取第一张匹配卡,其余建卡不推进
406
+ // 建卡数量硬闸:本轮只允许一张期望匹配卡推进 phase——取第一张匹配卡,其余建卡不推进
407
407
  // (提取 kanban_create 调用;假实现从会话事件取,真实实现同名)。
408
- // 修复轮 6:session.events 条目形态为 {type, data:{name, arguments}},name 在 data 下且
408
+ // session.events 条目形态为 {type, data:{name, arguments}},name 在 data 下且
409
409
  // arguments 是 JSON 字符串——统一经 toolName/toolArgs(src/dispatcher/session-events.ts)读取。
410
- // Task 5(宿主形态防御):session.events 缺失(undefined/非数组)时 console.error 留痕(含
410
+ // 宿主形态防御:session.events 缺失(undefined/非数组)时 console.error 留痕(含
411
411
  // chainId/phase/sessionId),随零产出语义计 stall——非静默,但不崩(诊断仅在缺失时打)。
412
412
  if (!turnError && agent !== null && !Array.isArray(agent.session?.events) && typeof agent.session?.snapshotEvents !== 'function') {
413
413
  console.error('[dsh-swarm][debug] 宿主会话事件缺失(形态异常),按零产出处理 chain=' + chainId + ' phase=' + orch.phase + ' sessionId=' + String(orch.sessionId));
@@ -419,7 +419,7 @@ export class VOrchestrator {
419
419
  const a = toolArgs(e);
420
420
  return a.assignee === expect.assignee && a.mode === expect.mode;
421
421
  });
422
- // 建卡失败防护(Fix D):V 本轮未产生期望卡(assignee+mode 不匹配)→ 记 stall 轮次并自动再唤醒
422
+ // 建卡失败防护:V 本轮未产生期望卡(assignee+mode 不匹配)→ 记 stall 轮次并自动再唤醒
423
423
  // 重试(≤3 次,间隔 5s/10s/15s 递增——覆盖采样波动/瞬时故障,2026-09-02 倒计时链实测:V 首轮
424
424
  // 只出文本不调工具、第二轮重放即正常建卡);超过上限 → 放弃:在链上锚点卡(最近终态卡,无则
425
425
  // 最新卡)发 [create-failed] system 评论显形(幂等:已有该评论则不再发)+ blockChain 链级终态
@@ -533,7 +533,7 @@ export class VOrchestrator {
533
533
  return 'gave-up';
534
534
  }
535
535
  // 未超限:createReworkTask(原任务保持 done)+ 新建复审卡(parents=rework,reviewAttempt=rework.reviewAttempt)。
536
- // 2026-09-03 P/PT 定位决议铁律3:复审卡 body 确定性注入上一轮 issues 对账输入——复审卡原为空 body,
536
+ // 2026-09-03 P/PT 定位决议铁律:复审卡 body 确定性注入上一轮 issues 对账输入——复审卡原为空 body,
537
537
  // 对账纯靠模型自觉(ch_1_mtjuukv2 三轮空转的结构根因之一);输入直接取作用域内 evidence,不查事件。
538
538
  const reconcileBody = [
539
539
  role === 'pt' ? '## PT 复审任务体要求(计划评审,只读)' : '## DT 复审任务体要求(实现校验+评审,只读护栏)',
@@ -561,9 +561,9 @@ export class VOrchestrator {
561
561
  return R20_PHASE_ORDER[Math.min(i + 1, R20_PHASE_ORDER.length - 1)];
562
562
  }
563
563
  async getVAgent(orch) {
564
- // B3:resume 与 create 都传 setup——恢复的 V 会话同样装配角色工具面(agent scope 注册随会话重建),
564
+ // resume 与 create 都传 setup——恢复的 V 会话同样装配角色工具面(agent scope 注册随会话重建),
565
565
  // 与 agent-runner.ts 的 installRoleTools 用法一致。
566
- // 修复轮 6:V 后台编排会话与 P/W/D 一致,显式设置 approval=never + sandbox=workspace-write,
566
+ // V 后台编排会话与 P/W/D 一致,显式设置 approval=never + sandbox=workspace-write,
567
567
  // 避免在无 preset 装配时因默认审批策略在后台无应答者而挂起(卡死调度器首轮 tick)。
568
568
  const setup = async (agentCtx) => {
569
569
  // 思考等级强制(waterfall):与 agent-runner 同缺陷——宿主 selection 无 create-options 覆盖层,
@@ -578,14 +578,14 @@ export class VOrchestrator {
578
578
  const session = agentCtx.agent?.session;
579
579
  session?.append?.('approval/policy', { policy: 'never', source: 'delegation' });
580
580
  session?.append?.('sandbox/mode', { mode: 'workspace-write', source: 'delegation' });
581
- // R21 对齐(2026-08-17):V=butler·orchestrator 零执行能力——先挂 kanban-v 裁剪 preset
581
+ // 对齐(2026-08-17):V=butler·orchestrator 零执行能力——先挂 kanban-v 裁剪 preset
582
582
  // (组合仅 persona + agent-instructions,无 bash/fs/run_code/web/skill/delegation 等执行/探索工具),
583
583
  // 再注入 kanban/spec 工具面;从基座层面落实「V 只路由、不执行」。
584
584
  const presets = agentCtx.get?.('agentPresets');
585
585
  if (presets) {
586
- // Task 4 加固:preset mount 失败即抛(fail-fast),不再 console.error 静默放行——V 会话
586
+ // preset mount 失败即抛(fail-fast),不再 console.error 静默放行——V 会话
587
587
  // 无 persona 基座不得裸奔。抛错 → create/resume 失败 → getVAgent 抛 → 主推进路径并入
588
- // 异常收场(Fix D stall 计数,超限 [create-failed] + blockChain);候选循环对非 model
588
+ // 异常收场(stall 计数,超限 [create-failed] + blockChain);候选循环对非 model
589
589
  // 错误立即失败(isModelUnavailableError=false → throw),不会被候选切换吞掉。
590
590
  const vSessionId = orch.sessionId ?? 'kbn-v-' + orch.chainId;
591
591
  try {
@@ -596,19 +596,19 @@ export class VOrchestrator {
596
596
  }
597
597
  }
598
598
  await installRoleTools(agentCtx, 'v', { kanban: this.kanban, wiki: this.wiki });
599
- // Task 4:setup 完整成功(mount + 工具面)后写身份标记——live 复用校验依据(同 agent-runner 组合标记机制)。
599
+ // setup 完整成功(mount + 工具面)后写身份标记——live 复用校验依据(同 agent-runner 组合标记机制)。
600
600
  const vAgent = agentCtx.agent;
601
601
  if (vAgent && typeof vAgent === 'object')
602
602
  vSessionCompositions.set(vAgent, V_SESSION_PRESET_ID);
603
603
  };
604
- // 模型候选链(Task 12):V 会话 create/resume 按 primary→fallbacks 静默切换;
604
+ // 模型候选链:V 会话 create/resume 按 primary→fallbacks 静默切换;
605
605
  // V 无任务卡可 block——全候选不可用抛最后错误(wakeV 调用方按既有错误路径处理)。
606
606
  const candidates = buildModelCandidates(this.configProvider.getEffective(), 'v', this.defaultModel);
607
607
  const spawnWith = async (opts) => {
608
608
  if (orch.sessionId) {
609
- // 修复轮 6:V 会话首轮创建后保持 live,resume 会抛 "cannot prepare session while it is live"。
609
+ // V 会话首轮创建后保持 live,resume 会抛 "cannot prepare session while it is live"。
610
610
  // 优先复用 agents registry 中仍 live 的会话(followup 续用),仅当会话已下线时才 resume。
611
- // Task 4 加固:live 复用前校验身份标记(参照 agent-runner resumeOrReuse 同款)——标记匹配
611
+ // live 复用前校验身份标记(参照 agent-runner resumeOrReuse 同款)——标记匹配
612
612
  // 才复用;缺失/不匹配(GUI 重开同名会话/宿主自建 incarnation/历史会话)拒绝盲复用,改走
613
613
  // resume 重跑 setup 重新 mount(身份自愈);resume 撞 live 冲突则抛错走防线——绝不静默
614
614
  // 把错误身份的会话交给调用方。
@@ -624,7 +624,7 @@ export class VOrchestrator {
624
624
  const h = await this.agents.resume({ resumeSessionId: orch.sessionId, ...opts, setup });
625
625
  return h.agent;
626
626
  }
627
- // M2(Q5)+归组:V 编排会话创建在主 agent 工作空间;缺失时询问用户注册工作区,仍不可得 → 抛错(dispatcher 捕获日志,V 待命)。
627
+ // 归组:V 编排会话创建在主 agent 工作空间;缺失时询问用户注册工作区,仍不可得 → 抛错(dispatcher 捕获日志,V 待命)。
628
628
  let ws = await this.chainWorkspace(orch.chainId);
629
629
  if (!ws) {
630
630
  ws = await resolveOrCreateWorkspace(this.ctx, null, 'chain ' + orch.chainId + ' V');
@@ -652,7 +652,7 @@ export class VOrchestrator {
652
652
  }
653
653
  throw lastErr;
654
654
  }
655
- /** M2(Q5)+归组:链的 workspaceDir(发起 /plan: 的主 agent 工作空间);缺失返回 null(调用方询问/报错,不落 kanban 存储)。 */
655
+ /** 归组:链的 workspaceDir(发起 /plan: 的主 agent 工作空间);缺失返回 null(调用方询问/报错,不落 kanban 存储)。 */
656
656
  async chainWorkspace(chainId) {
657
657
  const state = await this.kanban.snapshot();
658
658
  return state.chains.get(chainId)?.workspaceDir ?? null;
@@ -1,6 +1,6 @@
1
1
  import type { KanbanService } from '../domain/kanban-service.js';
2
2
  /** 心跳超时回收(running 无心跳 → failed,可重试)。
3
- * failed 任务的熔断(attempts≥maxRetries → blocked(gave_up))与重派由调度器(Dispatcher.tick)统一处理(B1)。 */
3
+ * failed 任务的熔断(attempts≥maxRetries → blocked(gave_up))与重派由调度器(Dispatcher.tick)统一处理。 */
4
4
  export declare class Watchdog {
5
5
  private timer;
6
6
  private readonly kanban;
@@ -1,5 +1,5 @@
1
1
  /** 心跳超时回收(running 无心跳 → failed,可重试)。
2
- * failed 任务的熔断(attempts≥maxRetries → blocked(gave_up))与重派由调度器(Dispatcher.tick)统一处理(B1)。 */
2
+ * failed 任务的熔断(attempts≥maxRetries → blocked(gave_up))与重派由调度器(Dispatcher.tick)统一处理。 */
3
3
  export class Watchdog {
4
4
  timer = null;
5
5
  kanban;
@@ -11,7 +11,7 @@ export class Watchdog {
11
11
  if (t.status === 'running') {
12
12
  const lastBeat = t.heartbeats.at(-1) ?? state.events.find((e) => e.taskId === t.id && e.kind === 'task/claimed')?.at ?? now;
13
13
  if (now - lastBeat > this.cfg.staleTimeoutSeconds * 1000) {
14
- await this.kanban.failTask(t.id, 'stale-reclaim', 'system'); // P0-5:超时回收发 failed(可重试),不直接 block
14
+ await this.kanban.failTask(t.id, 'stale-reclaim', 'system'); // 超时回收发 failed(可重试),不直接 block
15
15
  }
16
16
  }
17
17
  // failed 任务不在此处理:Dispatcher.tick 负责 attempts<maxRetries 重派 / attempts≥maxRetries 熔断
@@ -14,6 +14,13 @@ export interface EditableOverride {
14
14
  roles?: {
15
15
  models?: Partial<Record<Role, EditableModelInput>>;
16
16
  };
17
+ reviewEngine?: {
18
+ mode?: 'delegate' | 'managed';
19
+ managed?: {
20
+ provider?: string;
21
+ model?: string;
22
+ };
23
+ };
17
24
  }
18
25
  export interface EditableModelSnapshot {
19
26
  provider: string;
@@ -28,6 +35,13 @@ export interface EditableSnapshot {
28
35
  roles: {
29
36
  models: Partial<Record<Role, EditableModelSnapshot>>;
30
37
  };
38
+ reviewEngine: {
39
+ mode: 'delegate' | 'managed';
40
+ managed: {
41
+ provider: string;
42
+ model: string;
43
+ };
44
+ };
31
45
  }
32
46
  export type ConfigSource = 'override' | 'inherited';
33
47
  export type SourceMap = Record<string, ConfigSource>;
@@ -6,6 +6,16 @@ export function mergeConfig(baseline, override) {
6
6
  wiki.baseUrl = override.wikiVault.baseUrl;
7
7
  if (override?.wikiVault?.pagePrefix !== undefined)
8
8
  wiki.pagePrefix = override.wikiVault.pagePrefix;
9
+ const reviewEngine = {
10
+ mode: baseline.reviewEngine?.mode ?? 'delegate',
11
+ managed: { provider: baseline.reviewEngine?.managed?.provider ?? '', model: baseline.reviewEngine?.managed?.model ?? '' },
12
+ };
13
+ if (override?.reviewEngine?.mode !== undefined)
14
+ reviewEngine.mode = override.reviewEngine.mode;
15
+ if (override?.reviewEngine?.managed?.provider !== undefined)
16
+ reviewEngine.managed.provider = override.reviewEngine.managed.provider;
17
+ if (override?.reviewEngine?.managed?.model !== undefined)
18
+ reviewEngine.managed.model = override.reviewEngine.managed.model;
9
19
  const models = { ...baseline.roles.models };
10
20
  for (const role of ROLES) {
11
21
  const base = baseline.roles.models?.[role];
@@ -23,7 +33,7 @@ export function mergeConfig(baseline, override) {
23
33
  if (cur.provider && cur.model)
24
34
  models[role] = cur;
25
35
  }
26
- return { ...baseline, wikiVault: wiki, roles: { ...baseline.roles, models } };
36
+ return { ...baseline, wikiVault: wiki, roles: { ...baseline.roles, models }, reviewEngine };
27
37
  }
28
38
  export function computeSources(override) {
29
39
  const src = {};
@@ -37,6 +47,9 @@ export function computeSources(override) {
37
47
  src[`roles.models.${role}.${f}`] = hit ? 'override' : 'inherited';
38
48
  }
39
49
  }
50
+ src['reviewEngine.mode'] = override?.reviewEngine?.mode !== undefined ? 'override' : 'inherited';
51
+ src['reviewEngine.managed.provider'] = override?.reviewEngine?.managed?.provider !== undefined ? 'override' : 'inherited';
52
+ src['reviewEngine.managed.model'] = override?.reviewEngine?.managed?.model !== undefined ? 'override' : 'inherited';
40
53
  return src;
41
54
  }
42
55
  export function projectEditable(effective) {
@@ -49,6 +62,10 @@ export function projectEditable(effective) {
49
62
  return {
50
63
  wikiVault: { baseUrl: effective.wikiVault.baseUrl, pagePrefix: effective.wikiVault.pagePrefix },
51
64
  roles: { models },
65
+ reviewEngine: {
66
+ mode: effective.reviewEngine?.mode ?? 'delegate',
67
+ managed: { provider: effective.reviewEngine?.managed?.provider ?? '', model: effective.reviewEngine?.managed?.model ?? '' },
68
+ },
52
69
  };
53
70
  }
54
71
  export function validateConfig(snapshot) {
@@ -59,6 +76,10 @@ export function validateConfig(snapshot) {
59
76
  errs.push('wikiVault.baseUrl');
60
77
  if (!(snapshot.wikiVault.pagePrefix ?? '').trim())
61
78
  errs.push('wikiVault.pagePrefix');
79
+ // 就绪性(managed provider/model 是否已配好)是运行时探测,不在此校验;仅 mode 枚举把关。
80
+ const reMode = snapshot.reviewEngine?.mode;
81
+ if (reMode !== 'delegate' && reMode !== 'managed')
82
+ errs.push('reviewEngine.mode');
62
83
  for (const role of ROLES) {
63
84
  const m = snapshot.roles.models[role];
64
85
  if (!m)
@@ -97,5 +118,21 @@ export function diffOverride(baseline, snapshot) {
97
118
  }
98
119
  if (Object.keys(models).length)
99
120
  out.roles = { models: models };
121
+ const bre = baseline.reviewEngine ?? { mode: 'delegate', managed: { provider: '', model: '' } };
122
+ const snapMode = snapshot.reviewEngine?.mode ?? 'delegate';
123
+ const snapProvider = snapshot.reviewEngine?.managed?.provider ?? '';
124
+ const snapModel = snapshot.reviewEngine?.managed?.model ?? '';
125
+ const re = {};
126
+ if (snapMode !== bre.mode)
127
+ re.mode = snapMode;
128
+ const reManaged = {};
129
+ if (snapProvider !== bre.managed.provider)
130
+ reManaged.provider = snapProvider;
131
+ if (snapModel !== bre.managed.model)
132
+ reManaged.model = snapModel;
133
+ if (Object.keys(reManaged).length)
134
+ re.managed = reManaged;
135
+ if (Object.keys(re).length)
136
+ out.reviewEngine = re;
100
137
  return out;
101
138
  }
@@ -4,7 +4,7 @@ export declare function requiredDeliveryKeys(assignee: Role, mode: TaskMode): st
4
4
  export declare function missingPtDecisionKeys(handoff: Handoff | undefined): string[];
5
5
  /** 缺失的交付键(存在但为空的字符串/非字符串均视为缺失;pt_decision 走结构校验透传细粒度键)。
6
6
  * 可选 kbUrlBase:提供时对 w:kb 的 kb_url 做 host 前缀硬校验(防 LLM 手写错域名)、对 page_path 做
7
- * 命名空间格式校验(Q3&5:防 LLM 自造路径/拼错层级)——未提供则仅非空校验(兼容旧调用/测试)。 */
7
+ * 命名空间格式校验(防 LLM 自造路径/拼错层级)——未提供则仅非空校验(兼容旧调用/测试)。 */
8
8
  export declare function missingDeliveryKeys(assignee: Role, mode: TaskMode, handoff: Handoff | undefined, kbUrlBase?: string): string[];
9
9
  export declare function hasRequiredDelivery(assignee: Role, mode: TaskMode, handoff: Handoff | undefined): boolean;
10
10
  /** 交付契约缺失的父卡项(供 V 建下游卡前的前置校验)。 */
@@ -15,5 +15,5 @@ export interface MissingParentDelivery {
15
15
  missing: string[];
16
16
  }
17
17
  /** 对一组父任务 id 做交付契约校验,返回缺关键交付物的父卡清单(无缺失返回空数组)。
18
- * kbUrlBase 可选透传 missingDeliveryKeys(与 Task 4 的 C1 推导修正配套——local 模式传 '' 走 strict local 分支)。 */
18
+ * kbUrlBase 可选透传 missingDeliveryKeys(与上方 kbUrlBase 推导修正配套——local 模式传 '' 走 strict local 分支)。 */
19
19
  export declare function missingParentDelivery(state: BoardState, parentIds: string[], kbUrlBase?: string): MissingParentDelivery[];
@@ -1,6 +1,6 @@
1
1
  import { isAllowedWikiPagePath, isLocalKbPagePath, KB_PAGE_NAMESPACES_HINT } from '../wiki/page-path.js';
2
2
  /**
3
- * 上游交付契约(R20「上游对下游负责」宗旨):每阶段任务完成交接 metadata 必须产出的键。
3
+ * 上游交付契约(「上游对下游负责」宗旨):每阶段任务完成交接 metadata 必须产出的键。
4
4
  * key = `${assignee}:${mode}`;仅列出「下游实际读取」的硬交付物——
5
5
  * - w:kb(W2/W3):kb_url + page_path = KB 同步页(下游 wiki_read 读原文 / 收尾)
6
6
  * - p:openspec(P):artifacts_path = openspec 实施计划产物路径(W2 读取同步 KB);
@@ -32,7 +32,7 @@ export function missingPtDecisionKeys(handoff) {
32
32
  }
33
33
  /** 缺失的交付键(存在但为空的字符串/非字符串均视为缺失;pt_decision 走结构校验透传细粒度键)。
34
34
  * 可选 kbUrlBase:提供时对 w:kb 的 kb_url 做 host 前缀硬校验(防 LLM 手写错域名)、对 page_path 做
35
- * 命名空间格式校验(Q3&5:防 LLM 自造路径/拼错层级)——未提供则仅非空校验(兼容旧调用/测试)。 */
35
+ * 命名空间格式校验(防 LLM 自造路径/拼错层级)——未提供则仅非空校验(兼容旧调用/测试)。 */
36
36
  export function missingDeliveryKeys(assignee, mode, handoff, kbUrlBase) {
37
37
  const keys = requiredDeliveryKeys(assignee, mode);
38
38
  if (keys.length === 0)
@@ -40,7 +40,7 @@ export function missingDeliveryKeys(assignee, mode, handoff, kbUrlBase) {
40
40
  if (!handoff)
41
41
  return keys.slice();
42
42
  const m = handoff.metadata ?? {};
43
- // 推导修正(审查 C1):kbUrlBase === undefined 才是宽松;'' 是 local strict
43
+ // 推导修正:kbUrlBase === undefined 才是宽松;'' 是 local strict
44
44
  const hasBase = kbUrlBase !== undefined;
45
45
  const base = hasBase ? kbUrlBase.replace(/\/$/, '') : null;
46
46
  const strict = base !== null;
@@ -52,7 +52,7 @@ export function missingDeliveryKeys(assignee, mode, handoff, kbUrlBase) {
52
52
  }
53
53
  const v = m[k];
54
54
  if (strict && k === 'kb_url' && base === '') {
55
- // local 模式(D5):kb_url 必须显式空串(非缺失键)
55
+ // local 模式:kb_url 必须显式空串(非缺失键)
56
56
  if (typeof v !== 'string' || v.trim() !== '')
57
57
  missing.push(`${k} (本地模式 kb_url 必须为空串)`);
58
58
  continue;
@@ -77,7 +77,7 @@ export function hasRequiredDelivery(assignee, mode, handoff) {
77
77
  return missingDeliveryKeys(assignee, mode, handoff).length === 0;
78
78
  }
79
79
  /** 对一组父任务 id 做交付契约校验,返回缺关键交付物的父卡清单(无缺失返回空数组)。
80
- * kbUrlBase 可选透传 missingDeliveryKeys(与 Task 4 的 C1 推导修正配套——local 模式传 '' 走 strict local 分支)。 */
80
+ * kbUrlBase 可选透传 missingDeliveryKeys(与上方 kbUrlBase 推导修正配套——local 模式传 '' 走 strict local 分支)。 */
81
81
  export function missingParentDelivery(state, parentIds, kbUrlBase) {
82
82
  const out = [];
83
83
  for (const pid of parentIds) {
@@ -1,6 +1,6 @@
1
1
  import type { Handoff } from './types.js';
2
2
  /**
3
- * R20 D(execute) 交付物证据判定(C1/C2 共用):
3
+ * D(execute) 交付物证据判定:
4
4
  * 完成交接 metadata 必须带 changed_files(非空)+ (commit_hash | push) 至少其一,
5
5
  * 才算具备"实际执行并产出 git 产物"的证据。无证据 → 不判链完成 / kanban_complete 拒绝。
6
6
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * R20 D(execute) 交付物证据判定(C1/C2 共用):
2
+ * D(execute) 交付物证据判定:
3
3
  * 完成交接 metadata 必须带 changed_files(非空)+ (commit_hash | push) 至少其一,
4
4
  * 才算具备"实际执行并产出 git 产物"的证据。无证据 → 不判链完成 / kanban_complete 拒绝。
5
5
  *
@@ -18,7 +18,7 @@ export class FileEventStore {
18
18
  }
19
19
  }
20
20
  async append(ev) {
21
- // P1-8:seq 每次从文件尾行重读分配(跨进程安全,多 dsh 实例并发追加时 seq 唯一);
21
+ // seq 每次从文件尾行重读分配(跨进程安全,多 dsh 实例并发追加时 seq 唯一);
22
22
  // appendFileSync 单行 JSONL 追加在 POSIX 上原子(行 < 4KB,无锁无残留)。
23
23
  const all = this.readAllSync();
24
24
  const seq = all.length > 0 ? all[all.length - 1].seq + 1 : 0;
@@ -0,0 +1,15 @@
1
+ import type { BoardState, KanbanEvent, Task } from './types.js';
2
+ /** W3 收尾判据(镜像 kanban-service.completeTask 机械规则,但不含 dEvidenceOk 与链状态要求):
3
+ * 完成卡为 w+kb、链上无未终态卡、它是最后完成的卡、且链上存在 done 的 D 卡(防 W2 中间态误投)。 */
4
+ export declare function isFinalW3Completion(state: BoardState, chainId: string, completedTaskId: string): boolean;
5
+ /** 成功汇报(markdown)。判据不满足(W2 中间态/非 w:kb/非收尾)返回 null。 */
6
+ export declare function buildCompletionMessage(state: BoardState, chainId: string, completedTaskId: string, now: number): string | null;
7
+ export declare function suggestionsFor(reason: string): string[];
8
+ /** 链下全部 blocked 卡(title + 最近一次 task/blocked 原因)。 */
9
+ export declare function latestBlockedTasks(events: KanbanEvent[], tasks: Iterable<Task>, chainId: string): Array<{
10
+ id: string;
11
+ title: string;
12
+ reason: string;
13
+ }>;
14
+ /** 阻塞通知(markdown)。 */
15
+ export declare function buildBlockMessage(state: BoardState, chainId: string, chainReason: string, storageDir: string): string;