@joekytc/dsh-swarm 0.2.0 → 0.3.1

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 (105) hide show
  1. package/README.md +177 -139
  2. package/README.zh-CN.md +173 -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 +20 -1
  11. package/lib/config.js +15 -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.d.ts +6 -1
  44. package/lib/domain/memory.js +25 -8
  45. package/lib/domain/ocr-review.d.ts +47 -0
  46. package/lib/domain/ocr-review.js +105 -0
  47. package/lib/domain/permissions.js +5 -5
  48. package/lib/domain/projection.js +6 -6
  49. package/lib/domain/state-machine.js +1 -1
  50. package/lib/domain/task-parents.js +1 -1
  51. package/lib/domain/types.d.ts +4 -4
  52. package/lib/index.js +14 -9
  53. package/lib/roles/clean-fs-tools.js +3 -4
  54. package/lib/roles/preset-installer.js +3 -3
  55. package/lib/roles/skill-installer.js +1 -1
  56. package/lib/roles/toolsets.d.ts +60 -16
  57. package/lib/roles/toolsets.js +265 -47
  58. package/lib/roles/wiki-worker.js +3 -3
  59. package/lib/routes/kanban-http.d.ts +25 -2
  60. package/lib/routes/kanban-http.js +171 -11
  61. package/lib/routes/kanban-sse.d.ts +1 -1
  62. package/lib/routes/kanban-sse.js +1 -1
  63. package/lib/routes/planning-driver.d.ts +4 -2
  64. package/lib/routes/planning-driver.js +7 -3
  65. package/lib/routes/prefix-router.d.ts +5 -3
  66. package/lib/routes/prefix-router.js +14 -5
  67. package/lib/services/config-provider.d.ts +0 -4
  68. package/lib/services/config-provider.js +7 -9
  69. package/lib/services/im-delivery.d.ts +109 -0
  70. package/lib/services/im-delivery.js +301 -0
  71. package/lib/services/kanban-provider.d.ts +1 -1
  72. package/lib/services/kanban-provider.js +2 -2
  73. package/lib/services/ocr-cli.d.ts +35 -0
  74. package/lib/services/ocr-cli.js +128 -0
  75. package/lib/tools/kanban-tools.d.ts +2 -2
  76. package/lib/tools/kanban-tools.js +1 -1
  77. package/lib/tools/main-session-tools.d.ts +7 -3
  78. package/lib/tools/main-session-tools.js +120 -21
  79. package/lib/tools/ocr-review-tools.d.ts +11 -0
  80. package/lib/tools/ocr-review-tools.js +63 -0
  81. package/lib/tools/planning-tools.d.ts +5 -2
  82. package/lib/tools/planning-tools.js +19 -11
  83. package/lib/tools/wiki-tools.js +2 -2
  84. package/lib/wiki/kb-linkage.d.ts +1 -1
  85. package/lib/wiki/local-kb-client.js +3 -3
  86. package/lib/wiki/local-kb.d.ts +1 -1
  87. package/lib/wiki/local-kb.js +2 -2
  88. package/lib/wiki/page-path.d.ts +1 -1
  89. package/lib/wiki/page-path.js +6 -3
  90. package/lib/wiki/wiki-vault-client.d.ts +1 -1
  91. package/lib/wiki/wiki-vault-client.js +1 -1
  92. package/package.json +1 -1
  93. package/personas/kanban-d/agent.cordis.yml +33 -12
  94. package/personas/kanban-dt/agent.cordis.yml +23 -5
  95. package/personas/kanban-p/agent.cordis.yml +3 -3
  96. package/personas/kanban-pt/agent.cordis.yml +1 -1
  97. package/personas/kanban-v/agent.cordis.yml +3 -3
  98. package/personas/kanban-w/agent.cordis.yml +21 -11
  99. package/personas/persona-d.md +8 -6
  100. package/personas/persona-dt.md +12 -4
  101. package/personas/persona-p.md +1 -1
  102. package/personas/persona-v.md +2 -2
  103. package/personas/persona-w.md +2 -2
  104. package/personas/swarm/agent.cordis.yml +44 -0
  105. package/personas/swarm/preset.yml +5 -0
@@ -10,7 +10,7 @@ import { AgentRunner } from './agent-runner.js';
10
10
  import { Watchdog } from './watchdog.js';
11
11
  import { ChainAuditor } from './chain-auditor.js';
12
12
  import { mergeDAfterReview } from './merge-gate.js';
13
- import { buildSubagentTreeGuard } from '../roles/toolsets.js';
13
+ import { buildSubagentTreeGuard, buildSwarmSessionGuard, buildStandaloneDtGuard, registerStandaloneReviewerTools } from '../roles/toolsets.js';
14
14
  import { syncKbLinks } from '../wiki/kb-linkage.js';
15
15
  /** 读取部署默认模型(settings 的 agent-default-model 优先,其次 dsh-agent-default-model),角色未单独配置时回退使用。 */
16
16
  export function resolveDefaultModel(ctx) {
@@ -41,7 +41,7 @@ function parentsDone(task, state) {
41
41
  return parent !== undefined && (parent.status === 'done' || parent.status === 'archived');
42
42
  });
43
43
  }
