@zhushanwen/pi-subagent-workflow 8.14.7 → 9.0.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 +6 -5
- package/package.json +11 -7
- package/relay/relay.mjs +45 -2
- package/skills/workflow-script-format/SKILL.md +2 -56
- package/src/host/__tests__/inflight-reporter.test.ts +26 -1
- package/src/host/__tests__/pi-host.test.ts +418 -4
- package/src/host/inflight-reporter.ts +13 -10
- package/src/host/pi-host.ts +205 -17
- package/src/index.ts +21 -72
- package/src/injectors/__tests__/engine-awareness.test.ts +236 -33
- package/src/injectors/__tests__/helpers/injector-test-mocks.ts +15 -7
- package/src/injectors/__tests__/workflow-list-injector.test.ts +447 -40
- package/src/injectors/resource-list-injector.ts +103 -32
- package/src/injectors/subagent-list-injector.ts +4 -28
- package/src/injectors/workflow-list-injector.ts +8 -4
- package/src/interface/__tests__/capture-tool.ts +56 -0
- package/src/interface/__tests__/commands-resume.test.ts +103 -0
- package/src/interface/__tests__/tool-prompt-contract.test.ts +361 -0
- package/src/interface/__tests__/tool-render.test.ts +105 -2
- package/src/interface/__tests__/tool-subagents.test.ts +24 -26
- package/src/interface/__tests__/tool-workflow-resume.test.ts +227 -0
- package/src/interface/__tests__/tool-workflow-script.test.ts +557 -0
- package/src/interface/__tests__/tool-workflow.test.ts +884 -0
- package/src/interface/bg-notify-render.ts +18 -9
- package/src/interface/command-actions.ts +22 -17
- package/src/interface/commands.ts +58 -12
- package/src/interface/format.ts +50 -16
- package/src/interface/id-preview.ts +4 -4
- package/src/interface/subagent-actions.ts +18 -50
- package/src/interface/subagent-tool.ts +26 -18
- package/src/interface/subagents.ts +5 -11
- package/src/interface/tool-render.ts +14 -30
- package/src/interface/tool-shared.ts +55 -12
- package/src/interface/tool-subagents.ts +21 -11
- package/src/interface/tool-workflow-script.ts +52 -72
- package/src/interface/tool-workflow.ts +345 -139
- package/src/interface/views/WorkflowsView.ts +10 -7
- package/src/interface/views/__tests__/{WorkflowsView-signature.test.ts → WorkflowsView.test.ts} +155 -11
- package/src/interface/views/detail-content.ts +3 -5
- package/src/jsonl-run-store.ts +656 -544
- package/src/model-events.ts +35 -0
- package/src/session-lifecycle.ts +376 -85
- package/src/subagents-events.ts +56 -0
- package/src/workflow-events.ts +285 -200
- package/src/workflow-notify.ts +456 -0
- package/src/injectors/__tests__/engine-section-stability.test.ts +0 -353
- package/src/injectors/__tests__/model-list-injector.test.ts +0 -136
- package/src/injectors/__tests__/subagent-list-injector.test.ts +0 -376
- package/src/interface/__tests__/detectors.test.ts +0 -161
- package/src/interface/__tests__/subagent-tool-prompt.test.ts +0 -183
- package/src/interface/__tests__/tool-workflow-run-builtin-name.test.ts +0 -219
- package/src/interface/__tests__/tool-workflow-script-generate.test.ts +0 -251
- package/src/interface/__tests__/tool-workflow-throw-paths.test.ts +0 -215
- package/src/interface/__tests__/workflow-state-file-exposure.test.ts +0 -38
- package/src/interface/__tests__/workflow-tool-prompt.test.ts +0 -129
- package/src/interface/helpers.ts +0 -272
- package/src/interface/views/__tests__/detail-content-session-file.test.ts +0 -73
- package/src/interface/views/__tests__/record-progress.test.ts +0 -235
package/src/workflow-events.ts
CHANGED
|
@@ -9,32 +9,37 @@
|
|
|
9
9
|
* reload 存活,factory 重跑拿到同一 domain state,post-reload adoption 据此接管
|
|
10
10
|
* 在飞 run):
|
|
11
11
|
* 1. per-factory 域状态(lsRef / notifiedRunIds / workerHost / registry / sessionState)
|
|
12
|
-
* 2. log(pi.appendEntry 包装)+
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
12
|
+
* 2. log(pi.appendEntry 包装)+ runSettledEffects(onRunDone 三步终局副作用
|
|
13
|
+
* 管线,具名导出可直测)+ makeLifecycleDeps + makeDeps(LauncherDeps 纯装配)
|
|
14
|
+
* 3. 本域 3 个 pi.on handler(session_start / session_tree / session_shutdown)
|
|
15
|
+
* + 4 个跨域事件注册(notify ledger compaction 守卫 / model 缓存刷新 /
|
|
16
|
+
* subagents 父级联关闭×2——setup* 函数住各自域模块,本 seam 在原注册位
|
|
17
|
+
* 置调用;pi.on 全链注册顺序逐位不变,
|
|
18
|
+
* workflow-events.test.ts 锁定)
|
|
19
|
+
* 4. deps 守卫(单一出口,失败 throw)+ lazyDeps(tool lazy 注入源)
|
|
17
20
|
*
|
|
18
|
-
* 组合根消费面:setupWorkflowDomain(pi
|
|
19
|
-
* WorkflowDomainHandle(state / lazyDeps /
|
|
20
|
-
* tool + command
|
|
21
|
+
* 组合根消费面:setupWorkflowDomain(pi) 返回
|
|
22
|
+
* WorkflowDomainHandle(state / lazyDeps / isScriptRunning),
|
|
23
|
+
* tool + command 注册仍留 index.ts。
|
|
21
24
|
*
|
|
22
25
|
* [skill-reload D3] makeDeps 的三个 volatile 成员(eventBus / log / onRunDone 的
|
|
23
|
-
* pi
|
|
24
|
-
*
|
|
25
|
-
* 跟进)——在飞 pump 持有的旧 deps 对象经属性访问自动路由到新绑定,无需遍历重绑。
|
|
26
|
+
* pi)不做闭包快照:pi 从槽上 currentPi 现读(factory 重跑时覆盖登记)——在飞
|
|
27
|
+
* pump 持有的旧 deps 对象经属性访问自动路由到新绑定,无需遍历重绑。
|
|
26
28
|
*
|
|
27
29
|
* 测试入口:既有 index 挂载类测试(index-session-start / process-shutdown-hook /
|
|
28
30
|
* wave0-package-structure 等)经 factory 间接覆盖;mock 锚点是模块解析路径
|
|
29
31
|
* (jsonl-run-store / interface/* / subagent-core 深路径),随迁不改写。
|
|
32
|
+
* runSettledEffects 经 fake deps 直测(workflow-events.test.ts,
|
|
33
|
+
* 不挂装配面)。注册顺序经 workflow-events.test.ts 锁定。
|
|
30
34
|
*
|
|
31
35
|
* 架构导航见 docs/extensions/subagents/architecture.md §2.1。
|
|
32
36
|
*/
|
|
33
37
|
|
|
38
|
+
import { join } from "node:path";
|
|
39
|
+
|
|
34
40
|
import type {
|
|
35
41
|
ExtensionAPI,
|
|
36
42
|
ExtensionContext,
|
|
37
|
-
SessionCompactEvent,
|
|
38
43
|
SessionShutdownEvent,
|
|
39
44
|
SessionStartEvent,
|
|
40
45
|
SessionTreeEvent,
|
|
@@ -43,24 +48,31 @@ import { getLogger } from "@zhushanwen/pi-extension-logger";
|
|
|
43
48
|
import { toErrorMessage } from "@zhushanwen/pi-ext-guards";
|
|
44
49
|
// ═══ 经 core barrel 消费 workflow 域(引擎与 worker 住 packages/subagent-core) ═══
|
|
45
50
|
import { bestEffort } from "@zhushanwen/subagent-core";
|
|
46
|
-
import { getBoundNotifyLedger } from "@zhushanwen/subagent-core";
|
|
47
|
-
import { getModelConfigService } from "@zhushanwen/subagent-core";
|
|
48
51
|
import { getSubagentService } from "@zhushanwen/subagent-core";
|
|
49
52
|
import type { LauncherDeps } from "@zhushanwen/subagent-core";
|
|
50
|
-
import {
|
|
53
|
+
import { terminateRunningRuns } from "@zhushanwen/subagent-core";
|
|
54
|
+
import { setInFlightListener } from "@zhushanwen/subagent-core";
|
|
51
55
|
import {
|
|
52
56
|
evictDoneRunsBeyondCap,
|
|
53
57
|
MAX_RETAINED_DONE_RUNS,
|
|
58
|
+
runSummary,
|
|
54
59
|
scheduleTimeBudget,
|
|
60
|
+
STATE_DIR_NAME,
|
|
55
61
|
} from "@zhushanwen/subagent-core";
|
|
56
62
|
import type { WorkflowRun } from "@zhushanwen/subagent-core";
|
|
57
63
|
import { WorkerHostImpl } from "@zhushanwen/subagent-core";
|
|
58
64
|
import { WorkflowScriptRegistryImpl } from "@zhushanwen/subagent-core";
|
|
59
|
-
// [u7a D5]
|
|
60
|
-
//
|
|
61
|
-
import type
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
// [u7a D5] 在途上报出口:实例由本 seam 创建并接线 setInFlightListener(组合根零
|
|
66
|
+
// 管道),session_start / session_shutdown 驱动 attach/detach;测试可注入 fake。
|
|
67
|
+
import { createInFlightReporter, type InFlightReporter } from "./host/inflight-reporter.ts";
|
|
68
|
+
// [W2/V1 D1 第 8 行] 判活类消费面(isScriptRunning)经 core 投影单点
|
|
69
|
+
//(runSummary.status 二值——内部收拢注册表 ∨ 聚合混合判源)。
|
|
70
|
+
import { type RunSettlementRecord } from "./jsonl-run-store.ts";
|
|
71
|
+
import { notifyDone, trackNotifiedRunId } from "./workflow-notify.ts";
|
|
72
|
+
// ═══ 跨域事件注册(handler 体住各自域模块,本 seam 原位调用保注册顺序) ═══
|
|
73
|
+
import { setupNotifyLedgerCompactionGuard } from "./workflow-notify.ts";
|
|
74
|
+
import { setupModelEvents } from "./model-events.ts";
|
|
75
|
+
import { setupSubagentsCascadeEvents } from "./subagents-events.ts";
|
|
64
76
|
// ═══ session 生命周期装配 seam(bootstrap seam,设计 §3.1/D1) ═══
|
|
65
77
|
import {
|
|
66
78
|
getOrCreateDialogQueue,
|
|
@@ -142,52 +154,108 @@ function resolveCurrentPi(): ExtensionAPI {
|
|
|
142
154
|
return pi;
|
|
143
155
|
}
|
|
144
156
|
|
|
145
|
-
/** 组合根侧生产 deps 工厂:SessionLifecycleDeps 全部成员有生产默认实现(住
|
|
146
|
-
* session-lifecycle.ts——createOrReuseServices 单例语义 / WorktreeManager 每次
|
|
147
|
-
* 扫描新建 / JsonlRunStore per-session 新建),此处无本地构造可注入;工厂形态
|
|
148
|
-
* 保留为组合根侧注入点(测试或后续演进可在此覆盖)。
|
|
149
|
-
* 测试注入路径:不挂载 index.ts,直接调 setupSessionLifecycle(pi, ctx, fakeDeps)。
|
|
150
|
-
* [skill-reload D4] 唯一本地构造 onAdoptionFailed 依赖 setupWorkflowDomain 闭包
|
|
151
|
-
* (makeDeps / sessionState),其定义随迁函数体内部(见 makeDeps 之后)。 */
|
|
152
|
-
|
|
153
157
|
// ── workflow deps 守卫(单一出口) ─────────────────────────────────────────────
|
|
154
158
|
//
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
// throw,保住调用方 Promise 契约),getDeps(3 个 tool 的 lazy deps 源)throw
|
|
159
|
-
// (pi tool 框架将其转译为 tool 错误结果)。错误消息逐字保留(crash-recovery
|
|
160
|
-
// 测试锁 "store unavailable" / "loadAll failed" 子串)。
|
|
161
|
-
export type WorkflowDepsResolution =
|
|
162
|
-
| { ok: true; deps: LauncherDeps }
|
|
163
|
-
| { ok: false; reason: string };
|
|
159
|
+
// 守卫单一出口(lazyDeps 消费):state 缺失 / storeHealthy=false 时 throw(pi tool
|
|
160
|
+
// 框架将其转译为 tool 错误结果)。错误消息逐字保留(crash-recovery 测试锁
|
|
161
|
+
// "store unavailable" / "loadAll failed" 子串)。
|
|
164
162
|
|
|
165
163
|
// ── lazyDeps 样板收敛 ──────────────────────────────────────────────────────────
|
|
166
164
|
|
|
167
165
|
/**
|
|
168
|
-
* lazyDeps 单成员转发原语:属性访问触发
|
|
169
|
-
*
|
|
170
|
-
* 收敛原 index.ts 10 个同构 4 行 getter;每属性独立求值语义不变。
|
|
166
|
+
* lazyDeps 单成员转发原语:属性访问触发 deps 守卫求值,失败 throw,成功转发
|
|
167
|
+
* deps[key]。收敛原 index.ts 10 个同构 4 行 getter;每属性独立求值语义不变。
|
|
171
168
|
*/
|
|
172
169
|
function createLazy<K extends keyof LauncherDeps>(
|
|
173
|
-
resolve: () =>
|
|
170
|
+
resolve: () => LauncherDeps,
|
|
174
171
|
key: K,
|
|
175
172
|
): LauncherDeps[K] {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
173
|
+
return resolve()[key];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ── run 终局固定顺序副作用(makeDeps onRunDone 管线提级,可直测) ─────────────
|
|
177
|
+
|
|
178
|
+
/** runSettledEffects 的注入面(生产装配点 = makeDeps;直测注入 fake——模块行为
|
|
179
|
+
* 可脱离装配面独立测试)。 */
|
|
180
|
+
export interface RunSettledEffectsEnv {
|
|
181
|
+
/** 完成通知的发送面(现读 volatile pi:生产传 resolveCurrentPi,调用时解析——
|
|
182
|
+
* D3 不快照,reload 后自动路由新 pi)。 */
|
|
183
|
+
resolvePi(): ExtensionAPI;
|
|
184
|
+
/** notifyDone 完成通知去重窗口(domainState.notifiedRunIds,调用方持有)。 */
|
|
185
|
+
notifiedRunIds: Set<string>;
|
|
186
|
+
/** per-session 装配结果(sessionDir/runs 两字段调用时现读——sessionState
|
|
187
|
+
* 条目引用,adoption rebind 换新后自动跟进的 D3 语义保持)。 */
|
|
188
|
+
state: Pick<SessionLifecycleResult, "sessionDir" | "runs">;
|
|
189
|
+
/**
|
|
190
|
+
* [W2/V1 D1 第 7 行] 终局记录查询面(通知载荷源——journal run-settled 帧直取)。
|
|
191
|
+
* 生产装配 = JsonlRunStore.settledRecordOf;返回 undefined = 无终局帧(journal
|
|
192
|
+
* dispatch 失败窗口)——通知跳过 + error 留痕(不回退两态机字段兜底:I2 失效
|
|
193
|
+
* 窗口下兜底 = 恒 completed 假成功,比丢通知更糟)。
|
|
194
|
+
*/
|
|
195
|
+
settledRecordOf(runId: string): RunSettlementRecord | undefined;
|
|
196
|
+
/** evict 日志的 session 归属(lsRef 引用,调用时现读 lastSessionId)。 */
|
|
197
|
+
lsRef: { lastSessionId: string };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* run 终局后的固定顺序副作用管线(原 makeDeps 的 onRunDone 闭包体提级具名导出)。
|
|
202
|
+
*
|
|
203
|
+
* onRunDone 是全部 done 路径的单点汇聚(abortRun + error-recovery),顺序固化为
|
|
204
|
+
* notifyDone → trackNotifiedRunId → evictDoneRunsBeyondCap:
|
|
205
|
+
* notifyDone 先发完整聚合通知(淘汰后聚合根仍在参数 run 引用上不受影响),
|
|
206
|
+
* trackNotifiedRunId 有界化去重窗口,最后裁剪已终局 run 内存([W2/V1] 终局判定
|
|
207
|
+
* 经 core isRunSettled 换源——本管线被 onRunDone 触发时注册表已 note,本轮 run
|
|
208
|
+
* 恒可淘汰候选,且其终局时序 = 全局最新,恒在保留端,不被自身触发的裁剪淘汰)。
|
|
209
|
+
*
|
|
210
|
+
* 失败语义(直测锁定,workflow-events.test.ts):管线内部无围栏
|
|
211
|
+
* ——notifyDone 抛错(账本写账失败 / 降级直发非 stale 失败)时后续 track/evict
|
|
212
|
+
* 不执行、异常原样上抛,由调用方 finalizeRun 的 onRunDone 独立 try 围栏接住
|
|
213
|
+
* (core worker-message-pump,OR-4/B-4:真实副作用失败 error 留痕不崩宿主)。
|
|
214
|
+
* notifyDone 幂等早退(notifiedRunIds 已含 runId)不是错误:后两步照常执行。
|
|
215
|
+
*
|
|
216
|
+
* [D7] notifyDone 注入产物目录指针(<sessionDir>/workflow-state——
|
|
217
|
+
* journal/manifest/.state 同目录;state.sessionDir 是 sessionState 条目字段,
|
|
218
|
+
* adoption rebind 换新后自动跟进;目录分量经 core barrel STATE_DIR_NAME 单源)。
|
|
219
|
+
* [W2/V1 D1 第 7 行] notifyDone 尾参注入终局记录(载荷源 = 帧直取);记录缺席
|
|
220
|
+
*(journal dispatch 失败窗口)时通知跳过 + error 留痕——evict 照常
|
|
221
|
+
*(内存有界性独立于通知),track 不标(未发通知不占去重窗口,允许后续语义修正)。
|
|
222
|
+
*/
|
|
223
|
+
export function runSettledEffects(env: RunSettledEffectsEnv, run: WorkflowRun): void {
|
|
224
|
+
const settlement = env.settledRecordOf(run.runId);
|
|
225
|
+
if (settlement !== undefined) {
|
|
226
|
+
notifyDone(
|
|
227
|
+
env.resolvePi(),
|
|
228
|
+
run.runId,
|
|
229
|
+
run,
|
|
230
|
+
env.notifiedRunIds,
|
|
231
|
+
join(env.state.sessionDir, STATE_DIR_NAME),
|
|
232
|
+
settlement,
|
|
233
|
+
);
|
|
234
|
+
trackNotifiedRunId(env.notifiedRunIds, run.runId);
|
|
235
|
+
} else {
|
|
236
|
+
logger.error(
|
|
237
|
+
`[workflow] done notify skipped: settlement record unavailable (runId=${run.runId}) — ` +
|
|
238
|
+
"run-settled journal dispatch likely failed; recovery: consult the journal error log above",
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
const evicted = evictDoneRunsBeyondCap(env.state.runs, MAX_RETAINED_DONE_RUNS);
|
|
242
|
+
if (evicted > 0) {
|
|
243
|
+
logger.debug("[subagent-workflow] evicted done runs beyond cap", {
|
|
244
|
+
evicted,
|
|
245
|
+
keep: MAX_RETAINED_DONE_RUNS,
|
|
246
|
+
sessionId: env.lsRef.lastSessionId,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
179
249
|
}
|
|
180
250
|
|
|
181
251
|
// ── 组合根消费面 ───────────────────────────────────────────────────────────────
|
|
182
252
|
|
|
183
253
|
export interface WorkflowDomainHandle {
|
|
184
254
|
/** workflow 域状态(组合根只读消费:engine-awareness lastEngine 存取器 /
|
|
185
|
-
* workflows command runs getter
|
|
255
|
+
* workflows command runs getter)。 */
|
|
186
256
|
state: WorkflowDomainState;
|
|
187
|
-
/**
|
|
257
|
+
/** 2 个 tool + workflows command 的 lazy deps 注入源(workflow-script tool 走 state.registry 直供不经 lazyDeps)。 */
|
|
188
258
|
lazyDeps: LauncherDeps;
|
|
189
|
-
/** 守卫单一出口(pi.__workflowRun 消费点;lazyDeps 内部走同一函数)。 */
|
|
190
|
-
getWorkflowDeps(sessionId: string): WorkflowDepsResolution;
|
|
191
259
|
/** workflow-script tool 的重入检查(跨 session 全局视角)。 */
|
|
192
260
|
isScriptRunning(name: string): boolean;
|
|
193
261
|
}
|
|
@@ -196,18 +264,28 @@ export interface WorkflowDomainHandle {
|
|
|
196
264
|
* workflow 域事件族装配单一入口(事件族 seam,与 session-lifecycle.ts 同构)。
|
|
197
265
|
* index.ts 的 workflow 域退为本调用 + 装配结果消费。
|
|
198
266
|
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
267
|
+
* 本域 3 个 handler(session_start → session_tree → session_shutdown)与 4 个
|
|
268
|
+
* 跨域 setup* 注册(notify ledger compaction 守卫 / model 缓存刷新 / subagents
|
|
269
|
+
* 父级联关闭×2)交错,pi.on 全链注册顺序逐位不变(锁
|
|
270
|
+
* workflow-events.test.ts);engine-awareness
|
|
271
|
+
* (before_agent_start 链尾)仍由组合根在本调用之后注册(before_agent_start
|
|
272
|
+
* 链序不变,跨事件通道无注册时序语义)。
|
|
203
273
|
*/
|
|
204
274
|
export function setupWorkflowDomain(
|
|
205
275
|
pi: ExtensionAPI,
|
|
206
|
-
wiring: { inflightReporter
|
|
276
|
+
wiring: { inflightReporter?: InFlightReporter } = {},
|
|
207
277
|
): WorkflowDomainHandle {
|
|
208
|
-
|
|
278
|
+
// [u7a D5] 在途聚合上报接线(自组合根 index.ts 收编):core 状态迁移
|
|
279
|
+
// (spawn/close/arm/disarm)→ 出口回调 → reporter 经 select 通道推绝对计数帧。
|
|
280
|
+
// 出口为进程级单监听(在途记账本身是 pi 进程级模块状态),后注册覆盖先注册
|
|
281
|
+
// (jiti 重载幂等);回调同步 void,不进任何生命周期 await 链(D5 接线约束①)。
|
|
282
|
+
// ctx 由 session_start 注入(factory 阶段无 ui)。wiring.inflightReporter 是测试
|
|
283
|
+
// 注入面(fake reporter 观察 attach/detach 驱动时点);生产路径缺省真实创建——
|
|
284
|
+
// 组合根对实例零知识(纯管道参数已删)。
|
|
285
|
+
const inflightReporter = wiring.inflightReporter ?? createInFlightReporter();
|
|
286
|
+
setInFlightListener(inflightReporter.onInFlightChanged);
|
|
209
287
|
// [skill-reload D2] handle.state 即槽对象本身(不做解构重包装——否则容器每次
|
|
210
|
-
//
|
|
288
|
+
// factory 重跑新建,调用方拿不到「同一 domain state 引用」的接管前提)。
|
|
211
289
|
const domainState = getOrCreateWorkflowDomainState();
|
|
212
290
|
// [skill-reload D3] current pi 登记点:factory 每次重跑(reload 后新 factory 持新
|
|
213
291
|
// pi)在此覆盖槽上 volatile 绑定——旧 deps 对象的现读成员(见 makeDeps)自动
|
|
@@ -233,12 +311,19 @@ export function setupWorkflowDomain(
|
|
|
233
311
|
data,
|
|
234
312
|
});
|
|
235
313
|
} catch (err) {
|
|
236
|
-
|
|
314
|
+
// appendEntry 失败(stale ctx / session 已关闭等)= workflow:log entry 零痕迹。
|
|
315
|
+
// 独立通道留痕:extension-logger 的写点通道是 `subagents:log`(非
|
|
316
|
+
// `workflow:log`),不重入本函数;其 error() 内部 catch appendEntry 失败并
|
|
317
|
+
// 降级文件日志(TAIJI_AGENT_DEBUG=1 可见),自身不再抛。
|
|
318
|
+
logger.error(
|
|
319
|
+
`[subagent-workflow] workflow:log appendEntry failed (component=${component}): ${message}`,
|
|
320
|
+
{ level, reason: toErrorMessage(err) },
|
|
321
|
+
);
|
|
237
322
|
}
|
|
238
323
|
}
|
|
239
324
|
|
|
240
325
|
function makeDeps(
|
|
241
|
-
state: Pick<SessionLifecycleResult, "store" | "runs" | "sessionDir" | "runner"
|
|
326
|
+
state: Pick<SessionLifecycleResult, "store" | "runs" | "sessionDir" | "runner">,
|
|
242
327
|
) {
|
|
243
328
|
const deps: LauncherDeps = {
|
|
244
329
|
store: state.store,
|
|
@@ -246,30 +331,29 @@ export function setupWorkflowDomain(
|
|
|
246
331
|
runner: state.runner,
|
|
247
332
|
runs: state.runs,
|
|
248
333
|
registry,
|
|
249
|
-
// [skill-reload D3] 三个 volatile 成员(eventBus / log / onRunDone 的 pi
|
|
250
|
-
//
|
|
251
|
-
//
|
|
252
|
-
// 跟进)。在飞 pump 持有的旧 deps 对象经属性访问自动路由到新 pi/ctx。eventBus
|
|
334
|
+
// [skill-reload D3] 三个 volatile 成员(eventBus / log / onRunDone 的 pi)
|
|
335
|
+
// 现读不快照:pi 从槽 currentPi 解析(factory 重跑覆盖登记)。在飞 pump 持有的
|
|
336
|
+
// 旧 deps 对象经属性访问自动路由到新 pi。eventBus
|
|
253
337
|
// 是值成员必须 getter;log/onRunDone 是函数成员,现读在函数体内达成(函数引用
|
|
254
338
|
// 稳定,调用方缓存引用也无 stale 面)。
|
|
255
339
|
//
|
|
256
|
-
// onRunDone
|
|
257
|
-
//
|
|
258
|
-
//
|
|
259
|
-
//
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
|
|
340
|
+
// onRunDone = run 终局固定顺序副作用管线(提级为 runSettledEffects 具名
|
|
341
|
+
// 导出,顺序与失败语义的权威注释在该函数;此处纯装配注入依赖)。
|
|
342
|
+
// [W2/V1 D1 第 7 行] settledRecordOf 注入 = store 帧查询(notifyDone 在
|
|
343
|
+
// finalizeRun coda 内于 dispatch 落账之后触发,帧必已落 journal)。
|
|
344
|
+
onRunDone: (run: WorkflowRun) =>
|
|
345
|
+
runSettledEffects(
|
|
346
|
+
{
|
|
347
|
+
resolvePi: resolveCurrentPi,
|
|
348
|
+
notifiedRunIds,
|
|
349
|
+
state,
|
|
350
|
+
// [W2/V1 D1 第 7 行] 可选调用(fake lifecycle result 的 store 缺该面时返回 undefined
|
|
351
|
+
// ——notifyDone 跳过分支承接,见 runSettledEffects 注释)。
|
|
352
|
+
settledRecordOf: (runId) => state.store.settledRecordOf?.(runId),
|
|
353
|
+
lsRef,
|
|
354
|
+
},
|
|
355
|
+
run,
|
|
356
|
+
),
|
|
273
357
|
get eventBus() {
|
|
274
358
|
return resolveCurrentPi().events;
|
|
275
359
|
},
|
|
@@ -284,19 +368,23 @@ export function setupWorkflowDomain(
|
|
|
284
368
|
},
|
|
285
369
|
scheduleTimeBudget: (runId: string, budgetTimeMs: number) =>
|
|
286
370
|
scheduleTimeBudget(runId, deps, budgetTimeMs),
|
|
287
|
-
onWorkflowCall: (name: string, args: Record<string, unknown>, parentRun: WorkflowRun) =>
|
|
288
|
-
executeNestedWorkflow(name, args, parentRun, deps),
|
|
289
371
|
// [H2 W3] workflow agent() 统一派发入口(设计 §3.5):pump 侧 dispatchAgentCall
|
|
290
372
|
// 经此转调 SubagentService.executeWorkflowAgent——真实 record(origin:"workflow"
|
|
291
373
|
// + parentRunId)进 store、共享池/守护/journal 归 service 编排;parentRunId 由
|
|
292
374
|
// pump 补 run.runId。service 单例在 session_start 后必在(run 只能于 session 内
|
|
293
375
|
// 派发);null 时抛错由 pump 的 dispatchCall catch 兜底回发 failed result。
|
|
294
|
-
workflowAgentDispatch: (opts, parentRunId, signal) => {
|
|
376
|
+
workflowAgentDispatch: (opts, parentRunId, signal, stepIndex) => {
|
|
295
377
|
const service = getSubagentService();
|
|
296
378
|
if (!service) {
|
|
297
|
-
|
|
379
|
+
// [C2] 错误带恢复动作:service 缺席 = session_start 装配链失败(与
|
|
380
|
+
// getWorkflowDeps 的 Session not initialized 同根因),文案同款闭环。
|
|
381
|
+
throw new Error(
|
|
382
|
+
"workflow agent dispatch unavailable: subagent service not initialized " +
|
|
383
|
+
"(session_start assembly failed). Recovery: restart pi or reload this session; " +
|
|
384
|
+
"check the subagents extension logs for the root cause.",
|
|
385
|
+
);
|
|
298
386
|
}
|
|
299
|
-
return service.executeWorkflowAgent(opts, parentRunId, signal);
|
|
387
|
+
return service.executeWorkflowAgent(opts, parentRunId, signal, undefined, undefined, stepIndex);
|
|
300
388
|
},
|
|
301
389
|
log,
|
|
302
390
|
};
|
|
@@ -306,26 +394,32 @@ export function setupWorkflowDomain(
|
|
|
306
394
|
function isScriptRunning(name: string): boolean {
|
|
307
395
|
for (const state of sessionState.values()) {
|
|
308
396
|
for (const run of state.runs.values()) {
|
|
309
|
-
|
|
397
|
+
// [W2/V1 D1 第 8 行] 判活换源 runSummary 投影二值(混合判源收拢在 core
|
|
398
|
+
// 投影单点——原两态机 status 读随活体写点删除停更)。
|
|
399
|
+
if (run.spec.scriptName === name && runSummary(run).status === "running") return true;
|
|
310
400
|
}
|
|
311
401
|
}
|
|
312
402
|
return false;
|
|
313
403
|
}
|
|
314
404
|
|
|
315
|
-
/**
|
|
316
|
-
*
|
|
317
|
-
*
|
|
318
|
-
*
|
|
405
|
+
/** 组合根侧生产 deps 工厂:SessionLifecycleDeps 全部成员有生产默认实现(住
|
|
406
|
+
* session-lifecycle.ts——createOrReuseServices 单例语义 / WorktreeManager 每次
|
|
407
|
+
* 扫描新建 / JsonlRunStore per-session 新建),此处无本地构造可注入;工厂形态
|
|
408
|
+
* 保留为组合根侧注入点(测试或后续演进可在此覆盖)。
|
|
409
|
+
* 测试注入路径:不挂载 index.ts,直接调 setupSessionLifecycle(pi, ctx, fakeDeps)。
|
|
410
|
+
* [skill-reload D4] 唯一本地构造 onAdoptionFailed 依赖 setupWorkflowDomain 闭包
|
|
411
|
+
* (makeDeps 的 LauncherDeps 完整形态——workerHost / onRunDone 通知链经 D3
|
|
412
|
+
* 现读自动路由到新 pi;sessionState 移除依赖 domain state Map),归 workflow 域、
|
|
413
|
+
* session-lifecycle seam 无访问通道,经 SessionLifecycleDeps 注入。 */
|
|
319
414
|
function makeLifecycleDeps(): SessionLifecycleDeps {
|
|
320
415
|
return {
|
|
321
416
|
onAdoptionFailed: async (existing, reason) => {
|
|
322
|
-
//
|
|
323
|
-
//
|
|
324
|
-
//
|
|
417
|
+
// [D11] 统一中断:run 转 interrupted 暂停态(非终局),用户可见性由 v2
|
|
418
|
+
// 中断条目承载(workflow 列表「已中断(可续跑)」);terminate 前的
|
|
419
|
+
// rebind-first 已在 failAdoption 完成,中断条目落新 pi 权威 JSONL。
|
|
325
420
|
await terminateRunningRuns(
|
|
326
421
|
makeDeps(existing),
|
|
327
422
|
`skill reload adoption failed: ${reason}`,
|
|
328
|
-
{ notifyDone: true },
|
|
329
423
|
);
|
|
330
424
|
sessionState.delete(existing.sessionId);
|
|
331
425
|
},
|
|
@@ -341,59 +435,45 @@ export function setupWorkflowDomain(
|
|
|
341
435
|
// 开头一致)+ 装配结果写入 per-session sessionState。
|
|
342
436
|
// ════════════════════════════════════════════════════════════
|
|
343
437
|
pi.on("session_start", async (event: SessionStartEvent, ctx: ExtensionContext) => {
|
|
344
|
-
|
|
345
|
-
//
|
|
346
|
-
//
|
|
347
|
-
//
|
|
348
|
-
inflightReporter.attachSession(ctx);
|
|
349
|
-
// [skill-reload D4] adoption 入参接线:reason 是 handler 独占信息(event 参数),
|
|
350
|
-
// existing 是 sessionState(domainState 闭包)里的既有条目——两者都是
|
|
351
|
-
// session-lifecycle seam 的 adoption 分流判据,经 SessionStartOptions 传入。
|
|
352
|
-
// 非 reload 的 session_start 不取 existing(quit 族 session_shutdown 已删条目,
|
|
353
|
-
// 此处恒 undefined;显式不传也防误接管)。
|
|
354
|
-
const existing =
|
|
355
|
-
event.reason === "reload" ? sessionState.get(ctx.sessionManager.getSessionId()) : undefined;
|
|
356
|
-
const result = await setupSessionLifecycle(pi, ctx, makeLifecycleDeps(), {
|
|
357
|
-
reason: event.reason,
|
|
358
|
-
existing,
|
|
359
|
-
});
|
|
360
|
-
sessionState.set(result.sessionId, result);
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
// ════════════════════════════════════════════════════════════
|
|
364
|
-
// [U2 P-B4 降级] session_compact:compaction 对 custom entry 保留行为实装未
|
|
365
|
-
// 验证——检测 ledger/ack entry 被 compaction 清除时按内存态补写(notify-ledger
|
|
366
|
-
// compactionCheck;未清除则 no-op)。内存态在 compaction 后仍活着,作为补写源;
|
|
367
|
-
// 重启后的权威仍是两列 entry 差集(内存不承担销账职责)。
|
|
368
|
-
// ═══════════════════════════════════════════════════════════
|
|
369
|
-
pi.on("session_compact", (_event: SessionCompactEvent, _ctx: ExtensionContext) => {
|
|
438
|
+
// 装配链异常不向 pi 事件分发逃逸(pi 0.84.4 extension handler 未捕获的 rejection
|
|
439
|
+
// 直接炸进程——E1 同机制):围栏 error 留痕(含 sessionId)后保持 handler 不抛。
|
|
440
|
+
// lsRef 在 try 内先行赋值:getSessionId 自身抛错时 catch 里拿到的是上一 session
|
|
441
|
+
// 的 id(留痕仍可检索)。
|
|
370
442
|
try {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
443
|
+
lsRef.lastSessionId = ctx.sessionManager.getSessionId();
|
|
444
|
+
// [u7a D5] 初始上报(count=当下绝对计数):触发时点 = extension 加载完成 / session
|
|
445
|
+
// 就绪(factory 无 ctx/ui,session_start 是最早带 ctx 的钩子——plugin-bridge 同款
|
|
446
|
+
// 事实)。fire-and-forget 在 await 装配链之前发起,不阻塞也不被阻塞。
|
|
447
|
+
inflightReporter.attachSession(ctx);
|
|
448
|
+
// [skill-reload D4] adoption 入参接线:reason 是 handler 独占信息(event 参数),
|
|
449
|
+
// existing 是 sessionState(domainState 闭包)里的既有条目——两者都是
|
|
450
|
+
// session-lifecycle seam 的 adoption 分流判据,经 SessionStartOptions 传入。
|
|
451
|
+
// 非 reload 的 session_start 不取 existing(quit 族 session_shutdown 已删条目,
|
|
452
|
+
// 此处恒 undefined;显式不传也防误接管)。
|
|
453
|
+
const existing =
|
|
454
|
+
event.reason === "reload" ? sessionState.get(ctx.sessionManager.getSessionId()) : undefined;
|
|
455
|
+
const result = await setupSessionLifecycle(pi, ctx, makeLifecycleDeps(), {
|
|
456
|
+
reason: event.reason,
|
|
457
|
+
existing,
|
|
378
458
|
});
|
|
459
|
+
sessionState.set(result.sessionId, result);
|
|
460
|
+
} catch (err) {
|
|
461
|
+
logger.error(
|
|
462
|
+
`[subagent-workflow] session_start handler failed (sessionId=${lsRef.lastSessionId})`,
|
|
463
|
+
{ reason: toErrorMessage(err) },
|
|
464
|
+
);
|
|
379
465
|
}
|
|
380
466
|
});
|
|
381
467
|
|
|
382
468
|
// ════════════════════════════════════════════════════════════
|
|
383
|
-
//
|
|
469
|
+
// [U2 P-B4 降级] notify ledger compaction 补写守卫(notify 域)
|
|
384
470
|
// ════════════════════════════════════════════════════════════
|
|
385
|
-
pi
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
const sid = ctx.sessionManager.getSessionId();
|
|
392
|
-
const state = sessionState.get(sid);
|
|
393
|
-
if (state) {
|
|
394
|
-
state.runner.updateCtxModel(event.model);
|
|
395
|
-
}
|
|
396
|
-
});
|
|
471
|
+
setupNotifyLedgerCompactionGuard(pi);
|
|
472
|
+
|
|
473
|
+
// ════════════════════════════════════════════════════════════
|
|
474
|
+
// model 缓存刷新(model 域)
|
|
475
|
+
// ════════════════════════════════════════════════════════════
|
|
476
|
+
setupModelEvents(pi);
|
|
397
477
|
|
|
398
478
|
// ════════════════════════════════════════════════════════════
|
|
399
479
|
// session_tree:切分支前终止所有 running run(一次性生命周期——切走即作废)
|
|
@@ -409,47 +489,17 @@ export function setupWorkflowDomain(
|
|
|
409
489
|
try {
|
|
410
490
|
await terminateRunningRuns(makeDeps(state), "Session switched: run terminated");
|
|
411
491
|
} catch (err) {
|
|
412
|
-
|
|
492
|
+
// 外层兜底(正常路径 helper 内部已自过滤单 run 失败)——error 级:终态落盘
|
|
493
|
+
// 失败意味着重启后 kill-9 恢复的输入缺失,必须可见。
|
|
494
|
+
bestEffort(err, "terminateRunningRuns (session_tree handler)", "error");
|
|
413
495
|
}
|
|
414
496
|
}
|
|
415
497
|
});
|
|
416
498
|
|
|
417
499
|
// ════════════════════════════════════════════════════════════
|
|
418
|
-
// SP-4:
|
|
500
|
+
// SP-4: 父级联关闭(subagents 域)——/fork 与 /new 的级联 record 清理
|
|
419
501
|
// ════════════════════════════════════════════════════════════
|
|
420
|
-
|
|
421
|
-
// archive + worktree 清理)。before 事件在 session 替换前触发,确保旧 session 的
|
|
422
|
-
// subagent 在新 session 创建前被清理(随后的 session_shutdown → dispose 收割子进程)。
|
|
423
|
-
//
|
|
424
|
-
// [M2 修复] 旧实现把 /new 级联挂在 session_before_tree 上——SDK 中该事件只由
|
|
425
|
-
// AgentSession.navigateTree()(/tree 同 session 分支切换)触发,/new 走
|
|
426
|
-
// session_before_switch(reason:"new") + session_shutdown(reason:"new"),从不触发
|
|
427
|
-
// before_tree。后果双向:/new 级联是死代码;普通 /tree 分支导航反而误杀全部活跃
|
|
428
|
-
// subagent。现 /new 改挂 session_before_switch(reason==="new"),before_tree handler
|
|
429
|
-
// 移除(/tree 是同 session 内导航,record/子进程归属不变,无级联关闭诉求)。
|
|
430
|
-
pi.on("session_before_fork", (_event, _ctx) => {
|
|
431
|
-
const service = getSubagentService();
|
|
432
|
-
if (service) {
|
|
433
|
-
const count = service.onParentFork();
|
|
434
|
-
if (count > 0) {
|
|
435
|
-
logger.warn(`[subagents] /fork 级联关闭 ${count} 个 subagent`);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
});
|
|
439
|
-
|
|
440
|
-
pi.on("session_before_switch", (event, _ctx) => {
|
|
441
|
-
// /new(reason:"new")创建全新 session → 级联关闭旧 record。
|
|
442
|
-
// reason:"resume"(/resume /import 回到已有 session)不级联:record 按 rootSessionId
|
|
443
|
-
// 归属隔离,跨 session 读写由 store 过滤守卫,无需销毁。
|
|
444
|
-
if (event.reason !== "new") return;
|
|
445
|
-
const service = getSubagentService();
|
|
446
|
-
if (service) {
|
|
447
|
-
const count = service.onParentNew();
|
|
448
|
-
if (count > 0) {
|
|
449
|
-
logger.warn(`[subagents] /new 级联关闭 ${count} 个 subagent`);
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
});
|
|
502
|
+
setupSubagentsCascadeEvents(pi);
|
|
453
503
|
|
|
454
504
|
// ════════════════════════════════════════════════════════════
|
|
455
505
|
// session_shutdown:dispose subagents + terminate workflows + store 收尾 + cleanup
|
|
@@ -463,14 +513,15 @@ export function setupWorkflowDomain(
|
|
|
463
513
|
// stale ctx 反复 attempt;新 factory 建新 reporter 并覆盖进程级单监听)。存活的
|
|
464
514
|
// 在飞 run / store 经 D2 槽由 post-reload session_start(reason=reload) 的
|
|
465
515
|
// adoption 接管(D4,session-lifecycle.ts)。quit/new/resume/fork = 会话真离开,
|
|
466
|
-
// 六动作现状全保持;session_tree /
|
|
467
|
-
//
|
|
516
|
+
// 六动作现状全保持;session_tree 与 /new 父级联(subagents-events.ts)的
|
|
517
|
+
// terminate 路径不受影响(各自独立 handler,真语义)。
|
|
468
518
|
//
|
|
469
519
|
// store 收尾:每 session 的 JsonlRunStore 在 terminateRunningRuns 之后 dispose(刷
|
|
470
520
|
// pending 去抖批 + await in-flight 链,见 W2C5)。R3 声明:SIGTERM/SIGINT 走
|
|
471
521
|
// 组合根 process handler 不触发本路径,pending 去抖丢失等价崩溃链(重启后 kill-9
|
|
472
|
-
// 恢复收编 running
|
|
473
|
-
// running
|
|
522
|
+
// 恢复收编 running 残留——终态/创建均冷路径已落盘,丢的只有最后一次成功物化之后
|
|
523
|
+
// 的 running 尾巴(边沿防抖窗口内有批未物化时同属此列),ES1 已接受);不做
|
|
524
|
+
// best-effort SIGTERM dispose(需同步 IO 改造,
|
|
474
525
|
// 超出 wave 边界)。
|
|
475
526
|
// ════════════════════════════════════════════════════════════
|
|
476
527
|
pi.on("session_shutdown", async (event: SessionShutdownEvent, _ctx: ExtensionContext) => {
|
|
@@ -498,7 +549,17 @@ export function setupWorkflowDomain(
|
|
|
498
549
|
}
|
|
499
550
|
|
|
500
551
|
// ── subagents 域:dispose SubagentService ──
|
|
501
|
-
|
|
552
|
+
// dispose 是多步同步链,任一步同步抛错会跳过后续全部清理(terminateRunningRuns /
|
|
553
|
+
// store.dispose / dialogQueue.rejectAll)——围栏与下方 store.dispose 的防御同款:
|
|
554
|
+
// error 留痕(含 sessionId)后继续后续清理,不向 pi 事件分发逃逸。
|
|
555
|
+
try {
|
|
556
|
+
getSubagentService()?.dispose();
|
|
557
|
+
} catch (err) {
|
|
558
|
+
logger.error(
|
|
559
|
+
`[subagent-workflow] session_shutdown SubagentService.dispose failed (sessionId=${lsRef.lastSessionId})`,
|
|
560
|
+
{ reason: toErrorMessage(err) },
|
|
561
|
+
);
|
|
562
|
+
}
|
|
502
563
|
|
|
503
564
|
// [u7a D5] 在途上报通道随 session 终结:摘 ctx + 停重试(session 已死,重试直至
|
|
504
565
|
// 成功的语义只对活 session 成立;进程级出口监听保留——后续 /new 重新 attach)。
|
|
@@ -508,7 +569,8 @@ export function setupWorkflowDomain(
|
|
|
508
569
|
// H-5: 遍历所有 sessionState 条目清理(而不只 lastSessionId——
|
|
509
570
|
// 防御 session 切换但 session_tree 未先触发导致 lastSessionId 指向已删除 session 的情况)。
|
|
510
571
|
for (const [sessionId, state] of sessionState) {
|
|
511
|
-
// 编排顺序(W2C5):terminate(await,
|
|
572
|
+
// 编排顺序(W2C5):terminate(await,[D11] 中断转移落 record——run 停
|
|
573
|
+
// interrupted 暂停态可再 resume)
|
|
512
574
|
// → store.dispose(await,刷 pending 去抖批 + await in-flight 链,关「shutdown
|
|
513
575
|
// 时刻 pending 去抖写丢失」窗口)→ delete。terminate 的 running 过滤在 helper
|
|
514
576
|
// 内部(单 run 失败不中断其余);外层 try/catch 兜底防单 session 异常中断后续
|
|
@@ -516,13 +578,15 @@ export function setupWorkflowDomain(
|
|
|
516
578
|
try {
|
|
517
579
|
await terminateRunningRuns(makeDeps(state), "Session shutdown: run terminated");
|
|
518
580
|
} catch (err) {
|
|
519
|
-
|
|
581
|
+
// 外层兜底(正常路径 helper 内部已自过滤单 run 失败)——error 级:中断转移
|
|
582
|
+
// 落账失败意味着重启后收编链的输入缺失(该 run 仍呈 running 活体投影),必须可见。
|
|
583
|
+
bestEffort(err, "terminateRunningRuns (session_shutdown handler)", "error");
|
|
520
584
|
}
|
|
521
585
|
// dispose 自身恒 resolve,catch 兜底防御——handler 内抛错会中断后续 session
|
|
522
|
-
// 条目清理。不留静默吞错(错误必须可操作):
|
|
586
|
+
// 条目清理。不留静默吞错(错误必须可操作):warn 留痕带 sessionId/sessionDir,
|
|
523
587
|
// 排查「shutdown 后 run 状态不落盘」类问题时有迹可循。
|
|
524
588
|
await state.store.dispose().catch((err: unknown) => {
|
|
525
|
-
logger.
|
|
589
|
+
logger.warn(
|
|
526
590
|
`[subagent-workflow] session_shutdown store.dispose failed (sessionId=${sessionId}, sessionDir=${state.sessionDir})`,
|
|
527
591
|
{ reason: toErrorMessage(err) },
|
|
528
592
|
);
|
|
@@ -543,27 +607,44 @@ export function setupWorkflowDomain(
|
|
|
543
607
|
dialogQueue.rejectAll();
|
|
544
608
|
});
|
|
545
609
|
|
|
546
|
-
|
|
610
|
+
/** deps 守卫单一出口:state 缺失 / store 不健康时 throw(错误消息逐字保留——
|
|
611
|
+
* crash-recovery / session-lifecycle 等测试锁定子串),成功返回 LauncherDeps。 */
|
|
612
|
+
const resolveDepsFor = (sessionId: string): LauncherDeps => {
|
|
547
613
|
const state = sessionState.get(sessionId);
|
|
548
614
|
if (!state) {
|
|
549
|
-
|
|
615
|
+
// [C2] 错误带恢复动作(对齐下方 store unavailable 的 MF-1 闭环风格):state
|
|
616
|
+
// 缺席的 root cause 是 session_start 装配链失败(围栏 catch 只留 extension
|
|
617
|
+
// 日志),文案指引 reload + 查日志,接通「现象 → 根因」链路。前缀子串
|
|
618
|
+
// "Session not initialized" 被 session-lifecycle / workflow-events-deps-getter
|
|
619
|
+
// 测试锁定(toThrowError 子串匹配),改写时保留该前缀。
|
|
620
|
+
throw new Error(
|
|
621
|
+
"Session not initialized (session_start assembly failed). " +
|
|
622
|
+
"Recovery: restart pi or reload this session to re-run initialization; " +
|
|
623
|
+
"check the subagents extension logs (session_start failure) for the root cause.",
|
|
624
|
+
);
|
|
550
625
|
}
|
|
551
626
|
// MF-1: store 不健康时 fail-fast,避免 store.save 再次失败导致 run 状态不落地。
|
|
552
627
|
if (!state.storeHealthy) {
|
|
553
|
-
|
|
628
|
+
// 错误带恢复动作(错误 → 恢复闭环):loadAll 失败的 store 本进程内不恢复,
|
|
629
|
+
// 重启 pi 或重载 session(重建 store + 重跑 kill-9 恢复)是唯一出路。前半段
|
|
630
|
+
// 子串("store unavailable" / "loadAll failed")被 crash-recovery 等测试锁定。
|
|
631
|
+
throw new Error(
|
|
632
|
+
"Workflow store unavailable (loadAll failed in session_start). " +
|
|
633
|
+
"Restart pi or reload this session to re-run crash recovery.",
|
|
634
|
+
);
|
|
554
635
|
}
|
|
555
|
-
return
|
|
636
|
+
return makeDeps(state);
|
|
556
637
|
};
|
|
557
638
|
|
|
558
639
|
// ════════════════════════════════════════════════════════════
|
|
559
|
-
// lazyDeps(
|
|
640
|
+
// lazyDeps(2 个 tool + workflows command 的 lazy deps 注入源;
|
|
641
|
+
// workflow-script tool 走 state.registry 直供不经 lazyDeps)
|
|
560
642
|
//
|
|
561
|
-
// 属性访问触发
|
|
562
|
-
//
|
|
563
|
-
//
|
|
564
|
-
// session-lifecycle.test.ts 锁定)。
|
|
643
|
+
// 属性访问触发 deps 守卫 + makeDeps 求值(每属性独立,createLazy 原语转发;
|
|
644
|
+
// 守卫失败 throw——pi tool 框架转译为 tool 错误结果,session-lifecycle.test.ts
|
|
645
|
+
// 锁定消息子串)。
|
|
565
646
|
// ════════════════════════════════════════════════════════════
|
|
566
|
-
const resolveDeps = ():
|
|
647
|
+
const resolveDeps = (): LauncherDeps => resolveDepsFor(lsRef.lastSessionId);
|
|
567
648
|
const lazyDeps: LauncherDeps = {
|
|
568
649
|
get store() { return createLazy(resolveDeps, "store"); },
|
|
569
650
|
get runs() { return createLazy(resolveDeps, "runs"); },
|
|
@@ -572,17 +653,21 @@ export function setupWorkflowDomain(
|
|
|
572
653
|
get eventBus() { return createLazy(resolveDeps, "eventBus"); },
|
|
573
654
|
get workerHost() { return createLazy(resolveDeps, "workerHost"); },
|
|
574
655
|
get runner() { return createLazy(resolveDeps, "runner"); },
|
|
575
|
-
//
|
|
576
|
-
//
|
|
577
|
-
//
|
|
578
|
-
//
|
|
579
|
-
//
|
|
580
|
-
|
|
656
|
+
// [A1 修复循环 R3] workflowAgentDispatch 必须随 lazyDeps 转发:workflow tool 的
|
|
657
|
+
// run action 以 lazyDeps 为 deps 启动 run,漏本成员则 pump dispatchAgentCall 读到
|
|
658
|
+
// undefined 回退 deps.runner(SAR.run 占位 runId)→ record.parentRunId =
|
|
659
|
+
// "sar-unattached" → armed 回执落账键错 → fold 出 created 态 → IllegalTransitionError
|
|
660
|
+
// 让位,run journal 恒缺 armed 帧(真机 wf-1790034646281-w7tp4f 实证链,R2 裁决)。
|
|
661
|
+
get workflowAgentDispatch() { return createLazy(resolveDeps, "workflowAgentDispatch"); },
|
|
662
|
+
// scheduleTimeBudget / appendEntry 不可缺席(ports.ts D-12 regression fix 同族):
|
|
663
|
+
// rebuildRuntime 重排 run 级墙钟预算计时器、finalizeRun 的 pending:unregister
|
|
664
|
+
// 直落都经这两个成员消费——lazyDeps 缺席会让消费点拿到 undefined(可选属性
|
|
665
|
+
// 静默放行);appendEntry 缺席尤其危险:workflow tool 的 run action 以 lazyDeps
|
|
666
|
+
// 为 deps 启动 run,直落静默跳过 + emit 已删 = 注销 entry 永缺位。转发形态与其余成员一致。
|
|
581
667
|
get scheduleTimeBudget() { return createLazy(resolveDeps, "scheduleTimeBudget"); },
|
|
582
|
-
get onWorkflowCall() { return createLazy(resolveDeps, "onWorkflowCall"); },
|
|
583
668
|
get appendEntry() { return createLazy(resolveDeps, "appendEntry"); },
|
|
584
669
|
get log() { return createLazy(resolveDeps, "log"); },
|
|
585
670
|
};
|
|
586
671
|
|
|
587
|
-
return { state: domainState, lazyDeps,
|
|
672
|
+
return { state: domainState, lazyDeps, isScriptRunning };
|
|
588
673
|
}
|