@zhushanwen/subagent-core 0.2.0 → 0.4.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 (195) hide show
  1. package/README.md +45 -2
  2. package/agents/analyst.md +60 -0
  3. package/agents/coder.md +69 -0
  4. package/agents/debugger.md +66 -0
  5. package/agents/doc-reviewer.md +49 -0
  6. package/agents/explorer.md +63 -0
  7. package/agents/general-purpose.md +32 -0
  8. package/agents/orchestrator.md +61 -0
  9. package/agents/planner.md +53 -0
  10. package/agents/researcher.md +67 -0
  11. package/agents/reviewer.md +73 -0
  12. package/dist/chunk-4KN4TTG7.js +240 -0
  13. package/dist/chunk-APZY4IME.js +27 -0
  14. package/dist/chunk-X7SZ5HLQ.js +66 -0
  15. package/dist/execution/engine/engines/zcode/constants.cjs +65 -0
  16. package/dist/execution/engine/engines/zcode/constants.d.cts +93 -1
  17. package/dist/execution/engine/engines/zcode/constants.d.ts +93 -1
  18. package/dist/execution/engine/engines/zcode/constants.js +43 -1
  19. package/dist/execution/engine/engines/zcode/reader.d.cts +1 -1
  20. package/dist/execution/engine/engines/zcode/reader.d.ts +1 -1
  21. package/dist/execution/engine/engines/zcode/reader.js +4 -234
  22. package/dist/execution/engine/paths.js +7 -19
  23. package/dist/index.cjs +14709 -100
  24. package/dist/index.d.cts +4053 -200
  25. package/dist/index.d.ts +4053 -200
  26. package/dist/index.js +14358 -102
  27. package/dist/{types-BxyAidGf.d.cts → types-DpUO16pj.d.cts} +464 -1
  28. package/dist/{types-BxyAidGf.d.ts → types-DpUO16pj.d.ts} +464 -1
  29. package/package.json +6 -3
  30. package/src/__tests__/review-fix-loop-script.test.ts +90 -2
  31. package/src/__tests__/review-fix-loop-utils.test.ts +21 -0
  32. package/src/__tests__/smoke.test.ts +9 -2
  33. package/src/execution/__tests__/agent-profile.test.ts +232 -0
  34. package/src/execution/__tests__/agents-assembly.test.ts +219 -0
  35. package/src/execution/__tests__/chat-engine-routing.test.ts +71 -0
  36. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +1 -0
  37. package/src/execution/__tests__/create-concurrency-pool.test.ts +194 -0
  38. package/src/execution/__tests__/delivery-methods.test.ts +2 -0
  39. package/src/execution/__tests__/descendant-sweep.test.ts +269 -0
  40. package/src/execution/__tests__/dialog-queue.test.ts +199 -1
  41. package/src/execution/__tests__/epipe-fallback.test.ts +2 -0
  42. package/src/execution/__tests__/execution-runtime-face.test.ts +272 -0
  43. package/src/execution/__tests__/finalize-record.test.ts +106 -0
  44. package/src/execution/__tests__/gc-timer.test.ts +2 -0
  45. package/src/execution/__tests__/get-record-for-action-restart.test.ts +2 -0
  46. package/src/execution/__tests__/get-state-handshake.test.ts +127 -0
  47. package/src/execution/__tests__/keep-alive-no-progress.test.ts +378 -0
  48. package/src/execution/__tests__/kill-all-escalation.test.ts +196 -0
  49. package/src/execution/__tests__/lifecycle-manager-idle-timer-identity.test.ts +117 -0
  50. package/src/execution/__tests__/lifecycle-manager.test.ts +39 -0
  51. package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +99 -0
  52. package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +113 -0
  53. package/src/execution/__tests__/notify-ledger.test.ts +294 -0
  54. package/src/execution/__tests__/record-store-orphan-revive.test.ts +159 -0
  55. package/src/execution/__tests__/record-store.test.ts +83 -0
  56. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +4 -0
  57. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +1 -0
  58. package/src/execution/__tests__/run-spawn-edges.test.ts +407 -17
  59. package/src/execution/__tests__/run-spawn-integration.test.ts +1 -1
  60. package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +12 -4
  61. package/src/execution/__tests__/service-kill-escalation.test.ts +89 -0
  62. package/src/execution/__tests__/session-pending.test.ts +228 -0
  63. package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +110 -0
  64. package/src/execution/__tests__/session-runner-close-prune.test.ts +181 -0
  65. package/src/execution/__tests__/session-runner-epipe.test.ts +1 -0
  66. package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +226 -0
  67. package/src/execution/__tests__/settled-watchdog.test.ts +289 -0
  68. package/src/execution/__tests__/spawned-children.test.ts +153 -10
  69. package/src/execution/__tests__/subagent-actions-core.test.ts +997 -0
  70. package/src/execution/__tests__/subagent-service-message-close.test.ts +71 -10
  71. package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +174 -0
  72. package/src/execution/__tests__/subagent-service-notify-gate.test.ts +266 -0
  73. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +3 -0
  74. package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +316 -0
  75. package/src/execution/__tests__/timeout-integration.test.ts +7 -7
  76. package/src/execution/__tests__/ui-request-handler-factory.test.ts +95 -1
  77. package/src/execution/__tests__/ui-request-queue.test.ts +25 -0
  78. package/src/execution/__tests__/worktree-git-ops.test.ts +571 -0
  79. package/src/execution/__tests__/worktree-reconcile-aging.test.ts +204 -0
  80. package/src/execution/agent-registry.ts +225 -1
  81. package/src/execution/agents-assembly.ts +88 -0
  82. package/src/execution/concurrency-pool.ts +71 -9
  83. package/src/execution/dialog-queue.ts +101 -3
  84. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +49 -2
  85. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +87 -1
  86. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +26 -0
  87. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +83 -1
  88. package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +131 -0
  89. package/src/execution/engine/__tests__/registry.test.ts +90 -1
  90. package/src/execution/engine/engine-discovery.ts +12 -16
  91. package/src/execution/engine/engines/pi/task-spec-mapper.ts +3 -3
  92. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +274 -0
  93. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-appserver.json +36 -0
  94. package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +472 -0
  95. package/src/execution/engine/engines/zcode/__tests__/preparer-appserver.test.ts +387 -0
  96. package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +780 -0
  97. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +815 -0
  98. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +462 -0
  99. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +119 -1
  100. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +136 -6
  101. package/src/execution/engine/engines/zcode/appserver-home.ts +442 -0
  102. package/src/execution/engine/engines/zcode/appserver-probe.ts +141 -0
  103. package/src/execution/engine/engines/zcode/connection.ts +585 -0
  104. package/src/execution/engine/engines/zcode/constants.ts +129 -0
  105. package/src/execution/engine/engines/zcode/golden-sample.ts +54 -8
  106. package/src/execution/engine/engines/zcode/preparer.ts +25 -22
  107. package/src/execution/engine/engines/zcode/session-channel.ts +656 -0
  108. package/src/execution/engine/engines/zcode/zcode-engine.ts +909 -41
  109. package/src/execution/engine/host-task-spec.ts +3 -3
  110. package/src/execution/engine/port.ts +41 -1
  111. package/src/execution/engine/registry.ts +53 -0
  112. package/src/execution/finalize-record.ts +63 -5
  113. package/src/execution/get-state-handshake.ts +85 -10
  114. package/src/execution/lifecycle-manager.ts +27 -3
  115. package/src/execution/manifest-store.ts +53 -62
  116. package/src/execution/notifier.ts +10 -10
  117. package/src/execution/notify-ledger.ts +117 -16
  118. package/src/execution/record-entry.ts +8 -2
  119. package/src/execution/record-store.ts +29 -15
  120. package/src/execution/session-pending.ts +213 -62
  121. package/src/execution/session-runner.ts +974 -128
  122. package/src/execution/sessions-index.ts +10 -55
  123. package/src/execution/settled-watchdog.ts +99 -0
  124. package/src/execution/subagent-actions-core.ts +686 -0
  125. package/src/execution/subagent-service.ts +396 -49
  126. package/src/execution/ui-request-handler-factory.ts +27 -4
  127. package/src/execution/worktree-git-ops.ts +397 -0
  128. package/src/execution/worktree-manager.ts +72 -10
  129. package/src/execution/worktree-registry.ts +6 -12
  130. package/src/index.ts +413 -6
  131. package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +42 -3
  132. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +2 -4
  133. package/src/orchestration/__tests__/args-meta.test.ts +358 -0
  134. package/src/orchestration/__tests__/error-recovery-handlers.test.ts +1 -8
  135. package/src/orchestration/__tests__/error-recovery-rebuild-failure.test.ts +312 -0
  136. package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +382 -0
  137. package/src/orchestration/__tests__/file-run-store-prune.test.ts +168 -0
  138. package/src/orchestration/__tests__/file-run-store-throttle.test.ts +168 -0
  139. package/src/orchestration/__tests__/file-run-store.test.ts +389 -0
  140. package/src/orchestration/__tests__/helpers/flush-microtasks.ts +13 -0
  141. package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +34 -0
  142. package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +357 -0
  143. package/src/orchestration/__tests__/lifecycle-recover-crashed.test.ts +289 -0
  144. package/src/orchestration/__tests__/lifecycle.test.ts +77 -0
  145. package/src/orchestration/__tests__/run-snapshot.test.ts +353 -0
  146. package/src/orchestration/__tests__/script-generate.test.ts +314 -0
  147. package/src/orchestration/__tests__/worker-pending-timeout-abort.test.ts +275 -0
  148. package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +78 -1
  149. package/src/orchestration/__tests__/workflow-files.test.ts +186 -0
  150. package/src/orchestration/__tests__/workflow-run-summary.test.ts +119 -0
  151. package/src/orchestration/__tests__/workflow-script-registry-impl.test.ts +124 -0
  152. package/src/orchestration/agent-opts-resolver.ts +7 -7
  153. package/src/orchestration/args-meta.ts +198 -0
  154. package/src/orchestration/error-recovery.ts +416 -146
  155. package/src/orchestration/execute-agent-call.ts +9 -9
  156. package/src/orchestration/file-run-store.ts +327 -0
  157. package/src/orchestration/launcher.ts +35 -26
  158. package/src/orchestration/lifecycle.ts +355 -79
  159. package/src/orchestration/models/agent-call.ts +7 -7
  160. package/src/orchestration/models/budget.ts +5 -5
  161. package/src/orchestration/models/run-runtime.ts +13 -13
  162. package/src/orchestration/models/trace.ts +8 -8
  163. package/src/orchestration/models/workflow-run.ts +27 -27
  164. package/src/orchestration/models/workflow-script.ts +4 -4
  165. package/src/orchestration/run-snapshot.ts +266 -0
  166. package/src/orchestration/script-generate.ts +154 -0
  167. package/src/orchestration/script-lint.ts +33 -33
  168. package/src/orchestration/worker-handle.ts +10 -10
  169. package/src/orchestration/worker-host.ts +10 -10
  170. package/src/orchestration/worker-script-builder.ts +401 -346
  171. package/src/orchestration/workflow-files.ts +37 -11
  172. package/src/orchestration/workflow-run-summary.ts +69 -0
  173. package/src/orchestration/workflow-script-registry-impl.ts +31 -9
  174. package/src/shared/__tests__/agent-ref.test.ts +209 -2
  175. package/src/shared/__tests__/atomic-write.test.ts +267 -0
  176. package/src/shared/__tests__/bounded-serialize.test.ts +236 -0
  177. package/src/shared/__tests__/injection-render.test.ts +518 -0
  178. package/src/shared/__tests__/resource-discovery-host-roots.test.ts +474 -0
  179. package/src/shared/__tests__/resource-discovery.test.ts +3 -2
  180. package/src/shared/agent-ref.ts +143 -2
  181. package/src/shared/atomic-write.ts +320 -0
  182. package/src/shared/bounded-serialize.ts +154 -0
  183. package/src/shared/injection-render.ts +279 -0
  184. package/src/shared/meta-parser.ts +41 -1
  185. package/src/shared/resource-discovery.ts +104 -37
  186. package/src/shared/resource-meta.ts +14 -0
  187. package/src/shared/xml-injection.ts +9 -9
  188. package/workflows/README.md +9 -9
  189. package/workflows/chain.js +4 -2
  190. package/workflows/map-reduce.js +5 -3
  191. package/workflows/parallel.js +5 -3
  192. package/workflows/review-fix-loop-utils.cjs +14 -3
  193. package/workflows/review-fix-loop.js +18 -7
  194. package/workflows/scatter-gather.js +4 -2
  195. package/dist/chunk-3VOERJPJ.js +0 -22