44
- /** B6:从状态文件恢复 lastSeq;无文件时回退到事件日志尾行(不重放旧事件重复唤醒 V)。 */
44
+ /** 从状态文件恢复 lastSeq;无文件时回退到事件日志尾行(不重放旧事件重复唤醒 V)。 */
45
45
  function loadLastSeq(stateFile) {
46
46
  try {
47
47
  const raw = JSON.parse(readFileSync(stateFile, 'utf8'));
@@ -57,7 +57,7 @@ function saveLastSeq(stateFile, lastSeq) {
57
57
  }
58
58
  catch { /* 忽略写失败:事件日志仍是事实源 */ }
59
59
  }
60
- /** 修复轮 6:把 [dsh-swarm] 关键事件追加到 storageDir/dispatcher.log,便于无控制台时观测调度器状态。 */
60
+ /** [dsh-swarm] 关键事件追加到 storageDir/dispatcher.log,便于无控制台时观测调度器状态。 */
61
61
  function logToFile(file, msg) {
62
62
  try {
63
63
  writeFileSync(file, new Date().toISOString() + ' ' + msg + '\n', { flag: 'a' });
@@ -79,22 +79,22 @@ export function makeWakeImpl(wakeV, logFile, onSettled) {
79
79
  onSettled();
80
80
  };
81
81
  }
82
- /** 修复轮 6:单次异步操作加超时护栏——一个挂起的 V 编排会话不得卡死整个调度器 tick。 */
82
+ /** 单次异步操作加超时护栏——一个挂起的 V 编排会话不得卡死整个调度器 tick。 */
83
83
  function withTimeout(p, ms, label) {
84
84
  return Promise.race([
85
85
  p,
86
86
  new Promise((_, reject) => setTimeout(() => reject(new Error('timeout: ' + label)), ms)),
87
87
  ]);
88
88
  }
89
- /** 防线①:链级进度看门狗阈值。tick=2000ms × 45 ticks = 90s 无进展即重唤醒(grill Q2 决议)。 */
89
+ /** 防线①:链级进度看门狗阈值。tick=2000ms × 45 ticks = 90s 无进展即重唤醒。 */
90
90
  export const STALL_WATCHDOG_TICKS = 45;
91
- /** 防线①:链级重唤醒上限(grill Q3 决议:超限 [create-failed] + chain/blocked)。 */
91
+ /** 防线①:链级重唤醒上限(超限 [create-failed] + chain/blocked)。 */
92
92
  export const STALL_WATCHDOG_REWAKE_LIMIT = 3;
93
- /** 调度器:事件唤醒 V(R20 逐阶段建卡)+ 每任务一次性角色 agent + 心跳看门狗。
94
- * - B1:failed 且 attempts<maxRetries 的任务重派(claim→running,AgentRunner resume 同一会话);
93
+ /** 调度器:事件唤醒 V(逐阶段建卡)+ 每任务一次性角色 agent + 心跳看门狗。
94
+ * - failed 且 attempts<maxRetries 的任务重派(claim→running,AgentRunner resume 同一会话);
95
95
  * attempts≥maxRetries 熔断 blocked(gave_up)。
96
- * - B6:lastSeq 持久化,重启后仅唤醒 lastSeq 之后的事件。
97
- * - R5:inFlight 互斥,防止慢 tick 与下一轮并发重复派发同一任务。 */
96
+ * - lastSeq 持久化,重启后仅唤醒 lastSeq 之后的事件。
97
+ * - inFlight 互斥,防止慢 tick 与下一轮并发重复派发同一任务。 */
98
98
  export class Dispatcher {
99
99
  kanban;
100
100
  runner;
@@ -103,10 +103,10 @@ export class Dispatcher {
103
103
  maxRetries;
104
104
  stateFile;
105
105
  logFile;
106
- agents; // Fix round 1:宿主 agents 注册表(热重载豁免判据)
106
+ agents; // 宿主 agents 注册表(热重载豁免判据)
107
107
  stallProbe;
108
108
  lastSeq = null; // null=尚未加载(首轮 tick 从状态文件/事件日志尾行恢复)
109
- orphanReconciled = false; // 启动 reconcile(G)一次性闸:仅首轮 tick 执行孤儿收敛
109
+ orphanReconciled = false; // 启动 reconcile 一次性闸:仅首轮 tick 执行孤儿收敛
110
110
  inFlight = false;
111
111
  stallState = new Map();
112
112
  timer = null;
@@ -124,14 +124,14 @@ export class Dispatcher {
124
124
  async ensureLastSeq(state) {
125
125
  if (this.lastSeq !== null)
126
126
  return;
127
- // 修复轮 6:无状态文件(首次启动)时从 -1 起处理全部事件,避免跳过已存在的
128
- // chain/created / spec-card/approved 等可唤醒事件导致 V 永不建卡(B6 回归)。
129
- // 重放安全:VOrchestrator.wakeV 的 B6 幂等(已有匹配卡则跳过)保证不重复建卡。
127
+ // 无状态文件(首次启动)时从 -1 起处理全部事件,避免跳过已存在的
128
+ // chain/created / spec-card/approved 等可唤醒事件导致 V 永不建卡。
129
+ // 重放安全:VOrchestrator.wakeV 的幂等(已有匹配卡则跳过)保证不重复建卡。
130
130
  let loaded = loadLastSeq(this.stateFile) ?? -1;
131
131
  // 游标自愈:purge/renumber(整链硬删除物理重排 events.jsonl seq)或外部修复会把
132
132
  // 新事件 seq 压到旧游标之下,若不钳回,spec-card/approved 等可唤醒事件将被永久
133
133
  // 跳过 → V 永不建卡(2026-09-02 实测事故)。游标超前即视为状态文件失效,回退
134
- // 全量重放;重复唤醒由 wakeV B6 幂等与 done 卡不可变兜底。
134
+ // 全量重放;重复唤醒由 wakeV 幂等与 done 卡不可变兜底。
135
135
  const maxSeq = state.events.length > 0 ? state.events[state.events.length - 1].seq : -1;
136
136
  if (loaded > maxSeq) {
137
137
  logToFile(this.logFile, '[tick] lastSeq=' + loaded + ' > maxSeq=' + maxSeq + ' (post-purge cursor skew) → rewind to -1 and replay');
@@ -142,12 +142,12 @@ export class Dispatcher {
142
142
  }
143
143
  async tick() {
144
144
  if (this.inFlight)
145
- return; // R5:防重叠 tick 并发派发同一任务
145
+ return; // 防重叠 tick 并发派发同一任务
146
146
  this.inFlight = true;
147
147
  try {
148
148
  const state = await this.kanban.snapshot();
149
149
  await this.ensureLastSeq(state);
150
- // 启动 reconcile(G):仅首轮执行,位置在游标自愈之后、正常事件消费之前——
150
+ // 启动 reconcile:仅首轮执行,位置在游标自愈之后、正常事件消费之前——
151
151
  // 游标 rewind 可能全量重放旧事件,必须先把上次进程遗留的 running 孤儿卡收敛为 blocked,
152
152
  // 消除「进程重启 → whenIdle 协程死亡 → 卡 running 悬挂到看门狗 4h」的口子。
153
153
  if (!this.orphanReconciled) {
@@ -176,11 +176,11 @@ export class Dispatcher {
176
176
  await this.runner.runTask(t.id);
177
177
  }
178
178
  else if (t.status === 'failed' && t.attempts < this.maxRetries) {
179
- // B1:failed 重派——AgentRunner 内 claim→running + resume 同一会话
179
+ // failed 重派——AgentRunner 内 claim→running + resume 同一会话
180
180
  await this.runner.runTask(t.id);
181
181
  }
182
182
  else if (t.status === 'failed') {
183
- // B1:attempts≥maxRetries 熔断 blocked(gave_up),人工介入
183
+ // attempts≥maxRetries 熔断 blocked(gave_up),人工介入
184
184
  await this.kanban.blockTask(t.id, 'gave_up: max retries', 'system');
185
185
  }
186
186
  }
@@ -282,8 +282,8 @@ export class Dispatcher {
282
282
  this.stop();
283
283
  this.timer = setInterval(() => { void this.tick(); }, intervalMs);
284
284
  }
285
- /** 整链硬删除联动(E):purge 物理重排 events.jsonl seq,游标必须同步钳到当前 maxSeq。
286
- * 否则删链后新建链的可唤醒事件(seq < 旧内存游标)被运行中实例永久跳过——A1 仅在启动时自愈,
285
+ /** 整链硬删除联动:purge 物理重排 events.jsonl seq,游标必须同步钳到当前 maxSeq。
286
+ * 否则删链后新建链的可唤醒事件(seq < 旧内存游标)被运行中实例永久跳过——启动时自愈,
287
287
  * 覆盖不了运行中删链场景(2026-09-02 残留审计结论)。 */
288
288
  async onPurge() {
289
289
  const state = await this.kanban.snapshot();
@@ -299,10 +299,10 @@ export class Dispatcher {
299
299
  }
300
300
  }
301
301
  }
302
- /** 调度层装配:事件唤醒 V(R20 逐阶段建卡)+ 每任务一次性角色 agent + 心跳看门狗。
302
+ /** 调度层装配:事件唤醒 V(逐阶段建卡)+ 每任务一次性角色 agent + 心跳看门狗。
303
303
  * 仅在 agents 与 kanban 服务同时可用时由插件入口调用(不依赖可能已错过的 ready 事件)。
304
- * Task 7:收 ConfigProvider——storageDir 取启动时快照;wiki/模型链经 getEffective() 调用时热读取。 */
305
- /** 启动 reconcile(F):剔除事件流中已不存在的链的编排 entry(历史残留/外部 purge)。
304
+ * ConfigProvider——storageDir 取启动时快照;wiki/模型链经 getEffective() 调用时热读取。 */
305
+ /** 启动 reconcile:剔除事件流中已不存在的链的编排 entry(历史残留/外部 purge)。
306
306
  * 原地删除并返回被移除的 chainId 列表(调用方负责持久化与日志)。 */
307
307
  export function reconcileOrchestrations(orch, chains) {
308
308
  const removed = [...orch.keys()].filter((k) => !chains.has(k));
@@ -310,14 +310,14 @@ export function reconcileOrchestrations(orch, chains) {
310
310
  orch.delete(k);
311
311
  return removed;
312
312
  }
313
- /** 启动 reconcile(G):进程重启会杀死 runner 的 whenIdle 协程,上次遗留的 running 卡无人收尾,
313
+ /** 启动 reconcile:进程重启会杀死 runner 的 whenIdle 协程,上次遗留的 running 卡无人收尾,
314
314
  * 看门狗默认 4h(staleTimeoutSeconds=14400)才回收——重启后立即把 running 孤儿卡收敛为 blocked
315
315
  * (system comment + blockTask),中断显形且可重派续跑(重派将 resume 同一会话,进度保留)。
316
316
  * 状态机注:TaskStatus 无独立 'claimed' 态——claimTask 发 task/claimed 事件后投影即为 running,
317
317
  * 扫描 running 即覆盖「claimed 未收尾」;todo/ready/triage 从未派发,done/blocked/failed/archived
318
- * 已有归属或终态(且 failed 的处置归 B1 重派/熔断管辖),均不动。
319
- * Fix round 1(热重载兼容,双重判据):收敛每张 running 卡前先查宿主 agents 注册表
320
- * ctx.get('agents').get('kbn-<taskId>')(session id 构造同 AgentRunner.resumeOrReuse,Task 2 同款探明)。
318
+ * 已有归属或终态(且 failed 的处置归重派/熔断管辖),均不动。
319
+ * 热重载兼容,双重判据:收敛每张 running 卡前先查宿主 agents 注册表
320
+ * ctx.get('agents').get('kbn-<taskId>')(session id 构造同 AgentRunner.resumeOrReuse)。
321
321
  * 两个世界的分野:
322
322
  * - 进程重启:agents 注册表随宿主进程消亡,新进程内 kbn-<taskId> 必然查不到(undefined)
323
323
  * → 会话已死 → 全部收敛,语义与修复前一致;
@@ -382,12 +382,12 @@ export function startDispatcher(ctx, configProvider) {
382
382
  }
383
383
  /** 调度器装配主体(startDispatcher 的容错包裹内执行,异常落盘不阻断插件加载)。
384
384
  * config = 启动时快照,仅喂静态依赖(EventWaker/Watchdog/maxRetries);
385
- * wiki 与模型链读点走 configProvider.getEffective() 热生效(Task 7)。 */
385
+ * wiki 与模型链读点走 configProvider.getEffective() 热生效。 */
386
386
  function startDispatcherInner(ctx, configProvider, storageDir, logFile, provider, agents) {
387
387
  const config = configProvider.getEffective();
388
388
  const kanban = provider.service;
389
- // D2 双模式:local 走 LocalWikiClient(互链读写落本地库),remote 用配置的 WikiVaultClient(Task 10 同款分支)。
390
- // 消费方(AgentRunner/VOrchestrator)字段仍标 WikiVaultClient——local 为 LocalWikiClient(read/write/search 同面),Task 10 同款断言。
389
+ // 双模式:local 走 LocalWikiClient(互链读写落本地库),remote 用配置的 WikiVaultClient(同款分支)。
390
+ // 消费方(AgentRunner/VOrchestrator)字段仍标 WikiVaultClient——local 为 LocalWikiClient(read/write/search 同面),同款断言。
391
391
  const wiki = (configProvider.mode === 'local'
392
392
  ? new LocalWikiClient(ensureLocalKbRoot())
393
393
  : new WikiVaultClient(() => configProvider.getEffective().wikiVault));
@@ -408,7 +408,7 @@ function startDispatcherInner(ctx, configProvider, storageDir, logFile, provider
408
408
  catch { /* 忽略写失败 */ }
409
409
  };
410
410
  const vOrch = new VOrchestrator(ctx, kanban, agents, configProvider, orchestrations, wiki, defaultModel);
411
- // D23:链完成验收核对(重)——Chain(completed) 时核对主会话是否越权写工作区产物;
411
+ // 链完成验收核对——Chain(completed) 时核对主会话是否越权写工作区产物;
412
412
  // 发现越权 → chain/audit-warning,阻塞最终汇报直至用户 GUI 确认(chain/audit-confirmed)。
413
413
  const auditor = new ChainAuditor({
414
414
  kanban,
@@ -416,7 +416,7 @@ function startDispatcherInner(ctx, configProvider, storageDir, logFile, provider
416
416
  listLiveAgents: () => (ctx.get('agents')?.list?.() ?? []),
417
417
  });
418
418
  kanban.setOnChainCompleted(async (chainId) => {
419
- // 修复轮 7:传入本链发起工作区(Chain.workspaceDir),审计仅扫描该工作区内的会话,排除其他项目主会话
419
+ // 传入本链发起工作区(Chain.workspaceDir),审计仅扫描该工作区内的会话,排除其他项目主会话
420
420
  const chainState = await kanban.snapshot();
421
421
  const workspaceDir = chainState.chains.get(chainId)?.workspaceDir ?? null;
422
422
  const evidence = await auditor.check(chainId, workspaceDir);
@@ -424,7 +424,7 @@ function startDispatcherInner(ctx, configProvider, storageDir, logFile, provider
424
424
  console.warn('[dsh-swarm] chain audit warning: ' + chainId + ' evidence=' + evidence.length);
425
425
  await kanban.auditWarning(chainId, evidence, 'system');
426
426
  }
427
- // 合入门控(architecture-review 建议1):DT 通过后由 system 合入 TARGET_BRANCH;D 不再提前 merge/push。
427
+ // 合入门控:DT 通过后由 system 合入 TARGET_BRANCH;D 不再提前 merge/push。
428
428
  // 解析失败软跳过([merge-skip]),合入失败记录 [merge-failed],均不阻断收尾(坏代码未被合入 = 方向安全)。
429
429
  try {
430
430
  const r = await mergeDAfterReview(kanban, chainId, storageDir);
@@ -436,7 +436,7 @@ function startDispatcherInner(ctx, configProvider, storageDir, logFile, provider
436
436
  logToFile(logFile, '[merge-gate] error chain=' + chainId + ' ' + String(err));
437
437
  }
438
438
  });
439
- // Q3&5:W2/W3(w:kb) 完成 → 机械互链登记(清单页 ↔ 计划页 ↔ 结果页)。
439
+ // W2/W3(w:kb) 完成 → 机械互链登记(清单页 ↔ 计划页 ↔ 结果页)。
440
440
  // syncKbLinks 内部对 wiki 读写全容错,失败不阻塞完成;钩子本身再包一层 try(防御未来改动抛错)。
441
441
  kanban.setOnTaskCompleted(async (taskId) => {
442
442
  try {
@@ -448,9 +448,9 @@ function startDispatcherInner(ctx, configProvider, storageDir, logFile, provider
448
448
  }
449
449
  });
450
450
  const waker = new EventWaker(ctx, config);
451
- vOrch.onOrchChange = saveOrchs; // Fix D:stall re-wake 路径绕过 EventWaker,orch 变化自行落盘
451
+ vOrch.onOrchChange = saveOrchs; // stall re-wake 路径绕过 EventWaker,orch 变化自行落盘
452
452
  waker.setWakeImpl(makeWakeImpl(vOrch.wakeV.bind(vOrch), logFile, saveOrchs));
453
- // 0.1.0 delegation(spec FR2):全局子代理写护栏——普通插件 ctx 上注册的 guard 全局
453
+ // 0.1.0 delegation:全局子代理写护栏——普通插件 ctx 上注册的 guard 全局
454
454
  // 生效(dsh-tools:普通上下文守卫全局生效,agent.ctx 守卫仅对该 agent 生效)。
455
455
  // guard 内部仅对 kanban-dt 系会话收紧;DT 父会话自身仍由 agent-runner 的 agent.ctx
456
456
  // guard 双保险。dispose 时注销。
@@ -459,8 +459,24 @@ function startDispatcherInner(ctx, configProvider, storageDir, logFile, provider
459
459
  if (unguardSubagents) {
460
460
  ctx.on('dispose', unguardSubagents);
461
461
  }
462
+ // 蜂群模式硬闸:header.agentPreset==='swarm' 时只读+git 反选拦截;
463
+ // 内部对其余会话恒放行。dispose 时注销(与 subagent guard 同款)。
464
+ const unguardSwarm = toolsSvc?.guard?.(buildSwarmSessionGuard());
465
+ if (unguardSwarm) {
466
+ ctx.on('dispose', unguardSwarm);
467
+ }
468
+ // 独立评审硬闸:header.agentPreset==='kanban-dt' 且未经角色组合(session id 非 kbn-
469
+ // 前缀)时收紧(看板写工具拒、bash/run_code 仅只读 git+clone/fetch、wiki_write 仅
470
+ // reviews 命名空间);wiki_write 对非评审会话全局收紧(链上组合会话放行)。dispose
471
+ // 时注销(与 subagent/swarm guard 同款)。
472
+ const unguardStandalone = toolsSvc?.guard?.(buildStandaloneDtGuard());
473
+ if (unguardStandalone) {
474
+ ctx.on('dispose', unguardStandalone);
475
+ }
476
+ // 独立评审工具全局注册:ocr_review + wiki 三原语(wiki_write 会话级收紧由上 guard 完成)。
477
+ registerStandaloneReviewerTools(ctx, configProvider);
462
478
  const runner = new AgentRunner(ctx, kanban, configProvider, wiki, defaultModel);
463
- provider.runner = runner; // T32 fix:HTTP retry 复用同一执行器(failed→claim→spawn/resume)
479
+ provider.runner = runner; // HTTP retry 复用同一执行器(failed→claim→spawn/resume)
464
480
  const watchdog = new Watchdog(kanban, config.dispatcher);
465
481
  const dispatcher = new Dispatcher({
466
482
  kanban,
@@ -468,9 +484,9 @@ function startDispatcherInner(ctx, configProvider, storageDir, logFile, provider
468
484
  waker,
469
485
  watchdog,
470
486
  maxRetries: config.dispatcher.maxRetries,
471
- stateFile: join(dirname(orchFile), 'dispatcher-state.json'), // 与事件日志同目录(B6)
487
+ stateFile: join(dirname(orchFile), 'dispatcher-state.json'), // 与事件日志同目录
472
488
  logFile,
473
- agents, // Fix round 1:启动 reconcile 热重载豁免判据(宿主 agents 注册表)
489
+ agents, // 启动 reconcile 热重载豁免判据(宿主 agents 注册表)
474
490
  stallProbe: vOrch, // 防线①:链级停滞探针(orchestrationOf/isWakeInFlight/wake)
475
491
  });
476
492
  ctx.on('dispose', () => { dispatcher.stop(); watchdog.stop(); vOrch.dispose(); });
@@ -478,7 +494,7 @@ function startDispatcherInner(ctx, configProvider, storageDir, logFile, provider
478
494
  watchdog.start(config.dispatcher.heartbeatIntervalSeconds * 1000);
479
495
  logToFile(logFile, '[startDispatcher] dispatcher started (tick=2000ms)');
480
496
  void dispatcher.tick();
481
- // 整链硬删除联动(E/F/G-min):purge 物理重排事件 seq → 游标同步钳回(运行中实例不重启发跳过);
497
+ // 整链硬删除联动:purge 物理重排事件 seq → 游标同步钳回(运行中实例不重启发跳过);
482
498
  // V 编排 entry 同步剔除。kanban-http 的 delete 分支调用 provider.onChainDeleted。
483
499
  provider.onChainDeleted = async (chainId) => {
484
500
  await dispatcher.onPurge();
@@ -486,7 +502,7 @@ function startDispatcherInner(ctx, configProvider, storageDir, logFile, provider
486
502
  saveOrchs();
487
503
  logToFile(logFile, '[chain-deleted] cursor synced + orch entry removed chain=' + chainId);
488
504
  };
489
- // 启动 reconcile(F):历史残留/外部 purge 留下的死链编排 entry,按事件流存活链剔除
505
+ // 启动 reconcile:历史残留/外部 purge 留下的死链编排 entry,按事件流存活链剔除
490
506
  void (async () => {
491
507
  try {
492
508
  const snap = await kanban.snapshot();
@@ -6,7 +6,7 @@ export declare class EventWaker {
6
6
  private inFlight;
7
7
  private wakeImpl;
8
8
  constructor(_ctx: unknown, _config: KanbanConfig);
9
- /** 注入真实唤醒实现(T11.5:VOrchestrator.wakeV——创建/恢复 V 编排 agent 会话,按 R20 阶段序列建卡)。 */
9
+ /** 注入真实唤醒实现(VOrchestrator.wakeV——创建/恢复 V 编排 agent 会话,按阶段序列建卡)。 */
10
10
  setWakeImpl(impl: WakeImpl): void;
11
11
  onEvent(ev: KanbanEvent): Promise<void>;
12
12
  }
@@ -3,10 +3,10 @@ export class EventWaker {
3
3
  inFlight = new Set();
4
4
  wakeImpl = async () => { };
5
5
  constructor(_ctx, _config) { }
6
- /** 注入真实唤醒实现(T11.5:VOrchestrator.wakeV——创建/恢复 V 编排 agent 会话,按 R20 阶段序列建卡)。 */
6
+ /** 注入真实唤醒实现(VOrchestrator.wakeV——创建/恢复 V 编排 agent 会话,按阶段序列建卡)。 */
7
7
  setWakeImpl(impl) { this.wakeImpl = impl; }
8
8
  async onEvent(ev) {
9
- // v2:V 仅在规格卡批准后(spec-card/approved)或任务完成/阻塞(驱动阶段推进)时唤醒;
9
+ // V 仅在规格卡批准后(spec-card/approved)或任务完成/阻塞(驱动阶段推进)时唤醒;
10
10
  // chain/created 不再唤醒 V(V 不再处理旧 w1 预取阶段,等 /openspec: 建卡 + 批准后再行动)。
11
11
  const wakeable = (ev.kind === 'task/completed' || ev.kind === 'task/blocked' || ev.kind === 'spec-card/approved');
12
12
  if (!wakeable)
@@ -1,5 +1,5 @@
1
1
  /**
2
- * R20 D(execute) git 凭据注入(M4):使 D 会话的 git ls-remote/push 可用。
2
+ * D(execute) git 凭据注入:使 D 会话的 git ls-remote/push 可用。
3
3
  *
4
4
  * 机制:D 会话的 bash 每次调用都是新 shell(无环境持久),故 env 变量不可跨调用。
5
5
  * 采用 repo-local http extraheader(AUTHORIZATION: basic),写入 <repo>/.git/config——
@@ -8,7 +8,7 @@
8
8
  * 鉴权按托管方区分:
9
9
  * - GitLab PAT(glpat- 前缀)→ basic base64("oauth2:<PAT>")(GitLab 规范,实测 ls-remote 通过);
10
10
  * - 其余(GitHub 等)→ basic base64("x-access-token:<PAT>")。
11
- * D 会话为 danger-full-access(用户决策 Q4),HOME 可写,故 git 的 credential.helper
11
+ * D 会话为 danger-full-access(用户决策),HOME 可写,故 git 的 credential.helper
12
12
  * (store/osxkeychain 等)不再触发 "credential storage lock",无需中和。
13
13
  *
14
14
  * PAT 来源(按优先级):DSH 凭据服务 ctx.credentials($DSH_HOME/.credentials.yaml 的
@@ -1,7 +1,7 @@
1
1
  // src/dispatcher/git-credentials.ts
2
2
  import { execFileSync } from 'node:child_process';
3
3
  /**
4
- * R20 D(execute) git 凭据注入(M4):使 D 会话的 git ls-remote/push 可用。
4
+ * D(execute) git 凭据注入:使 D 会话的 git ls-remote/push 可用。
5
5
  *
6
6
  * 机制:D 会话的 bash 每次调用都是新 shell(无环境持久),故 env 变量不可跨调用。
7
7
  * 采用 repo-local http extraheader(AUTHORIZATION: basic),写入 <repo>/.git/config——
@@ -10,7 +10,7 @@ import { execFileSync } from 'node:child_process';
10
10
  * 鉴权按托管方区分:
11
11
  * - GitLab PAT(glpat- 前缀)→ basic base64("oauth2:<PAT>")(GitLab 规范,实测 ls-remote 通过);
12
12
  * - 其余(GitHub 等)→ basic base64("x-access-token:<PAT>")。
13
- * D 会话为 danger-full-access(用户决策 Q4),HOME 可写,故 git 的 credential.helper
13
+ * D 会话为 danger-full-access(用户决策),HOME 可写,故 git 的 credential.helper
14
14
  * (store/osxkeychain 等)不再触发 "credential storage lock",无需中和。
15
15
  *
16
16
  * PAT 来源(按优先级):DSH 凭据服务 ctx.credentials($DSH_HOME/.credentials.yaml 的
@@ -2,7 +2,7 @@ import type { KanbanConfig } from '../config.js';
2
2
  import type { Role } from '../domain/types.js';
3
3
  import type { AgentModelOptions } from './dispatcher.js';
4
4
  /**
5
- * 模型候选链(交付质量链 Task 12):角色创建时主模型 → fallbacks 依次静默切换。
5
+ * 模型候选链:角色创建时主模型 → fallbacks 依次静默切换。
6
6
  * primary = config.roles.models[role];未配置回退 defaultModel。
7
7
  * reasoningEffort 未指定一律 'high'。
8
8
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * 模型候选链(交付质量链 Task 12):角色创建时主模型 → fallbacks 依次静默切换。
2
+ * 模型候选链:角色创建时主模型 → fallbacks 依次静默切换。
3
3
  * primary = config.roles.models[role];未配置回退 defaultModel。
4
4
  * reasoningEffort 未指定一律 'high'。
5
5
  *
@@ -1,5 +1,5 @@
1
1
  /**
2
- * 会话事件条目统一读取助手(修复轮 6,举一反三):
2
+ * 会话事件条目统一读取助手:
3
3
  * 0.1.2(DSH-0.1.2-A4-03)起 Session.events getter 已移除——宿主新读取面是
4
4
  * `session.seq`(日志长度)+ `session.snapshotEvents(fromSeq?, toSeqExclusive?)`。
5
5
  * 事件条目形态(落盘与快照一致)为 { type, seq, time, data: { ... } }(append 时
@@ -1,6 +1,6 @@
1
1
  // src/dispatcher/session-events.ts
2
2
  /**
3
- * 会话事件条目统一读取助手(修复轮 6,举一反三):
3
+ * 会话事件条目统一读取助手:
4
4
  * 0.1.2(DSH-0.1.2-A4-03)起 Session.events getter 已移除——宿主新读取面是
5
5
  * `session.seq`(日志长度)+ `session.snapshotEvents(fromSeq?, toSeqExclusive?)`。
6
6
  * 事件条目形态(落盘与快照一致)为 { type, seq, time, data: { ... } }(append 时
@@ -1,6 +1,6 @@
1
1
  import type { BoardState, Task } from '../domain/types.js';
2
2
  /**
3
- * R20 D(execute) 目标仓库路径解析(B2/B3 共用):
3
+ * D(execute) 目标仓库路径解析:
4
4
  * 1. 任务 body 的 TARGET_REPO=<path> 标记(V 生成 D 任务体时写入,见 v-orchestrator D 阶段指令);
5
5
  * 2. 规格卡 file-prefetch 附件 ref(需求澄清清单 manifest 的仓库路径);
6
6
  * 3. 回退默认目录(kanban 存储 / 会话工作区)。
@@ -10,6 +10,6 @@ import type { BoardState, Task } from '../domain/types.js';
10
10
  * 存在性校验同时兜底沙箱根:workspace-write 以会话 cwd 为写边界,指向不存在的
11
11
  * 目录会导致所有写被拒,故不存在时回退而非保留。
12
12
  */
13
- /** 判断 child 是否位于 parent 目录内(含等于)。用于 D 目标仓库是否在会话工作空间内的判定(B 前置授权)。 */
13
+ /** 判断 child 是否位于 parent 目录内(含等于)。用于 D 目标仓库是否在会话工作空间内的判定(前置授权)。 */
14
14
  export declare function isPathInside(child: string, parent: string): boolean;
15
15
  export declare function resolveTargetRepoDir(task: Task, state: BoardState, fallback: string, allowFallback?: boolean): string;
@@ -2,7 +2,7 @@
2
2
  import { existsSync } from 'node:fs';
3
3
  import { resolve } from 'node:path';
4
4
  /**
5
- * R20 D(execute) 目标仓库路径解析(B2/B3 共用):
5
+ * D(execute) 目标仓库路径解析:
6
6
  * 1. 任务 body 的 TARGET_REPO=<path> 标记(V 生成 D 任务体时写入,见 v-orchestrator D 阶段指令);
7
7
  * 2. 规格卡 file-prefetch 附件 ref(需求澄清清单 manifest 的仓库路径);
8
8
  * 3. 回退默认目录(kanban 存储 / 会话工作区)。
@@ -12,7 +12,7 @@ import { resolve } from 'node:path';
12
12
  * 存在性校验同时兜底沙箱根:workspace-write 以会话 cwd 为写边界,指向不存在的
13
13
  * 目录会导致所有写被拒,故不存在时回退而非保留。
14
14
  */
15
- /** 判断 child 是否位于 parent 目录内(含等于)。用于 D 目标仓库是否在会话工作空间内的判定(B 前置授权)。 */
15
+ /** 判断 child 是否位于 parent 目录内(含等于)。用于 D 目标仓库是否在会话工作空间内的判定(前置授权)。 */
16
16
  export function isPathInside(child, parent) {
17
17
  const c = resolve(child);
18
18
  const p = resolve(parent);
@@ -37,6 +37,6 @@ export function resolveTargetRepoDir(task, state, fallback, allowFallback = true
37
37
  return abs;
38
38
  }
39
39
  // allowFallback=false(合入门控用):不降级到回退目录——合入必须落在显式声明的目标仓库,
40
- // 禁止 merge 到 kanban 存储/会话工作区等回退目录(M1,方向安全)。
40
+ // 禁止 merge 到 kanban 存储/会话工作区等回退目录(方向安全)。
41
41
  return allowFallback ? resolve(fallback) : '';
42
42
  }
@@ -18,11 +18,11 @@ export declare const R20_PHASE_EXPECT: Record<VPhase, {
18
18
  assignee: Role;
19
19
  mode: TaskMode;
20
20
  } | null>;
21
- /** M5:每阶段建卡的 body 生成指令(角色定位确定性模板,消除 V 自由发挥导致的角色漂移)。
21
+ /** 每阶段建卡的 body 生成指令(角色定位确定性模板,消除 V 自由发挥导致的角色漂移)。
22
22
  * P=计划者(绝不执行)、D=唯一执行者(TARGET_REPO 必须取自规格卡 file-prefetch 附件 ref,禁止回退/猜测)、
23
23
  * W=KB 同步(绝不执行代码)。V 把对应模板写入 kanban_create 的 body。 */
24
24
  export declare const PHASE_INSTRUCTIONS: Partial<Record<VPhase, string>>;
25
- /** D9(W 角色知识库双模式):按 kbMode 构建各阶段建卡 body 指令(phase 键控——W2/W3 同为
25
+ /** W 角色知识库双模式:按 kbMode 构建各阶段建卡 body 指令(phase 键控——W2/W3 同为
26
26
  * assignee='w'+mode='kb',assignee+mode 签名无法区分两相文案;系统状态本就按 phase 键控)。
27
27
  * remote → PHASE_INSTRUCTIONS 原文(护栏测试零改动前提);local → 覆盖 w2/w3/d/dt 四相
28
28
  * (skill 工具加载 llm-wiki + 本地库 fs 读写,禁 wiki_write/wiki_read),p/pt/summary 原样回落。
@@ -67,22 +67,22 @@ export declare class VOrchestrator {
67
67
  }>;
68
68
  }, configProvider: ConfigProvider, orchestrations: Map<string, ChainOrchestration>, wiki: WikiVaultClient, defaultModel?: AgentModelOptions);
69
69
  private currentPhase;
70
- /** Fix D:stall 自动再唤醒上限(同一阶段连续零产出 → 自动重试 ≤3 次,间隔递增)后放弃并显形。 */
70
+ /** stall 自动再唤醒上限(同一阶段连续零产出 → 自动重试 ≤3 次,间隔递增)后放弃并显形。 */
71
71
  private static readonly STALL_REWAKE_LIMIT;
72
- /** Fix D:stall 再唤醒基础延迟(按 stallCount 倍增:5s/10s/15s),给瞬时故障/采样波动恢复窗口。 */
72
+ /** stall 再唤醒基础延迟(按 stallCount 倍增:5s/10s/15s),给瞬时故障/采样波动恢复窗口。 */
73
73
  private static readonly STALL_REWAKE_DELAY_MS;
74
74
  private rewakeTimers;
75
75
  /** 同链 wakeV 并发防护:在途时后续唤醒合并为 pending,完成后补跑一次(事件不丢)。 */
76
76
  private waking;
77
77
  private pendingWake;
78
- /** Fix D:orchestration 变更回调(dispatcher 注入 saveOrchs)——stall re-wake 路径绕过
78
+ /** orchestration 变更回调(dispatcher 注入 saveOrchs)——stall re-wake 路径绕过
79
79
  * EventWaker,其 stallCount/phase 变化需自行落盘,防重启丢重试进度。 */
80
80
  onOrchChange?: () => void;
81
81
  wakeV(chainId: string): Promise<void>;
82
- /** Fix D:stall 自动再唤醒(≤3 次)。同链 pending 幂等;建卡成功(stallCount=0)后到期的
82
+ /** stall 自动再唤醒(≤3 次)。同链 pending 幂等;建卡成功(stallCount=0)后到期的
83
83
  * re-wake 自动作废(回调内按 stallCount 判空跳过)。 */
84
84
  private scheduleRewake;
85
- /** Fix D:清理待触发的 re-wake 定时器(插件 dispose 时调用)。 */
85
+ /** 清理待触发的 re-wake 定时器(插件 dispose 时调用)。 */
86
86
  dispose(): void;
87
87
  /** 链级看门狗(Dispatcher,防线①)探针:编排 entry;无 entry 返回 null。 */
88
88
  orchestrationOf(chainId: string): ChainOrchestration | null;
@@ -101,7 +101,7 @@ export declare class VOrchestrator {
101
101
  private handleReviewCompletion;
102
102
  private advance;
103
103
  private getVAgent;
104
- /** M2(Q5)+归组:链的 workspaceDir(发起 /plan: 的主 agent 工作空间);缺失返回 null(调用方询问/报错,不落 kanban 存储)。 */
104
+ /** 归组:链的 workspaceDir(发起 /plan: 的主 agent 工作空间);缺失返回 null(调用方询问/报错,不落 kanban 存储)。 */
105
105
  private chainWorkspace;
106
106
  }
107
107
  export {};