@zhushanwen/subagent-core 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/dist/{chunk-OL5BK4VN.js → chunk-LZJOEDGQ.js} +455 -203
  2. package/dist/engine-discovery-scan-Da4Dkzsi.d.cts +818 -0
  3. package/dist/engine-discovery-scan-Da4Dkzsi.d.ts +818 -0
  4. package/dist/execution/engine/engine-discovery-scan.cjs +216 -155
  5. package/dist/execution/engine/engine-discovery-scan.d.cts +1 -2
  6. package/dist/execution/engine/engine-discovery-scan.d.ts +1 -2
  7. package/dist/execution/engine/engine-discovery-scan.js +1 -1
  8. package/dist/execution/relay-env.js +5 -4
  9. package/dist/index.cjs +7686 -5894
  10. package/dist/index.d.cts +1876 -1360
  11. package/dist/index.d.ts +1876 -1360
  12. package/dist/index.js +7048 -5493
  13. package/dist.bundle/index.cjs +7747 -5957
  14. package/package.json +4 -4
  15. package/src/__tests__/manifest-store.test.ts +16 -15
  16. package/src/__tests__/robustness-medium-batch1.test.ts +10 -14
  17. package/src/__tests__/robustness-medium-batch2.test.ts +7 -2
  18. package/src/execution/__tests__/alive-store.test.ts +117 -1
  19. package/src/execution/__tests__/batch-finalized.test.ts +206 -0
  20. package/src/execution/__tests__/chat-engine-routing.test.ts +117 -1
  21. package/src/execution/__tests__/{cold-resurrect.test.ts → cold-lookup.test.ts} +133 -33
  22. package/src/execution/__tests__/collect-coordinator-service.test.ts +7 -5
  23. package/src/execution/__tests__/conversation-continuation.test.ts +1452 -0
  24. package/src/execution/__tests__/delivery-methods.test.ts +90 -176
  25. package/src/execution/__tests__/dialog-queue.test.ts +70 -3
  26. package/src/execution/__tests__/dispose-manifest-recovery.test.ts +475 -0
  27. package/src/execution/__tests__/execute-and-await-worktree.test.ts +3 -3
  28. package/src/execution/__tests__/execute-nesting.test.ts +8 -5
  29. package/src/execution/__tests__/execution-record.test.ts +8 -148
  30. package/src/execution/__tests__/finalize-record.test.ts +295 -129
  31. package/src/execution/__tests__/get-record-for-action-restart.test.ts +21 -29
  32. package/src/execution/__tests__/helpers/fake-engine-port.ts +13 -68
  33. package/src/execution/__tests__/helpers/legacy-sidecar.ts +33 -0
  34. package/src/execution/__tests__/helpers/subagent-service-mocks.ts +9 -5
  35. package/src/execution/__tests__/idle-gc.test.ts +60 -0
  36. package/src/execution/__tests__/lifecycle-manager.test.ts +3 -254
  37. package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +43 -22
  38. package/src/execution/__tests__/model-config-service.test.ts +111 -0
  39. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +10 -6
  40. package/src/execution/__tests__/notify-ledger.test.ts +42 -1
  41. package/src/execution/__tests__/rebuild-indexes.test.ts +329 -0
  42. package/src/execution/__tests__/record-binding.test.ts +613 -0
  43. package/src/execution/__tests__/record-origin.test.ts +318 -0
  44. package/src/execution/__tests__/record-store-intent-api.test.ts +600 -0
  45. package/src/execution/__tests__/record-store-orphan-revive.test.ts +71 -3
  46. package/src/execution/__tests__/record-store.test.ts +72 -54
  47. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +8 -5
  48. package/src/execution/__tests__/round-supervisor-workflow-origin.test.ts +205 -0
  49. package/src/execution/__tests__/run-orchestration-write-lease.test.ts +293 -0
  50. package/src/execution/__tests__/run-orchestration.test.ts +199 -0
  51. package/src/execution/__tests__/session-file-gc.test.ts +35 -4
  52. package/src/execution/__tests__/session-reconstructor.test.ts +78 -0
  53. package/src/execution/__tests__/state-marker.test.ts +288 -0
  54. package/src/execution/__tests__/stream-sink-retirement.test.ts +8 -5
  55. package/src/execution/__tests__/subagent-actions-core.test.ts +67 -16
  56. package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +0 -1
  57. package/src/execution/__tests__/subagent-service-notify-gate.test.ts +27 -11
  58. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +17 -11
  59. package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +130 -53
  60. package/src/execution/__tests__/subagent-service.test.ts +17 -215
  61. package/src/execution/__tests__/subprocess-agent-runner-no-progress-killall.test.ts +138 -0
  62. package/src/execution/__tests__/subprocess-agent-runner.test.ts +105 -660
  63. package/src/execution/__tests__/sync-collect-recovery.test.ts +23 -16
  64. package/src/execution/__tests__/terminal-write-latency.test.ts +79 -0
  65. package/src/execution/__tests__/workflow-agent-dispatch.test.ts +782 -0
  66. package/src/execution/alive-store.ts +37 -28
  67. package/src/execution/{cold-resurrect.ts → cold-lookup.ts} +48 -45
  68. package/src/execution/config.ts +1 -1
  69. package/src/execution/conversation-continuation.ts +541 -0
  70. package/src/execution/dialog-queue.ts +60 -12
  71. package/src/execution/engine/__tests__/common/capability-gate.test.ts +1 -1
  72. package/src/execution/engine/__tests__/common/journal-wiring.test.ts +16 -1
  73. package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/fake-engine-protocol.mjs +17 -51
  74. package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/smoke-run.fixture.json +0 -5
  75. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +1 -1
  76. package/src/execution/engine/__tests__/conformance/protocol-blackbox.test.ts +5 -9
  77. package/src/execution/engine/__tests__/engine-discovery-scan.test.ts +0 -3
  78. package/src/execution/engine/__tests__/engine-discovery.test.ts +0 -3
  79. package/src/execution/engine/__tests__/model-prompt.test.ts +0 -3
  80. package/src/execution/engine/__tests__/registry.test.ts +0 -1
  81. package/src/execution/engine/__tests__/routing.test.ts +0 -1
  82. package/src/execution/engine/__tests__/run-failure-worktree-cleanup.test.ts +0 -3
  83. package/src/execution/engine/client/__tests__/__fixtures__/fake-engine.mjs +0 -3
  84. package/src/execution/engine/client/__tests__/engine-client-reap.test.ts +212 -0
  85. package/src/execution/engine/client/__tests__/mirror.test.ts +73 -1
  86. package/src/execution/engine/client/__tests__/protocol-closure.test.ts +2 -9
  87. package/src/execution/engine/client/__tests__/remote-engine.test.ts +46 -12
  88. package/src/execution/engine/client/engine-client.ts +81 -25
  89. package/src/execution/engine/client/mirror.ts +9 -0
  90. package/src/execution/engine/client/remote-engine.ts +52 -34
  91. package/src/execution/engine/client/reverse-router.ts +6 -27
  92. package/src/execution/engine/common/__tests__/run-signals.test.ts +79 -0
  93. package/src/execution/engine/common/capability-gate.ts +27 -0
  94. package/src/execution/engine/common/data-dir.ts +3 -2
  95. package/src/execution/engine/common/errors.ts +1 -1
  96. package/src/execution/engine/common/journal-wiring.ts +18 -9
  97. package/src/execution/engine/common/run-signals.ts +83 -0
  98. package/src/execution/engine/d8-compat.ts +1 -1
  99. package/src/execution/engine/host/__tests__/host-bridge.test.ts +3 -18
  100. package/src/execution/engine/host/host-bridge.ts +19 -41
  101. package/src/execution/engine/host/pi-host-binding.ts +5 -5
  102. package/src/execution/engine/port.ts +34 -54
  103. package/src/execution/engine/types.ts +11 -14
  104. package/src/execution/execution-record.ts +15 -47
  105. package/src/execution/finalize-record.ts +94 -197
  106. package/src/execution/idle-gc.ts +9 -3
  107. package/src/execution/lifecycle-manager.ts +25 -361
  108. package/src/execution/lifecycle-predicates.ts +22 -19
  109. package/src/execution/manifest-store.ts +40 -42
  110. package/src/execution/model-config-service.ts +1 -1
  111. package/src/execution/notifier.ts +39 -1
  112. package/src/execution/notify-host.ts +13 -4
  113. package/src/execution/record-entry.ts +20 -2
  114. package/src/execution/record-store.ts +925 -160
  115. package/src/execution/relay-env.ts +8 -3
  116. package/src/execution/round-supervisor/domain.ts +17 -5
  117. package/src/execution/round-supervisor/service-binding.test.ts +89 -53
  118. package/src/execution/round-supervisor/service-binding.ts +71 -21
  119. package/src/execution/round-supervisor/supervisor.ts +23 -9
  120. package/src/execution/service/record-access.ts +547 -0
  121. package/src/execution/service/record-lifecycle.ts +542 -0
  122. package/src/execution/service/run-orchestration.ts +1647 -0
  123. package/src/execution/service/service-bootstrap.ts +111 -0
  124. package/src/execution/service/service-constants.ts +27 -0
  125. package/src/execution/service/session-baselines.ts +401 -0
  126. package/src/execution/service/sync-collect-domain.ts +442 -0
  127. package/src/execution/service/workflow-dispatch.ts +567 -0
  128. package/src/execution/session-file-gc.ts +11 -8
  129. package/src/execution/session-reconstructor.ts +44 -1
  130. package/src/execution/sessions-index.ts +20 -3
  131. package/src/execution/settled-watchdog.ts +86 -43
  132. package/src/execution/state-marker.ts +516 -0
  133. package/src/execution/stream-sink.ts +1 -1
  134. package/src/execution/subagent-actions-core.ts +53 -31
  135. package/src/execution/subagent-service.ts +643 -2980
  136. package/src/execution/subprocess-agent-runner.ts +46 -323
  137. package/src/execution/types.ts +60 -43
  138. package/src/execution/ui-request-handler-factory.ts +5 -0
  139. package/src/execution/worktree-manager.ts +1 -1
  140. package/src/execution/worktree-registry.ts +1 -1
  141. package/src/index.ts +6 -5
  142. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +2 -3
  143. package/src/orchestration/__tests__/agent-call-stream.test.ts +24 -49
  144. package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +4 -4
  145. package/src/orchestration/__tests__/file-run-store.test.ts +5 -7
  146. package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +2 -3
  147. package/src/orchestration/__tests__/run-snapshot.test.ts +27 -43
  148. package/src/orchestration/__tests__/worker-message-pump-workflow-dispatch.test.ts +274 -0
  149. package/src/orchestration/execute-agent-call.ts +1 -1
  150. package/src/orchestration/file-run-store.ts +1 -1
  151. package/src/orchestration/models/ports.ts +20 -13
  152. package/src/orchestration/models/types.ts +11 -13
  153. package/src/orchestration/run-snapshot.ts +18 -22
  154. package/src/orchestration/worker-message-pump.ts +33 -57
  155. package/src/shared/atomic-write.ts +10 -8
  156. package/dist/chunk-T2SYBW3I.js +0 -24
  157. package/dist/engine-discovery-scan-BXLA8y-z.d.cts +0 -1624
  158. package/dist/engine-discovery-scan-BXLA8y-z.d.ts +0 -1624
  159. package/src/execution/__tests__/chat-round-first-round-watchdog.test.ts +0 -255
  160. package/src/execution/__tests__/finalized-marker.test.ts +0 -104
  161. package/src/execution/__tests__/lifecycle-manager-lock.test.ts +0 -211
  162. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +0 -488
  163. package/src/execution/__tests__/subprocess-agent-runner-timeout.test.ts +0 -75
  164. package/src/execution/__tests__/tombstone-store.test.ts +0 -73
  165. package/src/execution/engine/__tests__/conformance/chat-round-protocol.test.ts +0 -230
  166. package/src/execution/finalized-marker.ts +0 -70
  167. package/src/execution/tombstone-store.ts +0 -72
