@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
@@ -1,488 +0,0 @@
1
- // subprocess-agent-runner-routing.test.ts —— P4 SAR 路由集成测试(验收 1/2/3 的
2
- // runner 级证据面):三层优先级落到 run、fallback 留痕进 ExecuteOptions(record 投影
3
- // 的入参面)、守卫/strict 报错入 result.error、journal 池 key 对齐(对齐点③:落盘
4
- // 路径 = 引擎 onPoolResolved 声明的池 key 派生路径)。
5
- //
6
- // fake 注入:pi 侧 mock SubagentService(executeAndAwait 记录入参);zcode 侧向
7
- // registry 注册 fake EnginePort(probe 结果可注入);frontmatter/全局配置经真实
8
- // ModelConfigService 单例(agentDir 指向临时目录,真实落盘 .md 与 config.json)。
9
-
10
- import { spawn } from "node:child_process";
11
- import * as fs from "node:fs";
12
- import * as os from "node:os";
13
- import * as path from "node:path";
14
-
15
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
16
-
17
- import type { AgentCallOpts, AgentResult } from "../../orchestration/models/types.ts";
18
- import type { EnginePort, RunContext } from "../engine/port.ts";
19
- import { clearEngines, registerEngine } from "../engine/registry.ts";
20
- import type { AgentOutcome, EngineCapabilities, ProbeReport } from "../engine/types.ts";
21
- import { ModelConfigService, setModelConfigService } from "../model-config-service.ts";
22
- // W10(§2.10 ②):子进程句柄断言改读 core 侧状态镜像(host/spawned-children——
23
- // 协议化后 spawnedChildren 持有方在引擎进程,core 消费镜像面;判据 pid 同构)。
24
- import {
25
- coreSpawnedChildrenMirror,
26
- _resetCoreSpawnedChildrenMirrorForTest,
27
- } from "../engine/host/spawned-children.ts";
28
- import { SubprocessAgentRunner } from "../subprocess-agent-runner.ts";
29
- import type { SubagentService } from "../subagent-service.ts";
30
- import type { ExecuteOptions } from "../types.ts";
31
-
32
- // ── fake 引擎(registry 登记,probe/run 可记录;run 模拟 onPoolResolved + 事件 emit)──
33
-
34
- interface FakeEngineCalls {
35
- probed: number;
36
- runs: Array<{ taskSpec: unknown; ctx: RunContext }>;
37
- }
38
-
39
- /** fake zcode 引擎的池 key 缺省值(机制跟随性用例:任意 key 落对应目录)。 */
40
- const FAKE_POOL_KEY = "home-test-provider-m1";
41
-
42
- function makeFakeZcodeEngine(probeOk: boolean, poolKey: string = FAKE_POOL_KEY): { engine: EnginePort; calls: FakeEngineCalls } {
43
- const calls: FakeEngineCalls = { probed: 0, runs: [] };
44
- const engine: EnginePort = {
45
- id: "zcode",
46
- capabilities: () => ({
47
- schemaEnforcement: "emulated",
48
- steer: "unsupported",
49
- conversation: "unsupported",
50
- personaInjection: "prompt",
51
- eventGranularity: "coarse",
52
- sandbox: "none",
53
- sessionRead: "outcome-only",
54
- resume: "cold",
55
- interrupt: "kill-only",
56
- permissionMode: "native",
57
- maxTurns: false,
58
- }),
59
- probe: () => {
60
- calls.probed++;
61
- const report: ProbeReport = probeOk
62
- ? { ok: true, engineVersion: "0.16.5", checks: [{ name: "stub", ok: true }] }
63
- : {
64
- ok: false,
65
- engineVersion: "",
66
- checks: [{ name: "binary", ok: false, detail: "missing" }],
67
- error: { code: "engine_probe_failed", recovery: "reinstall zcode and retry the probe" },
68
- };
69
- return Promise.resolve(report);
70
- },
71
- run: (taskSpec, ctx) => {
72
- calls.runs.push({ taskSpec, ctx });
73
- // 对齐点③模拟:prepare 期声明池 key → 事件 emit(zcode coarse 形态:终态后合成)
74
- ctx.onPoolResolved?.(poolKey);
75
- ctx.onEvent?.({ type: "message_end", usage: { input: 1, output: 1, cacheRead: 0, cacheWrite: 0 } });
76
- ctx.onEvent?.({ type: "turn_end" });
77
- return Promise.resolve({
78
- handle: { data: { v: 1, engineId: "zcode", sessionRef: {}, poolKey, adapterVersion: "t" } },
79
- outcome: { engineId: "zcode", content: "from-zcode" },
80
- });
81
- },
82
- interact: () => Promise.resolve({ ok: false, code: "engine_capability_unsupported", message: "stub" }),
83
- read: () => Promise.resolve({ engineId: "zcode", turns: [], source: "outcome-only" }),
84
- };
85
- return { engine, calls };
86
- }
87
-
88
- // ── mock pi 编排服务(executeAndAwait 入参记录——record 投影的入参面)──
89
-
90
- function makeMockPiService() {
91
- const executeOpts: ExecuteOptions[] = [];
92
- const executeAndAwait = vi.fn(async (opts: ExecuteOptions): Promise<AgentResult> => {
93
- executeOpts.push(opts);
94
- return { content: "from-pi", durationMs: 1, toolCalls: [] };
95
- });
96
- // [D4 聚合连带] SAR 构造器经 asEngineService 显式视图取引擎服务面——fake 的
97
- // face 即自身,getter 直接返回 self。
98
- const service = { executeAndAwait } as unknown as SubagentService & { asEngineService: unknown };
99
- (service as { asEngineService: unknown }).asEngineService = service;
100
- // [W3] 登记当前 mock 服务——委托式替身 port 的工厂闭包按它路由(getEngine 惰性)。
101
- currentPiService = service as unknown as SubagentService;
102
- return { service, executeOpts, executeAndAwait };
103
- }
104
-
105
- // ── [W3] 委托式替身 pi port(原 inproc PiEngine 的 workflow 分支形态)──
106
- // run() 还原引擎内 task→ExecuteOptions 映射(含 engine='pi' 留痕与 engineFallback
107
- // 透传——两断言的契约面)后委托 mock 服务的 executeAndAwait。
108
-
109
- let currentPiService: SubagentService | undefined;
110
-
111
- function piTaskToExecuteOptions(task: AgentCallOpts, ctx: RunContext): ExecuteOptions {
112
- const rawSlug = task.description ?? task.agent ?? "workflow-agent";
113
- return {
114
- task: task.prompt,
115
- slug: rawSlug.length > 35 ? rawSlug.slice(0, 35) : rawSlug,
116
- agent: task.agent,
117
- model: task.model,
118
- thinkingLevel: task.thinkingLevel,
119
- skillPath: task.skillPath,
120
- appendSystemPrompt: task.appendSystemPrompt,
121
- schema: task.schema,
122
- schemaEnv: task.schemaEnv,
123
- maxTurns: task.maxTurns,
124
- graceTurns: task.graceTurns,
125
- ctxModel: ctx.ctxModel,
126
- fork: task.fork,
127
- worktree: task.worktree,
128
- cwd: task.cwd,
129
- conversation: task.conversation,
130
- idleTimeoutMs: task.idleTimeoutMs,
131
- // P4 引擎留痕(D9①):实际执行引擎 id 恒 pi(原 PiEngine.run workflow 分支同款)
132
- engine: "pi",
133
- ...(ctx.engineFallback !== undefined ? { engineFallback: ctx.engineFallback } : {}),
134
- };
135
- }
136
-
137
- function makeDelegatingPiPort(getService: () => SubagentService | undefined): EnginePort {
138
- return {
139
- id: "pi",
140
- capabilities: (): EngineCapabilities => ({
141
- schemaEnforcement: "native",
142
- steer: "unsupported",
143
- conversation: "native",
144
- personaInjection: "flag",
145
- eventGranularity: "stream",
146
- sandbox: "emulated",
147
- sessionRead: "full",
148
- resume: "native",
149
- interrupt: "kill-only",
150
- permissionMode: "native",
151
- maxTurns: true,
152
- }),
153
- probe: async (): Promise<ProbeReport> => ({ ok: true, engineVersion: "fake", checks: [] }),
154
- run: async (task, ctx) => {
155
- const service = getService();
156
- if (service === undefined) throw new Error("mock pi service not installed");
157
- const result = await (service as unknown as {
158
- executeAndAwait: (opts: ExecuteOptions, signal?: AbortSignal) => Promise<AgentResult>;
159
- }).executeAndAwait(piTaskToExecuteOptions(task, ctx), ctx.signal);
160
- const outcome: AgentOutcome = {
161
- content: result.content,
162
- durationMs: result.durationMs,
163
- error: result.error,
164
- engineId: "pi",
165
- };
166
- return {
167
- handle: { data: { v: 1, engineId: "pi", sessionRef: {}, poolKey: "shared", adapterVersion: "fake-sar" } },
168
- outcome,
169
- };
170
- },
171
- interact: async () => ({ ok: false, code: "engine_interact_failed", message: "not supported in this test" }),
172
- read: async () => ({ engineId: "pi", turns: [], source: "outcome-only" }),
173
- };
174
- }
175
-
176
- // ── 环境 ──
177
-
178
- let tmpRoot: string;
179
- let agentDir: string;
180
- let prevDataDirEnv: string | undefined;
181
-
182
- function writeGlobalConfig(cfg: Record<string, unknown>): void {
183
- fs.mkdirSync(path.join(agentDir, "subagents"), { recursive: true });
184
- fs.writeFileSync(path.join(agentDir, "subagents", "config.json"), JSON.stringify(cfg));
185
- }
186
-
187
- function writeAgentMd(name: string, frontmatter: string): string {
188
- fs.mkdirSync(path.join(agentDir, "agents"), { recursive: true });
189
- const p = path.join(agentDir, "agents", `${name}.md`);
190
- fs.writeFileSync(p, `---\n${frontmatter}\n---\nbody`);
191
- return p;
192
- }
193
-
194
- function makeOpts(overrides?: Partial<AgentCallOpts>): AgentCallOpts {
195
- return {
196
- prompt: "task",
197
- description: "routing-test",
198
- ...overrides,
199
- };
200
- }
201
-
202
- /** 装配真实 ModelConfigService 单例(agentDir 指临时目录——frontmatter/config.json 真实落盘)。 */
203
- function installModelService(cfg?: Record<string, unknown>): void {
204
- if (cfg !== undefined) writeGlobalConfig(cfg);
205
- setModelConfigService(new ModelConfigService({ agentDir, cwd: tmpRoot }));
206
- }
207
-
208
- beforeEach(() => {
209
- _resetCoreSpawnedChildrenMirrorForTest();
210
- tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "sar-routing-"));
211
- agentDir = path.join(tmpRoot, "pi-agent");
212
- fs.mkdirSync(agentDir, { recursive: true });
213
- // journal 落盘隔离(getEngineDataDir 的权威通道):tmpRoot 下,不污染真实目录
214
- prevDataDirEnv = process.env["XYZ_AGENT_DATA_DIR"];
215
- process.env["XYZ_AGENT_DATA_DIR"] = path.join(tmpRoot, "engine-data");
216
- clearEngines();
217
- currentPiService = undefined;
218
- // [U-2 一致性修复 → W3] resolveHostPiEnginePort 对已注册 port 原样返回——登记
219
- // 委托式替身 pi port(registry 命中即路由命中;mock 服务的 DI 语义保持不变)。
220
- registerEngine("pi", () => makeDelegatingPiPort(() => currentPiService));
221
- });
222
-
223
- afterEach(() => {
224
- if (prevDataDirEnv === undefined) delete process.env["XYZ_AGENT_DATA_DIR"];
225
- else process.env["XYZ_AGENT_DATA_DIR"] = prevDataDirEnv;
226
- clearEngines();
227
- fs.rmSync(tmpRoot, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
228
- });
229
-
230
- describe("SAR 路由集成(P4 验收 1/2/3)", () => {
231
- it("缺省路径:无任何指定 → pi(本地 DI 绑定),ExecuteOptions.engine='pi' 留痕", async () => {
232
- installModelService();
233
- const pi = makeMockPiService();
234
- const sar = new SubprocessAgentRunner({ subagentService: pi.service });
235
-
236
- const result = await sar.run(makeOpts(), new AbortController().signal);
237
-
238
- expect(result.content).toBe("from-pi");
239
- expect(pi.executeOpts[0]?.engine).toBe("pi"); // record 投影的入参面(验收 3)
240
- expect(pi.executeOpts[0]?.engineFallback).toBeUndefined();
241
- });
242
-
243
- it("frontmatter 指定 zcode(probe ok)→ registry 引擎执行;journal 落盘路径 = 引擎池 key(对齐点③)", async () => {
244
- const agentRef = writeAgentMd("reviewer", "name: reviewer\ndescription: d\nengine: zcode");
245
- installModelService();
246
- const { engine, calls } = makeFakeZcodeEngine(true);
247
- registerEngine("zcode", () => engine);
248
- const pi = makeMockPiService();
249
- const sar = new SubprocessAgentRunner({ subagentService: pi.service });
250
-
251
- const result = await sar.run(makeOpts({ agent: agentRef }), new AbortController().signal);
252
-
253
- expect(result.content).toBe("from-zcode");
254
- expect(calls.runs).toHaveLength(1);
255
- expect(pi.executeAndAwait).not.toHaveBeenCalled();
256
- // 机制跟随性断言:journal 落在引擎声明的池 key 目录(任意 key 落对应目录——
257
- // 真实 zcode 引擎 poolKey 恒 'shared',对齐真实行为的断言见下一用例)
258
- const poolDir = path.join(tmpRoot, "engine-data", "engines", "zcode", FAKE_POOL_KEY);
259
- const journals = fs.existsSync(poolDir) ? fs.readdirSync(poolDir) : [];
260
- expect(journals).toHaveLength(1);
261
- expect(journals[0]).toMatch(/^journal-sa-.+\.jsonl$/);
262
- });
263
-
264
- it("journal 落盘对齐真实行为:poolKey 'shared' → engines/zcode/shared/journal-<taskId>.jsonl", async () => {
265
- // 生产 zcode 引擎 poolKey 恒 'shared'(ZCODE_SHARED_POOL_KEY)——fake 以同 key
266
- // 声明,断言 journal 精确落 shared 池目录(旧池化时代「shared 目录不存在」的
267
- // 反向锚定已随池化删除,此处锚定现行正向语义)
268
- const agentRef = writeAgentMd("reviewer", "name: reviewer\ndescription: d\nengine: zcode");
269
- installModelService();
270
- const { engine, calls } = makeFakeZcodeEngine(true, "shared");
271
- registerEngine("zcode", () => engine);
272
- const pi = makeMockPiService();
273
- const sar = new SubprocessAgentRunner({ subagentService: pi.service });
274
-
275
- await sar.run(makeOpts({ agent: agentRef }), new AbortController().signal);
276
-
277
- const taskId = calls.runs[0]?.ctx.taskId;
278
- expect(taskId).toMatch(/^sa-/);
279
- const sharedPoolDir = path.join(tmpRoot, "engine-data", "engines", "zcode", "shared");
280
- expect(fs.readdirSync(sharedPoolDir)).toEqual([`journal-${taskId}.jsonl`]);
281
- });
282
-
283
- it("调用参数 engine 覆盖 frontmatter(三层优先级落到 run,A7)", async () => {
284
- const agentRef = writeAgentMd("reviewer", "name: reviewer\ndescription: d\nengine: zcode");
285
- installModelService();
286
- const zcode = makeFakeZcodeEngine(true);
287
- registerEngine("zcode", () => zcode.engine);
288
- const pi = makeMockPiService();
289
- const sar = new SubprocessAgentRunner({ subagentService: pi.service });
290
-
291
- await sar.run(makeOpts({ agent: agentRef, engine: "pi" }), new AbortController().signal);
292
-
293
- expect(zcode.calls.runs).toHaveLength(0);
294
- expect(pi.executeOpts).toHaveLength(1); // 调用参数显式 pi 胜出
295
- });
296
-
297
- it("全局默认引擎(config defaultEngine=zcode)生效", async () => {
298
- installModelService({ version: 1, maxConcurrent: 6, defaultEngine: "zcode" });
299
- const { engine, calls } = makeFakeZcodeEngine(true);
300
- registerEngine("zcode", () => engine);
301
- const pi = makeMockPiService();
302
- const sar = new SubprocessAgentRunner({ subagentService: pi.service });
303
-
304
- await sar.run(makeOpts(), new AbortController().signal);
305
- expect(calls.runs).toHaveLength(1);
306
- expect(pi.executeAndAwait).not.toHaveBeenCalled();
307
- });
308
-
309
- it("frontmatter zcode probe 失败 → 路由回 pi + engineFallback 进 ExecuteOptions(record 留痕面,A9①)", async () => {
310
- const agentRef = writeAgentMd("reviewer", "name: reviewer\ndescription: d\nengine: zcode");
311
- installModelService();
312
- const zcode = makeFakeZcodeEngine(false);
313
- registerEngine("zcode", () => zcode.engine);
314
- const pi = makeMockPiService();
315
- const sar = new SubprocessAgentRunner({ subagentService: pi.service });
316
-
317
- const result = await sar.run(makeOpts({ agent: agentRef }), new AbortController().signal);
318
-
319
- expect(result.content).toBe("from-pi");
320
- expect(pi.executeOpts[0]?.engine).toBe("pi");
321
- expect(pi.executeOpts[0]?.engineFallback).toEqual({ from: "zcode", reason: "engine_probe_failed" });
322
- expect(zcode.calls.probed).toBe(1);
323
- });
324
-
325
- it("调用参数显式 zcode + probe 失败 → 不兜底,result.error 含 engine_probe_failed(守卫 a,A9②)", async () => {
326
- installModelService();
327
- const zcode = makeFakeZcodeEngine(false);
328
- registerEngine("zcode", () => zcode.engine);
329
- const pi = makeMockPiService();
330
- const sar = new SubprocessAgentRunner({ subagentService: pi.service });
331
-
332
- const result = await sar.run(makeOpts({ engine: "zcode" }), new AbortController().signal);
333
-
334
- expect(result.content).toBe("");
335
- expect(result.error).toContain("engine_probe_failed");
336
- expect(pi.executeAndAwait).not.toHaveBeenCalled();
337
- });
338
-
339
- it("frontmatter zcode + 显式 model + probe 失败 → model_not_available(守卫 c)", async () => {
340
- const agentRef = writeAgentMd("reviewer", "name: reviewer\ndescription: d\nengine: zcode");
341
- installModelService();
342
- const zcode = makeFakeZcodeEngine(false);
343
- registerEngine("zcode", () => zcode.engine);
344
- const pi = makeMockPiService();
345
- const sar = new SubprocessAgentRunner({ subagentService: pi.service });
346
-
347
- const result = await sar.run(
348
- makeOpts({ agent: agentRef, model: "builtin:bigmodel-coding-plan/GLM-5.3" }),
349
- new AbortController().signal,
350
- );
351
-
352
- expect(result.error).toContain("model_not_available");
353
- expect(pi.executeAndAwait).not.toHaveBeenCalled();
354
- });
355
-
356
- it("strict=true(config engineRouting.strict)→ frontmatter 层 probe 失败直接报错(A5)", async () => {
357
- const agentRef = writeAgentMd("reviewer", "name: reviewer\ndescription: d\nengine: zcode");
358
- installModelService({ version: 1, maxConcurrent: 6, engineRouting: { strict: true } });
359
- const zcode = makeFakeZcodeEngine(false);
360
- registerEngine("zcode", () => zcode.engine);
361
- const pi = makeMockPiService();
362
- const sar = new SubprocessAgentRunner({ subagentService: pi.service });
363
-
364
- const result = await sar.run(makeOpts({ agent: agentRef }), new AbortController().signal);
365
-
366
- expect(result.error).toContain("engine_probe_failed");
367
- expect(result.error).toContain("strict");
368
- expect(pi.executeAndAwait).not.toHaveBeenCalled();
369
- });
370
-
371
- it("调用参数未注册 id → engine_not_found 入 result.error(文案含注册清单)", async () => {
372
- installModelService();
373
- const pi = makeMockPiService();
374
- const sar = new SubprocessAgentRunner({ subagentService: pi.service });
375
-
376
- const result = await sar.run(makeOpts({ engine: "no-such-engine" }), new AbortController().signal);
377
-
378
- expect(result.error).toContain("engine_not_found");
379
- expect(result.error).toContain("Registered engines:");
380
- });
381
-
382
- it("frontmatter 未注册 id(解析期拦截)→ SAR 转错误结果(不 crash)", async () => {
383
- // frontmatter 校验在 agent 解析期(agent-registry 抛 EngineNotFoundError)——经
384
- // ModelConfigService.getAgentConfig 到达 SAR 路由装配,SAR catch 转 result.error
385
- const agentRef = writeAgentMd("broken", "name: broken\ndescription: d\nengine: ghost-engine");
386
- installModelService();
387
- const pi = makeMockPiService();
388
- const sar = new SubprocessAgentRunner({ subagentService: pi.service });
389
-
390
- const result = await sar.run(makeOpts({ agent: agentRef }), new AbortController().signal);
391
-
392
- expect(result.error).toContain("engine_not_found");
393
- expect(pi.executeAndAwait).not.toHaveBeenCalled();
394
- });
395
-
396
- it("[D10] RunContext.onChildSpawned 注入:子进程注册进 spawnedChildren,退出后按句移除", async () => {
397
- // C-ext-17 路径①回归:SAR 构造的 RunContext 必须提供 onChildSpawned(引擎 spawn
398
- // 后回调 → session-runner spawnedChildren 记账 → dispose killAll 收割兜底可见)
399
- const agentRef = writeAgentMd("reviewer", "name: reviewer\ndescription: d\nengine: zcode");
400
- installModelService();
401
- const { engine, calls } = makeFakeZcodeEngine(true);
402
- registerEngine("zcode", () => engine);
403
- const pi = makeMockPiService();
404
- const sar = new SubprocessAgentRunner({ subagentService: pi.service });
405
-
406
- await sar.run(makeOpts({ agent: agentRef }), new AbortController().signal);
407
-
408
- const ctx = calls.runs[0]?.ctx;
409
- expect(ctx?.onChildSpawned).toBeTypeOf("function");
410
- // 引擎回调(真实引擎在 spawn 成功后同步调)→ 按 taskId('sa-' 记账 key)注册可见
411
- const child = spawn(process.execPath, ["-e", ""]);
412
- ctx?.onChildSpawned?.(child);
413
- expect(coreSpawnedChildrenMirror().getChildByRecord(ctx.taskId)?.pid).toBe(child.pid);
414
- // 按句移除断言(W10 注):inproc 双模下子进程退出不回灌 host/childStateChanged
415
- // (该通道是 cli 协议形态专属)——镜像移除断言由 protocol-blackbox 的
416
- // childStateChanged 用例承载;此处等待 close 仅保证进程面收口不悬挂。
417
- await new Promise<void>((resolve) => child.once("close", () => resolve()));
418
- });
419
-
420
- // ── [D3-④] SAR 路径预检(capabilities 驱动;唯一有意行为变化 = zcode+worktree)──
421
-
422
- it("[D3-④] zcode + worktree:true → engine_capability_unsupported(workflow 域漏拦缺口修复),无 engine.run、无 journal 产物", async () => {
423
- const agentRef = writeAgentMd("reviewer", "name: reviewer\ndescription: d\nengine: zcode");
424
- installModelService();
425
- const { engine, calls } = makeFakeZcodeEngine(true);
426
- registerEngine("zcode", () => engine);
427
- const pi = makeMockPiService();
428
- const sar = new SubprocessAgentRunner({ subagentService: pi.service });
429
-
430
- const result = await sar.run(makeOpts({ agent: agentRef, worktree: true }), new AbortController().signal);
431
-
432
- expect(result.error).toContain("engine_capability_unsupported");
433
- expect(result.error).toContain("worktree");
434
- // 无进程创建 + 无 journal 落盘(拒绝在 wiring/run 之前)
435
- expect(calls.runs).toHaveLength(0);
436
- expect(pi.executeAndAwait).not.toHaveBeenCalled();
437
- const enginesRoot = path.join(tmpRoot, "engine-data", "engines");
438
- expect(fs.existsSync(enginesRoot) ? fs.readdirSync(enginesRoot) : []).toEqual([]);
439
- });
440
-
441
- it("[D3-④] zcode + maxTurns / fork → engine_capability_unsupported(现状行为保持的正向守护)", async () => {
442
- const agentRef = writeAgentMd("reviewer", "name: reviewer\ndescription: d\nengine: zcode");
443
- installModelService();
444
- const { engine, calls } = makeFakeZcodeEngine(true);
445
- registerEngine("zcode", () => engine);
446
- const pi = makeMockPiService();
447
- const sar = new SubprocessAgentRunner({ subagentService: pi.service });
448
-
449
- const maxTurnsResult = await sar.run(makeOpts({ agent: agentRef, maxTurns: 5 }), new AbortController().signal);
450
- expect(maxTurnsResult.error).toContain("engine_capability_unsupported");
451
- expect(maxTurnsResult.error).toContain("maxTurns");
452
- expect(maxTurnsResult.error).toContain("capabilities.maxTurns = false");
453
-
454
- const forkResult = await sar.run(makeOpts({ agent: agentRef, fork: true }), new AbortController().signal);
455
- expect(forkResult.error).toContain("engine_capability_unsupported");
456
- expect(forkResult.error).toContain("fork");
457
- expect(calls.runs).toHaveLength(0);
458
- });
459
-
460
- it("[V4⑤ 反向] pi + maxTurns → 直通不拦(pi 既有合法能力零回归),executeAndAwait 收到 maxTurns", async () => {
461
- installModelService();
462
- const pi = makeMockPiService();
463
- const sar = new SubprocessAgentRunner({ subagentService: pi.service });
464
-
465
- const result = await sar.run(makeOpts({ maxTurns: 3 }), new AbortController().signal);
466
-
467
- expect(result.content).toBe("from-pi");
468
- expect(pi.executeOpts[0]?.maxTurns).toBe(3);
469
- });
470
- });
471
-
472
- // [U-2 一致性修复] D4 错误契约:pi 未注册(引擎包未装/发现失败)时普通 run 路径
473
- // 不再静默直构 inproc——派发期显式 engine_not_found + 安装指引(与 zcode 对称)。
474
- describe("pi 未注册 → engine_not_found(D4 契约,U-2 修复锚定)", () => {
475
- it("pi 不在 registry → run 结果 error 含 engine_not_found + 安装指引,不静默走 inproc", async () => {
476
- clearEngines(); // 覆盖 beforeEach 的 inproc pi 登记——模拟发现失败/未装包
477
- installModelService();
478
- const pi = makeMockPiService();
479
- const sar = new SubprocessAgentRunner({ subagentService: pi.service });
480
-
481
- const result = await sar.run(makeOpts(), new AbortController().signal);
482
-
483
- expect(result.error).toContain("engine_not_found");
484
- expect(result.error).toContain("No engine packages were discovered");
485
- expect(result.error).toMatch(/install an engine package/i);
486
- expect(pi.executeAndAwait).not.toHaveBeenCalled();
487
- });
488
- });
@@ -1,75 +0,0 @@
1
- // src/execution/__tests__/subprocess-agent-runner-timeout.test.ts
2
- //
3
- // [D6 合流迁移] mergeTimeoutSignal(D-A9:per-call timeoutMs 合并进 AbortSignal)的
4
- // 行为测试——原定义在已删除的 execution/execute-options-mapper.ts(测试随迁),函数
5
- // 现居 subprocess-agent-runner.ts(唯一消费点,运行期件随消费方落位)。
6
- // 原用例编号(execute-options-mapper.test.ts 的 mergeTimeoutSignal describe,7 用例)
7
- // 与行为语义逐条保持。
8
- //
9
- // 接线测试(timeoutMs → engine 收到 merged signal → abort 收口)在
10
- // subprocess-agent-runner.test.ts 的 T3.6(经真实 PiEngine 链路),本文件锁纯函数行为。
11
-
12
- import { describe, expect, it, vi } from "vitest";
13
-
14
- import { mergeTimeoutSignal } from "../subprocess-agent-runner.ts";
15
-
16
- describe("mergeTimeoutSignal (D-A9)", () => {
17
- it("T3.6 timeoutMs===undefined → 原样返回 external signal", () => {
18
- const ctrl = new AbortController();
19
- const result = mergeTimeoutSignal(ctrl.signal, undefined);
20
- expect(result).toBe(ctrl.signal);
21
- });
22
-
23
- it("T3.6 timeoutMs<=0 → 原样返回 external signal", () => {
24
- const ctrl = new AbortController();
25
- const result = mergeTimeoutSignal(ctrl.signal, 0);
26
- expect(result).toBe(ctrl.signal);
27
- });
28
-
29
- it("T3.6 timeoutMs>0 → 返回新 signal(合并外部+超时两路)", () => {
30
- const ctrl = new AbortController();
31
- const result = mergeTimeoutSignal(ctrl.signal, 50);
32
- expect(result).not.toBe(ctrl.signal);
33
- expect(result.aborted).toBe(false);
34
- });
35
-
36
- it("T3.6 timeoutMs 到期 → merged signal abort", () => {
37
- vi.useFakeTimers();
38
- const ctrl = new AbortController();
39
- const merged = mergeTimeoutSignal(ctrl.signal, 50);
40
-
41
- expect(merged.aborted).toBe(false);
42
- vi.advanceTimersByTime(51);
43
- expect(merged.aborted).toBe(true);
44
- vi.useRealTimers();
45
- });
46
-
47
- it("T3.6 外部 signal abort → merged signal abort", () => {
48
- const ctrl = new AbortController();
49
- const merged = mergeTimeoutSignal(ctrl.signal, 5000);
50
- ctrl.abort();
51
- expect(merged.aborted).toBe(true);
52
- });
53
-
54
- it("T3.6 外部 signal 已 abort → 返回已 abort 的 signal", () => {
55
- const ctrl = new AbortController();
56
- ctrl.abort();
57
- const merged = mergeTimeoutSignal(ctrl.signal, 5000);
58
- expect(merged.aborted).toBe(true);
59
- });
60
-
61
- it("T3.17 NFR: merged signal abort → timeout timer 清理", () => {
62
- vi.useFakeTimers();
63
- const ctrl = new AbortController();
64
- const merged = mergeTimeoutSignal(ctrl.signal, 50);
65
-
66
- ctrl.abort(); // 外部 abort → merged 也 abort
67
- expect(merged.aborted).toBe(true);
68
-
69
- // 推进时间,不应再有副作用
70
- vi.advanceTimersByTime(100);
71
- // timer 应被清理(通过 abort event listener)
72
- // 无异常 = timer 已正确清理
73
- vi.useRealTimers();
74
- });
75
- });
@@ -1,73 +0,0 @@
1
- // src/__tests__/tombstone-store.test.ts
2
- //
3
- // tombstone-store 专属测试。
4
- // 覆盖:write→read 往返 / 缺 sidecar → undefined / 损坏 sidecar → undefined / 结构校验。
5
-
6
- import * as fs from "node:fs";
7
- import * as os from "node:os";
8
- import * as path from "node:path";
9
-
10
- import { afterEach, beforeEach, describe, expect, it } from "vitest";
11
-
12
- import { readCancelledTombstone, writeCancelledTombstone } from "../tombstone-store.ts";
13
-
14
- describe("tombstone-store", () => {
15
- let tmpDir: string;
16
- let sessionFile: string;
17
-
18
- beforeEach(() => {
19
- tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "ts-test-"));
20
- sessionFile = path.join(tmpDir, "2026-01-01_uuid.jsonl");
21
- });
22
- afterEach(() => {
23
- fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
24
- });
25
-
26
- describe("write → read 往返", () => {
27
- it("写入后读回完整数据", () => {
28
- writeCancelledTombstone(sessionFile, {
29
- id: "bg-1", status: "cancelled", agent: "worker", startedAt: 1000, endedAt: 2000,
30
- });
31
- const tomb = readCancelledTombstone(sessionFile);
32
- expect(tomb).toEqual({
33
- id: "bg-1", status: "cancelled", agent: "worker", startedAt: 1000, endedAt: 2000,
34
- });
35
- });
36
-
37
- it("sidecar 路径 = sessionFile + '.cancelled'", () => {
38
- writeCancelledTombstone(sessionFile, {
39
- id: "bg-1", status: "cancelled", agent: "w", startedAt: 1, endedAt: 2,
40
- });
41
- expect(fs.existsSync(`${sessionFile}.cancelled`)).toBe(true);
42
- });
43
- });
44
-
45
- describe("读降级", () => {
46
- it("无 sidecar → undefined(正常——非 cancelled record)", () => {
47
- expect(readCancelledTombstone(sessionFile)).toBeUndefined();
48
- });
49
-
50
- it("损坏 JSON → undefined", () => {
51
- fs.writeFileSync(`${sessionFile}.cancelled`, "NOT JSON\n", "utf-8");
52
- expect(readCancelledTombstone(sessionFile)).toBeUndefined();
53
- });
54
-
55
- it("status 非 'cancelled' → undefined", () => {
56
- fs.writeFileSync(
57
- `${sessionFile}.cancelled`,
58
- `${JSON.stringify({ id: "x", status: "done", agent: "w", startedAt: 1, endedAt: 2 })}\n`,
59
- "utf-8",
60
- );
61
- expect(readCancelledTombstone(sessionFile)).toBeUndefined();
62
- });
63
-
64
- it("缺必填字段 → undefined", () => {
65
- fs.writeFileSync(
66
- `${sessionFile}.cancelled`,
67
- `${JSON.stringify({ id: "x", status: "cancelled" })}\n`, // 缺 agent/startedAt/endedAt
68
- "utf-8",
69
- );
70
- expect(readCancelledTombstone(sessionFile)).toBeUndefined();
71
- });
72
- });
73
- });