@@ -5,7 +5,14 @@
5
5
  // M2-B1 把 spawnedChildren 从 Set<ChildProcess> 改为 Map<recordId, ChildProcess>,
6
6
  // 建立 record→child 映射(busy 投递定位活进程用,设计决策 6)。本文件测 Map API 核心行为:
7
7
  // - getChildByRecord:set 后能查到;delete 后查不到;未注册返回 undefined
8
- // - killAllSpawnedChildren:遍历 .values() kill + clear;跳过 child.killed=true;单 child kill 抛错不阻断
8
+ // - killAllSpawnedChildren:遍历 .values() kill + clear;单 child kill 抛错不阻断
9
+ // - [R1 D6③] killAllSpawnedChildren 编排扩容:先触发 engine registry dispose(触发不等待),
10
+ // 后杀 per-record children——dispose 全部先于任何 kill(D6① 时序)
11
+ //
12
+ // [T2-⑤ / LC-2] 跳过条件变更(原「跳过 child.killed=true」契约已废止):killed=发过
13
+ // kill 请求 ≠ 已死,只有 exitCode/signalCode 非 null(已确认死亡)才跳过;killed 但
14
+ // 未确认死亡的 child 补 SIGKILL 升级(SIGTERM 可能被无视)。新契约的完整行为矩阵见
15
+ // kill-all-escalation.test.ts(runSpawn 集成面),本文件保留纯 Map 遍历面的正/反例。
9
16
  //