@@ -0,0 +1,274 @@
1
+ // src/orchestration/__tests__/worker-message-pump-workflow-dispatch.test.ts
2
+ //
3
+ // [H2 W3] pump 切换 executeWorkflowAgent 端到端单测(设计
4
+ // subagent-workflow-record-unification.md §3.5 终态数据流 / §5 W3)。
5
+ //
6
+ // 真实链路形态:handleWorkerMessage(agent-call) → dispatchAgentCall →
7
+ // deps.workflowAgentDispatch(SubagentService.executeWorkflowAgent 注入形态)→
8
+ // fake pi EnginePort(协议 seam 替身,与 W2 workflow-agent-dispatch.test 同源)。
9
+ //
10
+ // 锁四组面:
11
+ // 1. record 进 store:dispatch 在途时 record 带 origin:"workflow" +
12
+ // parentRunId=run.runId(真实 record 经 store 治理面可见——进度源切换的
13
+ // 数据前提);record 级 pending:register 照旧发射。
14
+ // 2. worker 协议回包不变:引擎应答 → executeAgentCall finalize →
15
+ // postMessage({type:"agent-result", callId, result})(cached:false);
16
+ // trace node 终态摘要保真(node.result 来自 executeWorkflowAgent 的
17
+ // AgentResult 出口,D2「node 保留 result」)。
18
+ // 3. 成功即终态化(D7 联动回归):settle 后 record closed/gc + record 级
19
+ // pending:unregister(archive 出内存,run 视图经磁盘面回查终态)。
20
+ // 4. 注入面回退:未注入 workflowAgentDispatch(旧测试 deps)→ deps.runner.run
21
+ // 被调(SAR 旧编排路径,W4 归位;两路互不串扰)。
22
+ //
23
+ // 替身形态与测试红线对齐 W2 workflow-agent-dispatch.test.ts:XYZ_AGENT_DATA_DIR
24
+ // 指 tmpdir(不触真实数据目录);logger mock 防噪声。
25
+
26
+ import * as fs from "node:fs";
27
+ import * as os from "node:os";
28
+ import * as path from "node:path";
29
+
30
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
31
+
32
+ const { loggerMock } = vi.hoisted(() => ({
33
+ loggerMock: { debug: vi.fn(), warn: vi.fn(), error: vi.fn() },
34
+ }));
35
+ vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
36
+
37
+ import { Budget } from "../models/budget.ts";
38
+ import { RunRuntime } from "../models/run-runtime.ts";
39
+ import { Trace } from "../models/trace.ts";
40
+ import { WorkflowRun } from "../models/workflow-run.ts";
41
+ import type { LifecycleDeps, WorkerHandlers } from "../models/ports.ts";
42
+ import type { AgentCallOpts } from "../models/types.ts";
43
+ import type { WorkerHandle } from "../worker-handle.ts";
44
+ import { handleWorkerMessage } from "../worker-message-pump.ts";
45
+ import { ModelConfigService } from "../../execution/model-config-service.ts";
46
+ import type { ModelInfo, ModelRegistryLike } from "../../execution/model-resolver.ts";
47
+ import type { RecordStore } from "../../execution/record-store.ts";
48
+ import { SubagentService } from "../../execution/subagent-service.ts";
49
+ import type { PiLike } from "../../execution/subagent-service.ts";
50
+ import { clearEngines } from "../../execution/engine/registry.ts";
51
+ import { registerFakePiEngine, type FakePiEnginePort } from "../../execution/__tests__/helpers/fake-engine-port.ts";
52
+
53
+ // ── harness ──────────────────────────────────────────────────
54
+
55
+ function makeEmptyRegistry(): ModelRegistryLike {
56
+ return { getAvailable: () => [], find: () => undefined, hasConfiguredAuth: () => true };
57
+ }
58
+
59
+ const ctxModel: ModelInfo = { id: "m", name: "M", provider: "p", reasoning: false };
60
+
61
+ interface PumpHarness {
62
+ service: SubagentService;
63
+ store: RecordStore;
64
+ pi: { appendEntry: ReturnType<typeof vi.fn>; events: { emit: ReturnType<typeof vi.fn> }; sendMessage: ReturnType<typeof vi.fn> };
65
+ fake: FakePiEnginePort;
66
+ run: WorkflowRun;
67
+ postMessage: ReturnType<typeof vi.fn>;
68
+ deps: LifecycleDeps;
69
+ tmpRoot: string;
70
+ }
71
+
72
+ function makePumpHarness(runId: string): PumpHarness {
73
+ const tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "wf-pump-it-"));
74
+ process.env.XYZ_AGENT_DATA_DIR = path.join(tmpRoot, "engine-data");
75
+ const agentDir = path.join(tmpRoot, "agent");
76
+ const modelService = new ModelConfigService({ agentDir, cwd: agentDir });
77
+ modelService.initModel({ modelRegistry: makeEmptyRegistry(), sessionId: "wf-pump-it", ctxModel });
78
+ const service = new SubagentService({ cwd: agentDir, modelService });
79
+ const pi = {
80
+ appendEntry: vi.fn(),
81
+ events: { emit: vi.fn() },
82
+ sendMessage: vi.fn(),
83
+ };
84
+ service.initSession({ pi: pi as unknown as PiLike, sessionId: "wf-pump-it" });
85
+ clearEngines();
86
+ const fake = registerFakePiEngine();
87
+
88
+ const run = new WorkflowRun(
89
+ runId,
90
+ { scriptName: "test-wf", scriptSource: "agent('调研 A')", args: {}, scriptPath: "/tmp/test.js" },
91
+ { status: "running", budget: new Budget({ maxTokens: 100_000 }), calls: new Map(), trace: new Trace(), errorLogs: [] },
92
+ { startedAt: new Date().toISOString() },
93
+ );
94
+ const postMessage = vi.fn();
95
+ const initialWorker = { postMessage, terminate: vi.fn(async () => {}) } as unknown as WorkerHandle;
96
+ run.assignRuntime(new RunRuntime(initialWorker, new AbortController()));
97
+
98
+ // 组合根注入形态(extension index.ts makeDeps 的等价内联):dispatch 闭包捕获
99
+ // service 单例,parentRunId 由 pump 侧补 run.runId。
100
+ const deps = {
101
+ store: { save: vi.fn(async () => {}) },
102
+ workerHost: { start: vi.fn() },
103
+ runner: { run: vi.fn(async () => ({ content: "legacy-runner", durationMs: 1, error: undefined, toolCalls: [] })) },
104
+ runs: new Map([[runId, run]]),
105
+ eventBus: { emit: vi.fn() },
106
+ log: vi.fn(),
107
+ workflowAgentDispatch: (opts: AgentCallOpts, parentRunId: string, signal?: AbortSignal) =>
108
+ service.executeWorkflowAgent(opts, parentRunId, signal),
109
+ } as unknown as LifecycleDeps;
110
+ return { service, store: Reflect.get(service, "store") as RecordStore, pi, fake, run, postMessage, deps, tmpRoot };
111
+ }
112
+
113
+ function makeHandlers(): WorkerHandlers {
114
+ return {
115
+ onMessage: vi.fn(async () => {}),
116
+ onError: vi.fn(async () => {}),
117
+ onExit: vi.fn(async () => {}),
118
+ } as unknown as WorkerHandlers;
119
+ }
120
+
121
+ /** 微任务冲刷(dispatch → 路由 → record 注册 → acquire → engine.run 链)。 */
122
+ async function flush(times = 8): Promise<void> {
123
+ for (let i = 0; i < times; i++) {
124
+ await new Promise<void>((resolve) => setImmediate(resolve));
125
+ }
126
+ }
127
+
128
+ function findAgentResultPost(postMessage: ReturnType<typeof vi.fn>, callId: number) {
129
+ return postMessage.mock.calls
130
+ .map((c) => c[0] as { type?: string; callId?: number; result?: { content?: string; error?: string }; cached?: boolean })
131
+ .find((m) => m.type === "agent-result" && m.callId === callId);
132
+ }
133
+
134
+ let prevDataDirEnv: string | undefined;
135
+
136
+ beforeEach(() => {
137
+ prevDataDirEnv = process.env["XYZ_AGENT_DATA_DIR"];
138
+ });
139
+
140
+ afterEach(() => {
141
+ vi.restoreAllMocks();
142
+ if (prevDataDirEnv === undefined) delete process.env["XYZ_AGENT_DATA_DIR"];
143
+ else process.env["XYZ_AGENT_DATA_DIR"] = prevDataDirEnv;
144
+ });
145
+
146
+ // ── 1+2+3:注入 dispatch 的端到端主路径 ───────────────────────
147
+
148
+ describe("pump → executeWorkflowAgent 端到端", () => {
149
+ it("record 进 store(origin=workflow + parentRunId)→ settle → 回包/终态摘要/D7 终态化全链", async () => {
150
+ const h = makePumpHarness("wf-pump-e2e-1");
151
+ await handleWorkerMessage(
152
+ h.run,
153
+ { type: "agent-call", callId: 1, opts: { prompt: "调研 A", description: "research-a" } },
154
+ h.deps,
155
+ makeHandlers(),
156
+ );
157
+ await flush();
158
+
159
+ // 1. dispatch 在途:真实 record 在 store(origin/parentRunId——views store 订阅的数据
160
+ // 前提)。经 views 同款查询面(queries.collectRecordsByParentRunId)断言命中,
161
+ // 再经 getMutable 拿真 record 验 originFields(listRunning 是 RecordSnapshot
162
+ // 轻投影,无 origin 字段——勿用)。
163
+ const found = h.service.queries.collectRecordsByParentRunId("wf-pump-e2e-1", 100);
164
+ expect(found).toHaveLength(1);
165
+ expect(found[0]!.origin).toBe("workflow");
166
+ expect(found[0]!.parentRunId).toBe("wf-pump-e2e-1");
167
+ expect(found[0]!.status).toBe("running");
168
+ const record = h.store.getMutable(found[0]!.id)!;
169
+ expect(record.origin).toBe("workflow");
170
+ expect(record.parentRunId).toBe("wf-pump-e2e-1");
171
+ // 引擎收到 run(taskId = record.id——守护/journal 键,W2 已锁定)
172
+ expect(h.fake.runs).toHaveLength(1);
173
+ // record 级 pending:register 照旧(D5:record 域发射面不动)
174
+ const registered = h.pi.events.emit.mock.calls.find((c) => c[0] === "pending:register");
175
+ expect(registered?.[1]).toMatchObject({ id: record.id, type: "subagent" });
176
+
177
+ // 2. 引擎应答 → worker 协议回包 + trace node 终态摘要
178
+ // (settle 后链路含 journal.close 的真 fs await,用 waitFor 收敛)
179
+ h.fake.runs[0]!.settle({ content: "ok", sessionId: "sess-1", sessionFile: "/tmp/sess-1.jsonl", durationMs: 5 });
180
+ await vi.waitFor(() => {
181
+ expect(findAgentResultPost(h.postMessage, 1)).toBeDefined();
182
+ });
183
+
184
+ const posted = findAgentResultPost(h.postMessage, 1);
185
+ expect(posted).toBeDefined();
186
+ expect(posted!.cached).toBe(false);
187
+ expect(posted!.result!.content).toBe("ok");
188
+ expect(posted!.result!.error).toBeUndefined();
189
+ // node 终态摘要(D2:live 删除后 node 保留 result——数据源 = executeWorkflowAgent
190
+ // 的 AgentResult 出口,经 executeAgentCall finalizeCall 写入)
191
+ const node = h.run.state.trace.find(1);
192
+ expect(node?.status).toBe("completed");
193
+ expect(node?.result?.content).toBe("ok");
194
+ expect(node?.result?.sessionFile).toBe("/tmp/sess-1.jsonl");
195
+ expect(node?.completedAt).toBeDefined();
196
+ expect(h.run.state.calls.get(1)?.status).toBe("done");
197
+
198
+ // 3. D7 联动:成功即终态化(closed/gc + archive 出内存)+ record 级 unregister。
199
+ // (archive 后从磁盘重建面回查终态的「run 视图不丢行」行为由 W1 record-store
200
+ // 的 mergedRecords 测试锁定——本 harness 的 pi mock 不落盘,不在此重复。)
201
+ expect(h.store.listRunning()).toHaveLength(0);
202
+ const unregistered = h.pi.events.emit.mock.calls.find((c) => c[0] === "pending:unregister");
203
+ expect(unregistered?.[1]).toMatchObject({ id: record.id });
204
+
205
+ // 4. run 级 pending 配对不回归(D5:lifecycle↔pump 的 run 级注册/注销对零改动
206
+ // ——lifecycle.ts 零 diff 由 git 自查;此处锁 pump 侧 run 终态注销行为):
207
+ // 脚本 return → finalizeRun emit pending:unregister with id=runId。
208
+ await handleWorkerMessage(h.run, { type: "return", result: "done" }, h.deps, makeHandlers());
209
+ const runUnregister = h.deps.eventBus!.emit as ReturnType<typeof vi.fn>;
210
+ const runUnregCall = runUnregister.mock.calls.find((c) => c[0] === "pending:unregister");
211
+ expect(runUnregCall?.[1]).toMatchObject({ id: "wf-pump-e2e-1" });
212
+ expect(h.run.state.status).toBe("done");
213
+ });
214
+
215
+ it("失败结果:引擎 error outcome → 回包 error + trace failed 终态摘要", async () => {
216
+ const h = makePumpHarness("wf-pump-e2e-2");
217
+ await handleWorkerMessage(
218
+ h.run,
219
+ { type: "agent-call", callId: 2, opts: { prompt: "调研 B", description: "research-b" } },
220
+ h.deps,
221
+ makeHandlers(),
222
+ );
223
+ await flush();
224
+ expect(h.fake.runs).toHaveLength(1);
225
+
226
+ // 失败结果带巨额 usage 打爆 run 预算(Budget maxTokens=100k)——executeAgentCall
227
+ // 的「预算超限不重试」分支直接 finalize(否则 error 结果走 1s/2s 退避重试,
228
+ // 测试需 fake timers 推进;重试语义已由 execute-agent-call.test.ts 锁定,此处
229
+ // 聚焦 pump 切换后的回包/终态摘要)。
230
+ h.fake.runs[0]!.settle({ content: "", error: "engine blew up", durationMs: 3, usage: { input: 1_000_000, output: 1, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 1 } });
231
+ await vi.waitFor(() => {
232
+ expect(findAgentResultPost(h.postMessage, 2)).toBeDefined();
233
+ });
234
+
235
+ const posted = findAgentResultPost(h.postMessage, 2);
236
+ expect(posted).toBeDefined();
237
+ expect(posted!.result!.error).toBe("engine blew up");
238
+ const node = h.run.state.trace.find(2);
239
+ expect(node?.status).toBe("failed");
240
+ expect(node?.result?.error).toBe("engine blew up");
241
+ expect(h.run.state.calls.get(2)?.status).toBe("done");
242
+ });
243
+ });
244
+
245
+ // ── 4:注入面回退(旧 deps 不注入 dispatch) ──────────────────
246
+
247
+ describe("workflowAgentDispatch 未注入回退", () => {
248
+ it("旧 deps(仅 runner)→ deps.runner.run 被调([H2 W4] SAR.run 已掏空为纯转调 executeWorkflowAgent,两分支执行体归一;生产恒注入 dispatch 本分支不可达,仅为旧测试 deps 形态兼容)", async () => {
249
+ const run = new WorkflowRun(
250
+ "wf-pump-fallback-1",
251
+ { scriptName: "test-wf", scriptSource: "agent('hi')", args: {}, scriptPath: "/tmp/test.js" },
252
+ { status: "running", budget: new Budget(), calls: new Map(), trace: new Trace(), errorLogs: [] },
253
+ { startedAt: new Date().toISOString() },
254
+ );
255
+ const runnerRun = vi.fn(async () => ({ content: "legacy", durationMs: 1, error: undefined, toolCalls: [] }));
256
+ const postMessage = vi.fn();
257
+ const deps = {
258
+ store: { save: vi.fn(async () => {}) },
259
+ workerHost: { start: vi.fn() },
260
+ runner: { run: runnerRun },
261
+ runs: new Map(),
262
+ eventBus: { emit: vi.fn() },
263
+ log: vi.fn(),
264
+ } as unknown as LifecycleDeps;
265
+ run.assignRuntime(new RunRuntime({ postMessage, terminate: vi.fn(async () => {}) } as unknown as WorkerHandle, new AbortController()));
266
+
267
+ await handleWorkerMessage(run, { type: "agent-call", callId: 7, opts: { prompt: "p" } }, deps, makeHandlers());
268
+ await flush();
269
+
270
+ expect(runnerRun).toHaveBeenCalledTimes(1);
271
+ const posted = findAgentResultPost(postMessage, 7);
272
+ expect(posted?.result?.content).toBe("legacy");
273
+ });
274
+ });
@@ -127,7 +127,7 @@ function delay(ms: number): Promise<void> {
127
127
  * @param budget Budget 值对象(consume + isExceeded 检查)
128
128
  * @param signal AbortSignal(runner.run 传播;abort 后不重试)
129
129
  * @param trace Trace 值对象(finalizeCall 时 update)
130
- * @param onEvent 实时事件回调(live record 更新用)
130
+ * @param onEvent 透传 service 派发路径(journal 转发 + 守护刷新源)
131
131
  * @param stream streaming sink(透传 runner.run)
132
132
  * @param isOrphaned 孤儿判定谓词(OB2,可选,默认恒 false):true 时 finalizeCall
133
133
  * 跳过 trace.update(判定语义与正确性论证见 finalizeCall 文档注释)。递归重试
@@ -240,7 +240,7 @@ export class FileRunStore implements RunStore {
240
240
  // mkdir recursive 每次 save 前执行:幂等零成本(目录已存在时仅一次 stat),
241
241
  // 且免「构造时预建」——构造时建会在宿主尚未 configureCore 的窗口抛错。
242
242
  await mkdir(this.stateDir(), { recursive: true });
243
- // toRunSnapshot 补 v 字段(D4 裁决②写入侧)+ strip live 落盘
243
+ // toRunSnapshot 补 v 字段(D4 裁决②写入侧);live strip 已随 [H2 W3] live 字段删除退役
244
244
  const line = JSON.stringify(toRunSnapshot(run));
245
245
  await appendFile(this.stateFilePath(run.runId), line + "\n", "utf8");
246
246
  if (isTerminal) {
@@ -10,7 +10,7 @@
10
10
  *
11
11
  * 层归属:Engine。零 infra 依赖(AC-1)。
12
12
  */
13
- import type { StreamSink, SubagentStream } from "../../execution/stream-sink.ts";
13
+ import type { SubagentStream } from "../../execution/stream-sink.ts";
14
14
  import type { AgentEvent } from "../../shared/agent-event.ts";
15
15
  import type { WorkerHandle } from "../worker-handle.ts";
16
16
  import type { RunSpec } from "./run-spec.ts";
@@ -22,11 +22,11 @@ import type { WorkflowRun } from "./workflow-run.ts";
22
22
  /**
23
23
  * Agent 子进程执行 port。Infra 实现:SubprocessAgentRunner。
24
24
  *
25
- * run 执行单次 agent 调用(委托 SubagentService.executeAndAwait),返回结构化结果。
26
- * signal 用于 abort 传播。
25
+ * run 执行单次 agent 调用(委托 SubagentService.executeWorkflowAgent,[H2 W4] 纯转调),
26
+ * 返回结构化结果。signal 用于 abort 传播。
27
27
  *
28
- * onEvent(可选):强类型 AgentEvent 回调,供调用方实时更新 live record 供 TUI 展示进度。
29
- * 不传则不回调(向后兼容;现有调用点不传不受影响)。
28
+ * onEvent(可选):强类型 AgentEvent 回调,透传 service 派发路径(journal 转发/守护
29
+ * 刷新源),生产 pump 侧恒 undefined。
30
30
  *
31
31
  * D-005: onEvent 签名从 raw Record<string,unknown> 升级为 AgentEvent——委托后不再有
32
32
  * raw JSONL 中间层(executeAndAwait 直接出 AgentEvent,session-runner handleSdkEvent 出口)。
@@ -153,12 +153,19 @@ export interface LifecycleDeps {
153
153
  parentRun: WorkflowRun,
154
154
  ) => Promise<unknown>;
155
155
  /**
156
- * UI streaming sink(ctx.ui.setWidget),workflow agent call 创建 SubagentStream 用。
157
- *
158
- * Interface makeDeps 注入(从 SubagentService.getStreamSink() 取)。
159
- * dispatchAgentCall 用它创建 SubagentStream(widgetKey=subagent-stream-<runId>-<stepIndex>),
160
- * 使 workflow agent call 的 text_delta 走与 background subagent 相同的 streaming 链路。
161
- * 可选——无 UI 模式(TUI/RPC setWidget)时为 undefined,dispatchAgentCall 不创建 stream。
162
- */
163
- streamSink?: StreamSink;
156
+ * [H2 W3] workflow agent() 统一派发入口(SubagentService.executeWorkflowAgent
157
+ * deps 注入形态,设计 §3.5 终态数据流)。窄函数类型——不引 execution 层具体类,
158
+ * 保持本 ports 文件零 infra/execution 依赖。由组合根(extension index.ts makeDeps)
159
+ * 注入:闭包捕获 getSubagentService() 单例,parentRunId 由 pump 侧补 run.runId
160
+ *
161
+ * 可选——未注入时(旧测试 deps)dispatchAgentCall 回退 deps.runner([H2 W4]
162
+ * SAR 已掏空为纯转调 executeWorkflowAgent,两分支执行体归一非双轨,差异仅 parentRunId
163
+ * 来源:注入 = 真实 run.runId,回退 = SAR_UNATTACHED_PARENT_RUN_ID 占位;生产装配
164
+ * 两字段同时注入,dispatch 恒优先)。
165
+ */
166
+ workflowAgentDispatch?: (
167
+ opts: AgentCallOpts,
168
+ parentRunId: string,
169
+ signal?: AbortSignal,
170
+ ) => Promise<AgentResult>;
164
171
  }
@@ -20,7 +20,7 @@ import type {
20
20
  ToolCallEntry,
21
21
  } from "@zhushanwen/subagent-engine-sdk";
22
22
 
23
- import type { ExecutionRecord, WorktreeHandle } from "../../execution/types.ts";
23
+ import type { WorktreeHandle } from "../../execution/types.ts";
24
24
 
25
25
  // ── 状态机 ────────────────────────────────────────────────────
26
26
 
@@ -76,7 +76,8 @@ export function canRunTransition(from: RunStatus, to: RunStatus): boolean {
76
76
  * 历史值 20 偏紧——描述性 slug 如 "audit-structured-output"(23)/ "fix-subagent-wf-tools"(21)
77
77
  * 会撞上限,放宽到 35 兼顾「短到能塞进 TUI 标题行」与「容纳合理描述性 kebab-case 名」。
78
78
  * 放本文件的原因:约束对象是 AgentCallOpts.description(slug 的源字段,见下方 slug 派生说明),
79
- * 与字段同文件;worker-message-pumplive record slug 截断)与壳侧 tool schema maxLength 共享引用。
79
+ * 与字段同文件;subagent-actions-coreslug 校验)、subagent-service(record slug 截断)
80
+ * 与壳侧 tool schema maxLength 共享引用。
80
81
  */
81
82
  export const SLUG_MAX_LENGTH = 35;
82
83
 
@@ -351,20 +352,17 @@ export interface ExecutionTraceNode {
351
352
  */
352
353
  sessionId?: string;
353
354
  /**
354
- * Session JSONL 绝对路径。finalizeCall 从 result.sessionFile 透传。
355
- * 持久化到快照(serializeRun),跨 session 重水合后保留。
356
- */
355
+ * Session JSONL 绝对路径。finalizeCall 从 result.sessionFile 透传。
356
+ * 持久化到快照(serializeRun),跨 session 重水合后保留。
357
+ */
357
358
  sessionFile?: string;
358
- /**
359
- * Live 执行进度对象(running 时存在,done 时由 dispatchAgentCall 清除)。
360
- *
361
- * 挂在 node 上(D-10 单源延伸:AgentCall.traceNode 与 Trace.nodes 共享同一引用)。
362
- * TUI 通过 trace.toArray() 读 node.live,派生 getEventLog/getCurrentActivity 实时展示。
363
- * 不持久化(序列化时 strip;重跑时由 dispatchAgentCall 重建)。
364
- */
365
- live?: ExecutionRecord;
366
359
  }
367
360
 
361
+ // [H2 W3] `live?: ExecutionRecord` 字段已删除(设计 subagent-workflow-record-
362
+ // unification.md D2:trace node 的 live 字段删除,只保留 result 终态摘要)。运行期
363
+ // 实时进度改由 views 经 store 查询 collectRecordsByParentRunId(parentRunId) 订阅
364
+ // 真实 record(origin:"workflow"),不再经 pump 旁路 progress record 挂载。
365
+
368
366
  /**
369
367
  * Trace.update 用的 patch(字段全可选)。
370
368
  *
@@ -4,8 +4,8 @@
4
4
  //
5
5
  // 为什么需要它:WorkflowRun 的 JSONL 快照投影此前两宿主各写一份(core
6
6
  // file-run-store.ts 的 toSnapshot/fromSnapshot 与 pi 壳 jsonl-run-store.ts 的
7
- // serializeRun/deserializeRun)——字段集一致但语义细节分叉(live-strip、版本
8
- // guard、budgetRef 剔除),修一处漏一处。本模块收敛为单源 codec:字段演进
7
+ // serializeRun/deserializeRun)——字段集一致但语义细节分叉(版本 guard、
8
+ // budgetRef 剔除),修一处漏一处。本模块收敛为单源 codec:字段演进
9
9
  // 单点(G2),两宿主(FileRunStore / pi JsonlRunStore)各自只保留 IO 策略
10
10
  // (rewrite/append/去抖),投影与版本衔接语义全部经此模块。
11
11
  //
@@ -103,10 +103,9 @@ export interface RunSnapshot {
103
103
  * WorkflowRun → 单行快照。
104
104
  *
105
105
  * - 补 v 字段(D4 裁决②:写入恒带当前版本)。
106
- * - strip live(防御内聚):calls[].traceNode trace 数组节点的 `live` 运行期
107
- * 对象剥除——ExecutionRecord 含可变 turns[]/controller,不可序列化且跨进程
108
- * 必死(重跑时由 dispatchAgentCall 重建);strip 产出新对象,不 mutate 内存
109
- * 中的 run(save 后 run 可继续跑)。
106
+ * - trace 节点全量序列化([H2 W3] live strip 分支随 ExecutionTraceNode.live 字段
107
+ * 删除而退役——节点不再携带运行期对象,无需防御性剥离;旧快照经 fromRunSnapshot
108
+ * 重水合时多余键自然丢弃,存量行零迁移)。
110
109
  * - spec.budgetRef 剔除:父 Budget 共享引用是进程内优化(嵌套 workflow 预算
111
110
  * 共享),非持久化数据;Budget 实例若混入 spec 落盘将退化为普通对象投影
112
111
  * (重水合后类型不符的脏字段)——重水合后 budget 从 state.budget 独立重建,
@@ -131,22 +130,19 @@ export function toRunSnapshot(run: WorkflowRun): RunSnapshot {
131
130
  usedCost: run.state.budget.usedCost,
132
131
  totalCallCount: run.state.budget.totalCallCount,
133
132
  },
134
- calls: Array.from(run.state.calls.values(), (c) => {
135
- // strip live(同 trace 序列化,不持久化运行期对象)
136
- const { live: _live, ...traceNodeRest } = c.traceNode;
137
- return {
138
- id: c.id,
139
- opts: c.opts,
140
- status: c.status,
141
- attempts: c.attempts,
142
- result: c.result,
143
- sessionId: c.sessionId,
144
- sessionFile: c.sessionFile,
145
- traceNode: traceNodeRest,
146
- };
147
- }),
148
- // trace 节点浅拷贝时 strip live 字段
149
- trace: run.state.trace.toArray().map(({ live: _live, ...rest }) => rest),
133
+ calls: Array.from(run.state.calls.values(), (c) => ({
134
+ id: c.id,
135
+ opts: c.opts,
136
+ status: c.status,
137
+ attempts: c.attempts,
138
+ result: c.result,
139
+ sessionId: c.sessionId,
140
+ sessionFile: c.sessionFile,
141
+ traceNode: c.traceNode,
142
+ })),
143
+ // trace 节点全量拷贝序列化(toArray 返回内部数组只读引用,拷贝为可变数组
144
+ // 落盘——节点不再携带运行期对象,无需防御性剥离)
145
+ trace: [...run.state.trace.toArray()],
150
146
  errorLogs: run.state.errorLogs,
151
147
  error: run.state.error,
152
148
  scriptResult: run.state.scriptResult,
@@ -9,7 +9,7 @@
9
9
  * pending:unregister → onRunDone」终态序列的唯一定义点(D5-② 单点化,收敛原
10
10
  * 8 处逐字复制;OR-8 收口步骤与 OR-4/B-4 双围栏内化于本函数)
11
11
  *
12
- * 4 个导出函数(domain-models.md §失败处理矩阵):
12
+ * 4 个 handle* 路由函数(domain-models.md §失败处理矩阵)+ 终态化/重建/防御 helper 若干:
13
13
  * - handleWorkerMessage(run, raw, deps, handlers) — 路由 agent_call/return/error/log
14
14
  * - handleWorkerError(run, err, deps, handlers) — worker uncaught error
15
15
  * - handleWorkerExit(run, code, handle, deps, handlers) — worker exit
@@ -39,14 +39,10 @@
39
39
 
40
40
  import { getLogger } from "../core/logger.ts";
41
41
 
42
- import { SLUG_MAX_LENGTH } from "./models/types.ts";
43
- import { createRecord, updateFromEvent } from "../execution/execution-record.ts";
44
- import { SubagentStream } from "../execution/stream-sink.ts";
45
- import type { AgentEvent } from "../shared/agent-event.ts";
46
42
  import { resolveAgentOpts } from "./agent-opts-resolver.ts";
47
43
  import { executeAgentCall } from "./execute-agent-call.ts";
48
44
  import { AgentCall } from "./models/agent-call.ts";
49
- import type { LifecycleDeps, WorkerHandlers } from "./models/ports.ts";
45
+ import type { AgentRunner, LifecycleDeps, WorkerHandlers } from "./models/ports.ts";
50
46
  import { RunRuntime } from "./models/run-runtime.ts";
51
47
  import type { WorkerLogEntry } from "./models/types.ts";
52
48
  import type { AgentCallOpts, AgentResult, DoneReason, ExecutionTraceNode } from "./models/types.ts";
@@ -400,7 +396,7 @@ function discardInFlightCalls(run: WorkflowRun): number[] {
400
396
  * result 以 IN_FLIGHT_CALL_CANCELLED_MSG 承载取消原因;
401
397
  * - trace 节点置 failed + 固定取消文案 + completedAt(「failed + Cancelled 文案」
402
398
  * 即取消的既有表达形态,不新增状态枚举);
403
- * - node.live 清除(终态 run TUI 轮询,防 ExecutionRecord 滞留)。
399
+ * - node 无运行期附属对象([H2 W3] trace.live 退役后节点只剩终态摘要字段)。
404
400
  *
405
401
  * 调用点约定:每个 transition("done") 成功后、store.save 之前——先收口再落盘,
406
402
  * 内存态与持久化快照在同一时点收敛(「run-snapshot 落盘前」的实现形态)。
@@ -419,7 +415,6 @@ export function closeOutInFlightCalls(run: WorkflowRun): number[] {
419
415
  if (call.status === "running") {
420
416
  call.markDone({ content: "", error: IN_FLIGHT_CALL_CANCELLED_MSG });
421
417
  }
422
- call.traceNode.live = undefined;
423
418
  run.state.trace.update(callId, {
424
419
  status: "failed",
425
420
  result: { content: "", error: IN_FLIGHT_CALL_CANCELLED_MSG },
@@ -697,24 +692,13 @@ function dispatchAgentCall(
697
692
  return;
698
693
  }
699
694
 
700
- // 构建 trace 节点 + live record(TUI 实时进度)
695
+ // 构建 trace 节点([H2 W3] trace.live 退役——实时进度改由 views 经 store 订阅
696
+ // collectRecordsByParentRunId(parentRunId) 查询真实 record,设计 D2;节点保留
697
+ // 终态摘要 result,终态由 executeAgentCall → finalizeCall 写入)。
701
698
  const agentName = msg.opts.description ?? msg.opts.agent ?? "unknown";
702
- // slug 复用 agentName(超长截断),live record 的 slug 仅用于 TUI 展示。
703
- const liveSlug = agentName.length > SLUG_MAX_LENGTH ? agentName.slice(0, SLUG_MAX_LENGTH) : agentName;
704
699
  const now = new Date().toISOString();
705
- // 未显式指定 model 的展示口径(live record 与 trace node 共用,两处一致)。
700
+ // 未显式指定 model 的展示口径。
706
701
  const model = msg.opts.model ?? "default";
707
- // live record:收口 agent 执行过程中的 text/thinking/toolCalls/usage,
708
- // 供 TUI 在 agent 运行期间显示进度(getEventLog/getCurrentActivity)。
709
- // 完成时由下方 .then 清除(终态由 node.result 承载)。
710
- const liveRecord = createRecord(String(msg.callId), {
711
- agent: agentName,
712
- model,
713
- mode: "background",
714
- task: msg.opts.prompt,
715
- slug: liveSlug,
716
- startedAt: Date.now(),
717
- });
718
702
  const node: ExecutionTraceNode = {
719
703
  stepIndex: msg.callId,
720
704
  agent: agentName,
@@ -723,7 +707,6 @@ function dispatchAgentCall(
723
707
  status: "running" as const,
724
708
  phase: msg.phase,
725
709
  startedAt: now,
726
- live: liveRecord,
727
710
  };
728
711
  run.state.trace.append(node);
729
712
 
@@ -750,8 +733,6 @@ function dispatchAgentCall(
750
733
  const errorResult: AgentResult = { content: "", error: resolved.error };
751
734
  call.markDone(errorResult);
752
735
  run.state.calls.set(msg.callId, call);
753
- // 无子进程执行,清除空 live record(终态由 result 承载)
754
- node.live = undefined;
755
736
  run.state.trace.update(msg.callId, {
756
737
  status: "failed",
757
738
  result: errorResult,
@@ -777,19 +758,20 @@ function dispatchAgentCall(
777
758
  // 后者已守 terminal(isTerminal)早期 return)。fallback new AbortController 已移除。
778
759
  const runtime = run.runtime!;
779
760
  const signal = runtime.controller.signal;
780
- // D-005: onEvent 签名升级——executeAndAwait 直接出 AgentEvent(强类型,
781
- // session-runner handleSdkEvent 出口),不再有 raw JSONL 中间层。
782
- // jsonlToAgentEvent 翻译——直接 updateFromEvent。
783
- // TUI tick 轮询 trace.toArray() node.live,无需显式通知。
784
- const onEvent = (event: AgentEvent): void => {
785
- updateFromEvent(liveRecord, event);
786
- };
787
- // 创建 streaming sink:widgetKey = subagent-stream-<runId>-<stepIndex>。
788
- // 复用 background subagent SubagentStream setWidget RPC 链路(agent-call-streaming-extension.md)。
789
- // streamSink 缺失(无 UI 模式)时 stream=undefined,executeAgentCall 正常执行不 streaming。
790
- const stream = deps.streamSink
791
- ? new SubagentStream(`${run.runId}-${msg.callId}`, deps.streamSink)
792
- : undefined;
761
+ // [H2 W3] 执行 port 切换(设计 §3.5 终态数据流:pump 薄化为「消息转调 + run 级
762
+ // 收尾」):workflowAgentDispatch = SubagentService.executeWorkflowAgent 的注入
763
+ // 形态——真实 record(origin:"workflow" + parentRunId=run.runId)进 store、事件
764
+ // 流经 service journal 接线进 record、streaming/守护/池归 service 派发路径。
765
+ // 未注入时(旧测试 deps)回退 deps.runner——[H2 W4] SAR.run 已掏空为纯转调
766
+ // executeWorkflowAgent,两分支执行体归一(同一 service 编排),仅 parentRunId
767
+ // 来源不同(注入路径闭包携带真实 run.runId;回退路径为 SAR 直调占位)。生产
768
+ // 装配恒注入 dispatch(extension index.ts makeDeps),回退分支生产不可达。
769
+ // 旁路 progress record 族(createRecord + updateFromEvent + SubagentStream +
770
+ // trace.live 挂载)随本切换整体退役——TUI/GUI 实时进度改从 store 订阅(D2)。
771
+ const dispatch = deps.workflowAgentDispatch;
772
+ const runner: AgentRunner = dispatch
773
+ ? { run: (rOpts, rSignal) => dispatch(rOpts, run.runId, rSignal) }
774
+ : deps.runner;
793
775
  // 原 gate.withSlot(fn, signal) 语义内联:pre-aborted 时 reject AbortError(
794
776
  // 下方 .catch 依赖此约定不记错),否则直接执行——并发调度归 ConcurrencyPool。
795
777
  const dispatchCall = async (): Promise<void> => {
@@ -798,22 +780,17 @@ function dispatchAgentCall(
798
780
  abortErr.name = "AbortError";
799
781
  throw abortErr;
800
782
  }
801
- try {
802
- // OB2(S7 残留):isOrphaned 谓词注入——旧代际 finalize 在 trace.update 前被
803
- // 拦截(判定语义与下方 .then/.catch 守卫同一 isOrphanedCall,详见
804
- // execute-agent-call.ts finalizeCall 文档注释)。
805
- await executeAgentCall(call, deps.runner, run.state.budget, signal, run.state.trace, onEvent, stream, () => isOrphanedCall(run, msg.callId, call));
806
- } finally {
807
- stream?.dispose();
808
- }
783
+ // OB2(S7 残留):isOrphaned 谓词注入——旧代际 finalize 在 trace.update 前被
784
+ // 拦截(判定语义与下方 .then/.catch 守卫同一 isOrphanedCall,详见
785
+ // execute-agent-call.ts finalizeCall 文档注释)。onEvent/stream 两实参显式
786
+ // undefined 占位([H2 W3] live record 更新与 SubagentStream 已随旁路退役;
787
+ // 位置参数不得前移——isOrphaned 是第 8 形参)。
788
+ await executeAgentCall(call, runner, run.state.budget, signal, run.state.trace, undefined, undefined, () => isOrphanedCall(run, msg.callId, call));
809
789
  };
810
790
  void dispatchCall()
811
791
  .then(() => {
812
- // 清除 live record:终态已由 executeAgentCall finalizeCall 写入 node.result,
813
- // live 不再需要(且含可变状态,不保留)。无论 stale 与否都清,避免内存泄漏。
814
- // M4: 必须在 stale guard 之前清,否则跨 rebuild 的迟到 completion 会累积 live record。
815
- node.live = undefined;
816
- // run 终止(终态)后到达的 stale completion 不写 state
792
+ // run 终止(终态)后到达的 stale completion 不写 state(终态由
793
+ // executeAgentCall finalizeCall 写入 node.result,node 无运行期附属对象)。
817
794
  if (run.state.status !== "running") return;
818
795
  // 孤儿 call 守卫(S7-second 竞态):rebuild 的 discardInFlightCalls 已移除本
819
796
  // call、或重跑 dispatch 已用新实例替换同 callId 条目时,本 completion 属于旧
@@ -852,13 +829,12 @@ function dispatchAgentCall(
852
829
  // 兜底回发:executeAgentCall 抛非 Abort 异常时(如 runner undefined 的 TypeError、
853
830
  // dispatchCall 内部 bug)原 catch 仅 console.error,worker 内对 callId 的 pending
854
831
  // Promise 永不 resolve → agent() 永久 await → worker 脚本挂死。构造 failed AgentResult
855
- //(与 resolveAgentOpts 失败路径 L262-275 一致的模式)postAgentResult 回 worker,
832
+ //(与 resolveAgentOpts 失败路径一致的模式)postAgentResult 回 worker,
856
833
  // 让 pending Promise resolve(结果为 error),脚本可继续或失败退出。
857
834
  // 孤儿 call 守卫(与 .then 对称,S7-second 竞态):rebuild 后本 call 已被 discard
858
835
  // 移除/替换——markDone 虽在孤儿实例上无害,但 trace.update 会污染重跑新建的同
859
836
  // stepIndex 节点、postAgentResult 会劫持新 worker 的同 callId pending。孤儿时只
860
- // 留日志,全部跳过。node.live 无条件先清(旧节点已脱离 trace,防御性统一)。
861
- node.live = undefined;
837
+ // 留日志,全部跳过。
862
838
  if (isOrphanedCall(run, msg.callId, call)) {
863
839
  deps.log?.("debug", "workflow:worker-message-pump", "orphan agent call failure dropped", { runId: run.runId, callId: msg.callId });
864
840
  return;
@@ -871,8 +847,8 @@ function dispatchAgentCall(
871
847
  if (call.status === "pending") call.markRunning();
872
848
  call.markDone(errorResult);
873
849
  }
874
- // state 一致性三件套(与 resolveAgentOpts 失败 L268-276 / .then L319-325 对等):
875
- // trace 标 failed + 清 live record(防泄漏)+ 持久化(catch 恰是最需留证的场景)。
850
+ // state 一致性三件套(与 resolveAgentOpts 失败 / .then 路径对等):
851
+ // trace 标 failed + 持久化(catch 恰是最需留证的场景)。
876
852
  // stale 终态(run 已 done)时 run.runtime 为 undefined,postAgentResult 用
877
853
  // optional chaining 跳过 worker 回发;trace/state 写入仍执行(无害,终态快照已存)。
878
854
  run.state.trace.update(msg.callId, {