@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.
- package/README.md +176 -139
- package/README.zh-CN.md +172 -125
- package/client/ConfigSection.tsx +96 -5
- package/client/ConfigSelect.tsx +3 -1
- package/client/TaskDrawer.tsx +2 -0
- package/client/config-store.ts +48 -7
- package/client/kanban.css +69 -0
- package/client/timeline-model.ts +2 -0
- package/lib/client.js +172 -14
- package/lib/config.d.ts +18 -1
- package/lib/config.js +14 -0
- package/lib/dispatcher/agent-runner.d.ts +13 -5
- package/lib/dispatcher/agent-runner.js +78 -38
- package/lib/dispatcher/chain-auditor.d.ts +4 -4
- package/lib/dispatcher/chain-auditor.js +10 -8
- package/lib/dispatcher/dispatcher.d.ts +18 -18
- package/lib/dispatcher/dispatcher.js +59 -43
- package/lib/dispatcher/event-waker.d.ts +1 -1
- package/lib/dispatcher/event-waker.js +2 -2
- package/lib/dispatcher/git-credentials.d.ts +2 -2
- package/lib/dispatcher/git-credentials.js +2 -2
- package/lib/dispatcher/model-candidates.d.ts +1 -1
- package/lib/dispatcher/model-candidates.js +1 -1
- package/lib/dispatcher/session-events.d.ts +1 -1
- package/lib/dispatcher/session-events.js +1 -1
- package/lib/dispatcher/target-repo.d.ts +2 -2
- package/lib/dispatcher/target-repo.js +3 -3
- package/lib/dispatcher/v-orchestrator.d.ts +8 -8
- package/lib/dispatcher/v-orchestrator.js +41 -41
- package/lib/dispatcher/watchdog.d.ts +1 -1
- package/lib/dispatcher/watchdog.js +2 -2
- package/lib/domain/config-override.d.ts +14 -0
- package/lib/domain/config-override.js +38 -1
- package/lib/domain/delivery-contract.d.ts +2 -2
- package/lib/domain/delivery-contract.js +5 -5
- package/lib/domain/delivery-evidence.d.ts +1 -1
- package/lib/domain/delivery-evidence.js +1 -1
- package/lib/domain/event-store.js +1 -1
- package/lib/domain/im-message.d.ts +15 -0
- package/lib/domain/im-message.js +149 -0
- package/lib/domain/kanban-service.d.ts +13 -10
- package/lib/domain/kanban-service.js +36 -28
- package/lib/domain/memory.js +1 -1
- package/lib/domain/ocr-review.d.ts +47 -0
- package/lib/domain/ocr-review.js +105 -0
- package/lib/domain/permissions.js +5 -5
- package/lib/domain/projection.js +6 -6
- package/lib/domain/state-machine.js +1 -1
- package/lib/domain/task-parents.js +1 -1
- package/lib/domain/types.d.ts +4 -4
- package/lib/index.js +14 -9
- package/lib/roles/clean-fs-tools.js +3 -4
- package/lib/roles/preset-installer.js +3 -3
- package/lib/roles/skill-installer.js +1 -1
- package/lib/roles/toolsets.d.ts +60 -16
- package/lib/roles/toolsets.js +265 -47
- package/lib/roles/wiki-worker.js +3 -3
- package/lib/routes/kanban-http.d.ts +25 -2
- package/lib/routes/kanban-http.js +171 -11
- package/lib/routes/kanban-sse.d.ts +1 -1
- package/lib/routes/kanban-sse.js +1 -1
- package/lib/routes/planning-driver.d.ts +4 -2
- package/lib/routes/planning-driver.js +7 -3
- package/lib/routes/prefix-router.d.ts +3 -3
- package/lib/routes/prefix-router.js +3 -1
- package/lib/services/config-provider.d.ts +0 -4
- package/lib/services/config-provider.js +7 -9
- package/lib/services/im-delivery.d.ts +94 -0
- package/lib/services/im-delivery.js +263 -0
- package/lib/services/kanban-provider.d.ts +1 -1
- package/lib/services/kanban-provider.js +2 -2
- package/lib/services/ocr-cli.d.ts +35 -0
- package/lib/services/ocr-cli.js +128 -0
- package/lib/tools/kanban-tools.d.ts +2 -2
- package/lib/tools/kanban-tools.js +1 -1
- package/lib/tools/main-session-tools.d.ts +6 -2
- package/lib/tools/main-session-tools.js +58 -20
- package/lib/tools/ocr-review-tools.d.ts +11 -0
- package/lib/tools/ocr-review-tools.js +63 -0
- package/lib/tools/planning-tools.d.ts +5 -2
- package/lib/tools/planning-tools.js +11 -9
- package/lib/tools/wiki-tools.js +2 -2
- package/lib/wiki/kb-linkage.d.ts +1 -1
- package/lib/wiki/local-kb-client.js +3 -3
- package/lib/wiki/local-kb.d.ts +1 -1
- package/lib/wiki/local-kb.js +2 -2
- package/lib/wiki/page-path.d.ts +1 -1
- package/lib/wiki/page-path.js +6 -3
- package/lib/wiki/wiki-vault-client.d.ts +1 -1
- package/lib/wiki/wiki-vault-client.js +1 -1
- package/package.json +1 -1
- package/personas/kanban-d/agent.cordis.yml +5 -5
- package/personas/kanban-dt/agent.cordis.yml +23 -5
- package/personas/kanban-p/agent.cordis.yml +3 -3
- package/personas/kanban-pt/agent.cordis.yml +1 -1
- package/personas/kanban-v/agent.cordis.yml +3 -3
- package/personas/kanban-w/agent.cordis.yml +5 -5
- package/personas/persona-d.md +1 -1
- package/personas/persona-dt.md +12 -4
- package/personas/persona-p.md +1 -1
- package/personas/persona-v.md +2 -2
- package/personas/swarm/agent.cordis.yml +43 -0
- 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' }, //
|
|
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
|
-
/**
|
|
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
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
152
|
+
/** stall 自动再唤醒上限(同一阶段连续零产出 → 自动重试 ≤3 次,间隔递增)后放弃并显形。 */
|
|
153
153
|
static STALL_REWAKE_LIMIT = 3;
|
|
154
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
259
|
+
// 阶段推进循环(举一反三):跳过已完成(终态卡)阶段,
|
|
260
260
|
// 停在需要建卡的阶段建卡并推进;避免「推进 phase 后不建下一卡」造成的流水线停滞。
|
|
261
261
|
for (;;) {
|
|
262
|
-
//
|
|
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
|
-
//
|
|
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)
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
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 吞掉,
|
|
401
|
-
//
|
|
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
|
-
//
|
|
406
|
+
// 建卡数量硬闸:本轮只允许一张期望匹配卡推进 phase——取第一张匹配卡,其余建卡不推进
|
|
407
407
|
// (提取 kanban_create 调用;假实现从会话事件取,真实实现同名)。
|
|
408
|
-
//
|
|
408
|
+
// session.events 条目形态为 {type, data:{name, arguments}},name 在 data 下且
|
|
409
409
|
// arguments 是 JSON 字符串——统一经 toolName/toolArgs(src/dispatcher/session-events.ts)读取。
|
|
410
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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
|
-
//
|
|
564
|
+
// resume 与 create 都传 setup——恢复的 V 会话同样装配角色工具面(agent scope 注册随会话重建),
|
|
565
565
|
// 与 agent-runner.ts 的 installRoleTools 用法一致。
|
|
566
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
586
|
+
// preset mount 失败即抛(fail-fast),不再 console.error 静默放行——V 会话
|
|
587
587
|
// 无 persona 基座不得裸奔。抛错 → create/resume 失败 → getVAgent 抛 → 主推进路径并入
|
|
588
|
-
// 异常收场(
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
609
|
+
// V 会话首轮创建后保持 live,resume 会抛 "cannot prepare session while it is live"。
|
|
610
610
|
// 优先复用 agents registry 中仍 live 的会话(followup 续用),仅当会话已下线时才 resume。
|
|
611
|
-
//
|
|
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
|
-
//
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
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'); //
|
|
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
|
-
*
|
|
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
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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) {
|
|
@@ -18,7 +18,7 @@ export class FileEventStore {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
async append(ev) {
|
|
21
|
-
//
|
|
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;
|