10
17
  // 直接操作模块级 spawnedChildren(afterEach clear 防泄漏),不依赖 runSpawn/spawn/fs。
11
18
 
@@ -16,13 +23,57 @@ import { afterEach, describe, expect, it, vi } from "vitest";
16
23
  const { loggerMock } = vi.hoisted(() => ({
17
24
  loggerMock: { debug: vi.fn(), warn: vi.fn(), error: vi.fn() },
18
25
  }));
26
+ // mock core logger:session-runner 与 engine/registry 共用同一 facade 模块(两处
27
+ // `../../core/logger.ts` 解析到同一文件),本 mock 对二者同时生效。
19
28
  vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
20
29
 
30
+ import type { EnginePort } from "../engine/port.ts";
31
+ import { clearEngines, getEngine, registerEngine } from "../engine/registry.ts";
21
32
  import { getChildByRecord, killAllSpawnedChildren, spawnedChildren } from "../session-runner.ts";
22
33
 
23
- /** 假 child:只关心 killed 标记 + kill 调用(killAllSpawnedChildren 遍历用)。 */
24
- function makeFakeChild(killed = false): ChildProcess & { kill: ReturnType<typeof vi.fn> } {
25
- return { killed, kill: vi.fn() } as unknown as ChildProcess & { kill: ReturnType<typeof vi.fn> };
34
+ /**
35
+ * child:只关心 killed 标记 + kill 调用(killAllSpawnedChildren 遍历用)。
36
+ * extra 可注入 exitCode/signalCode(模拟已确认死亡的形态)。
37
+ */
38
+ function makeFakeChild(
39
+ killed = false,
40
+ extra: Partial<Pick<ChildProcess, "exitCode" | "signalCode">> = {},
41
+ ): ChildProcess & { kill: ReturnType<typeof vi.fn> } {
42
+ return { killed, kill: vi.fn(), ...extra } as unknown as ChildProcess & {
43
+ kill: ReturnType<typeof vi.fn>;
44
+ };
45
+ }
46
+
47
+ /** 带 dispose 的假引擎(编排测试用——dispose 触发顺序记进 events,与 child kill 事件共序)。 */
48
+ function makeDisposingEngine(
49
+ id: string,
50
+ events: string[],
51
+ disposeImpl?: () => Promise<void>,
52
+ ): EnginePort {
53
+ return {
54
+ id,
55
+ capabilities: () => ({
56
+ schemaEnforcement: "native",
57
+ steer: "unsupported",
58
+ conversation: "unsupported",
59
+ personaInjection: "prompt",
60
+ eventGranularity: "coarse",
61
+ sandbox: "none",
62
+ sessionRead: "outcome-only",
63
+ resume: "unsupported",
64
+ interrupt: "kill-only",
65
+ permissionMode: "fixed",
66
+ }),
67
+ probe: () =>
68
+ Promise.resolve({ ok: true, engineVersion: "0.0.0-test", checks: [{ name: "stub", ok: true }] }),
69
+ run: () => Promise.reject(new Error("fake engine: run not implemented")),
70
+ interact: () => Promise.resolve({ ok: false, code: "engine_capability_unsupported", message: "stub" }),
71
+ read: () => Promise.resolve({ engineId: id, turns: [], source: "outcome-only" }),
72
+ dispose: disposeImpl ?? (() => {
73
+ events.push(`dispose:${id}`);
74
+ return Promise.resolve();
75
+ }),
76
+ };
26
77
  }
27
78
 
28
79
  describe("spawnedChildren Map + getChildByRecord (M2-B1)", () => {
@@ -58,17 +109,29 @@ describe("spawnedChildren Map + getChildByRecord (M2-B1)", () => {
58
109
  expect(spawnedChildren.size).toBe(0);
59
110
  });
60
111
 
61
- it("killAllSpawnedChildren 跳过 child.killed=true(不重复 kill kill 的)", () => {
112
+ it("[T2-⑤] killed 但未确认死亡 不跳过,补 SIGKILL 升级(killed=发过请求≠已死)", () => {
62
113
  const alive = makeFakeChild(false);
63
- const alreadyKilled = makeFakeChild(true);
114
+ const killedNotClosed = makeFakeChild(true); // SIGTERM 已发、exit/signal 仍 null
64
115
  spawnedChildren.set("rec-1", alive);
65
- spawnedChildren.set("rec-2", alreadyKilled);
116
+ spawnedChildren.set("rec-2", killedNotClosed);
66
117
 
67
118
  const n = killAllSpawnedChildren();
68
119
 
69
- expect(n).toBe(1);
70
- expect(alive.kill).toHaveBeenCalledTimes(1);
71
- expect(alreadyKilled.kill).not.toHaveBeenCalled();
120
+ expect(n).toBe(2);
121
+ expect(alive.kill).toHaveBeenCalledWith("SIGTERM");
122
+ // dispose 是最后兜底,对 killed-not-closed 直接升级 SIGKILL(不再赌 SIGTERM 生效)
123
+ expect(killedNotClosed.kill).toHaveBeenCalledWith("SIGKILL");
124
+ expect(spawnedChildren.size).toBe(0);
125
+ });
126
+
127
+ it("[T2-⑤] killed 且已确认死亡(signalCode 非 null)→ 跳过,不重复发信号", () => {
128
+ const dead = makeFakeChild(true, { signalCode: "SIGTERM" });
129
+ spawnedChildren.set("rec-1", dead);
130
+
131
+ const n = killAllSpawnedChildren();
132
+
133
+ expect(n).toBe(0);
134
+ expect(dead.kill).not.toHaveBeenCalled();
72
135
  expect(spawnedChildren.size).toBe(0);
73
136
  });
74
137
 
@@ -90,3 +153,83 @@ describe("spawnedChildren Map + getChildByRecord (M2-B1)", () => {
90
153
  expect(spawnedChildren.size).toBe(0);
91
154
  });
92
155
  });
156
+
157
+ describe("killAllSpawnedChildren 编排扩容(R1 D6③:先引擎 dispose 后 per-record children)", () => {
158
+ afterEach(() => {
159
+ spawnedChildren.clear();
160
+ // registry 是进程级全局状态(Symbol.for 槽),防用例间 fake 引擎泄漏串扰
161
+ clearEngines();
162
+ });
163
+
164
+ it("先全部触发已实例化引擎 dispose,后遍历杀 children(D6① 时序:dispose 先于 SIGTERM)", () => {
165
+ const events: string[] = [];
166
+ registerEngine("e-alpha", () => makeDisposingEngine("e-alpha", events));
167
+ registerEngine("e-beta", () => makeDisposingEngine("e-beta", events));
168
+ getEngine("e-alpha"); // disposeEngines 只遍历已实例化单例,须先实例化
169
+ getEngine("e-beta");
170
+
171
+ const c1 = makeFakeChild();
172
+ const c2 = makeFakeChild();
173
+ c1.kill.mockImplementation(() => {
174
+ events.push("kill:rec-a");
175
+ });
176
+ c2.kill.mockImplementation(() => {
177
+ events.push("kill:rec-b");
178
+ });
179
+ spawnedChildren.set("rec-a", c1);
180
+ spawnedChildren.set("rec-b", c2);
181
+
182
+ const n = killAllSpawnedChildren();
183
+
184
+ expect(n).toBe(2);
185
+ // 时序断言(不依赖穿插序):最后一个 dispose 的索引 < 第一个 kill 的索引
186
+ const disposeIdx = events
187
+ .map((e, i) => (e.startsWith("dispose:") ? i : -1))
188
+ .filter((i) => i >= 0);
189
+ const firstKill = events.findIndex((e) => e.startsWith("kill:"));
190
+ expect(disposeIdx.length).toBe(2);
191
+ expect(firstKill).toBeGreaterThan(Math.max(...disposeIdx));
192
+ expect(spawnedChildren.size).toBe(0);
193
+ });
194
+
195
+ it("引擎 dispose 同步 throw 不阻断 children 清理(best-effort 编排,catch 记日志继续)", () => {
196
+ const events: string[] = [];
197
+ registerEngine(
198
+ "throwy",
199
+ () =>
200
+ makeDisposingEngine("throwy", events, () => {
201
+ throw new Error("dispose boom");
202
+ }),
203
+ );
204
+ getEngine("throwy");
205
+ const child = makeFakeChild();
206
+ child.kill.mockImplementation(() => {
207
+ events.push("kill:rec-1");
208
+ });
209
+ spawnedChildren.set("rec-1", child);
210
+
211
+ expect(() => killAllSpawnedChildren()).not.toThrow();
212
+ expect(child.kill).toHaveBeenCalledTimes(1);
213
+ expect(spawnedChildren.size).toBe(0);
214
+ expect(loggerMock.warn).toHaveBeenCalledWith(
215
+ expect.stringContaining("[engine-registry] engine 'throwy' dispose threw synchronously"),
216
+ );
217
+ });
218
+
219
+ it("引擎仅注册工厂未实例化 → 不触发 dispose 也不实例化(停机路径不反向创建资源)", () => {
220
+ const events: string[] = [];
221
+ const engine = makeDisposingEngine("unused", events);
222
+ registerEngine("unused", () => engine);
223
+ const child = makeFakeChild();
224
+ child.kill.mockImplementation(() => {
225
+ events.push("kill:rec-1");
226
+ });
227
+ spawnedChildren.set("rec-1", child);
228
+
229
+ const n = killAllSpawnedChildren();
230
+
231
+ expect(n).toBe(1);
232
+ expect(events).toEqual(["kill:rec-1"]); // 无 dispose 事件混入
233
+ expect(child.kill).toHaveBeenCalledTimes(1);
234
+ });
235
+ });