@zhushanwen/subagent-core 0.6.0 → 0.7.1

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 (278) hide show
  1. package/README.md +1 -1
  2. package/dist/chunk-KXVBIVOU.js +16 -0
  3. package/dist/chunk-T2SYBW3I.js +24 -0
  4. package/dist/chunk-VURUAGMM.js +2305 -0
  5. package/dist/{types-C3fE3R9x.d.cts → engine-discovery-scan-ocpM8FmI.d.cts} +770 -668
  6. package/dist/{types-C3fE3R9x.d.ts → engine-discovery-scan-ocpM8FmI.d.ts} +770 -668
  7. package/dist/execution/engine/engine-discovery-scan.cjs +2210 -0
  8. package/dist/execution/engine/engine-discovery-scan.d.cts +4 -0
  9. package/dist/execution/engine/engine-discovery-scan.d.ts +4 -0
  10. package/dist/execution/engine/engine-discovery-scan.js +20 -0
  11. package/dist/execution/engine/paths.cjs +6 -23
  12. package/dist/execution/engine/paths.d.cts +1 -8
  13. package/dist/execution/engine/paths.d.ts +1 -8
  14. package/dist/execution/engine/paths.js +1 -1
  15. package/dist/execution/relay-env.cjs +12 -28
  16. package/dist/execution/relay-env.d.cts +1 -33
  17. package/dist/execution/relay-env.d.ts +1 -33
  18. package/dist/execution/relay-env.js +5 -3
  19. package/dist/index.cjs +14045 -16059
  20. package/dist/index.d.cts +1035 -1579
  21. package/dist/index.d.ts +1035 -1579
  22. package/dist/index.js +9838 -13800
  23. package/dist.bundle/index.cjs +28875 -30323
  24. package/package.json +20 -26
  25. package/src/__tests__/record-store-last-line.test.ts +16 -7
  26. package/src/core/error-message.ts +7 -6
  27. package/src/core/host-services.ts +8 -2
  28. package/src/core/notify-ports.ts +17 -2
  29. package/src/execution/__tests__/chat-engine-routing.test.ts +62 -28
  30. package/src/execution/__tests__/chat-round-first-round-watchdog.test.ts +255 -0
  31. package/src/execution/__tests__/collect-coordinator-service.test.ts +36 -47
  32. package/src/execution/__tests__/collect-mixed-dispatch.test.ts +20 -53
  33. package/src/execution/__tests__/delivery-methods.test.ts +179 -237
  34. package/src/execution/__tests__/execute-and-await-worktree.test.ts +0 -2
  35. package/src/execution/__tests__/execute-nesting.test.ts +20 -13
  36. package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +20 -33
  37. package/src/execution/__tests__/gc-timer.test.ts +7 -22
  38. package/src/execution/__tests__/get-record-for-action-restart.test.ts +20 -10
  39. package/src/execution/__tests__/helpers/fake-engine-port.ts +187 -0
  40. package/src/execution/__tests__/helpers/spawn-mock.ts +36 -9
  41. package/src/execution/__tests__/helpers/subagent-service-mocks.ts +1 -1
  42. package/src/execution/__tests__/idle-gc.test.ts +200 -0
  43. package/src/execution/__tests__/lifecycle-predicates.test.ts +26 -21
  44. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +5 -2
  45. package/src/execution/__tests__/reconcile-sweep.test.ts +200 -0
  46. package/src/execution/__tests__/record-store.test.ts +60 -6
  47. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +20 -13
  48. package/src/execution/__tests__/round-supervisor.test.ts +293 -0
  49. package/src/execution/__tests__/session-pending.test.ts +119 -17
  50. package/src/execution/__tests__/stream-sink-retirement.test.ts +10 -10
  51. package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +1 -11
  52. package/src/execution/__tests__/subagent-service-notify-gate.test.ts +33 -30
  53. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +2 -9
  54. package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +59 -129
  55. package/src/execution/__tests__/subagent-service.test.ts +2 -2
  56. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +109 -7
  57. package/src/execution/__tests__/subprocess-agent-runner.test.ts +108 -36
  58. package/src/execution/__tests__/sync-collect-recovery.test.ts +21 -36
  59. package/src/execution/__tests__/workflow-state-root.test.ts +232 -0
  60. package/src/execution/agent-registry.ts +6 -2
  61. package/src/execution/dialog-queue.ts +6 -73
  62. package/src/execution/engine/__tests__/common/capability-gate.test.ts +115 -15
  63. package/src/execution/engine/__tests__/common/errors.test.ts +11 -18
  64. package/src/execution/engine/__tests__/common/kill-chain.test.ts +7 -0
  65. package/src/execution/engine/__tests__/common/nesting-guard.test.ts +9 -3
  66. package/src/execution/engine/__tests__/common/session-view-service.test.ts +90 -3
  67. package/src/execution/engine/__tests__/conformance/H9-test-disposition.md +116 -0
  68. package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/fake-engine-protocol.mjs +377 -0
  69. package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/recorded.fixture.json +331 -0
  70. package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/smoke-run.fixture.json +213 -0
  71. package/src/execution/engine/__tests__/conformance/chat-round-protocol.test.ts +230 -0
  72. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +69 -42
  73. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +4 -81
  74. package/src/execution/engine/__tests__/conformance/contract.probe.test.ts +67 -44
  75. package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +81 -54
  76. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +133 -70
  77. package/src/execution/engine/__tests__/conformance/engine-crash.test.ts +143 -0
  78. package/src/execution/engine/__tests__/conformance/fake-engine-capabilities.ts +19 -0
  79. package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +7 -2
  80. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +6 -5
  81. package/src/execution/engine/__tests__/conformance/protocol-blackbox.test.ts +398 -0
  82. package/src/execution/engine/__tests__/conformance/registry-fork-filter.test.ts +221 -0
  83. package/src/execution/engine/__tests__/conformance/round-liveness-supervisor.test.ts +302 -0
  84. package/src/execution/engine/__tests__/conformance/static-spawn-guard.test.ts +58 -0
  85. package/src/execution/engine/__tests__/engine-discovery-scan.test.ts +558 -0
  86. package/src/execution/engine/__tests__/host-task-spec.test.ts +45 -0
  87. package/src/execution/engine/__tests__/model-ref-split.test.ts +74 -0
  88. package/src/execution/engine/__tests__/registry.test.ts +206 -1
  89. package/src/execution/engine/__tests__/routing.test.ts +97 -0
  90. package/src/execution/engine/__tests__/run-failure-worktree-cleanup.test.ts +97 -0
  91. package/src/execution/engine/client/__tests__/__fixtures__/fake-engine.mjs +294 -0
  92. package/src/execution/engine/client/__tests__/engine-client.test.ts +516 -0
  93. package/src/execution/engine/client/__tests__/mirror.test.ts +67 -0
  94. package/src/execution/engine/client/__tests__/pid-file.test.ts +257 -0
  95. package/src/execution/engine/client/__tests__/protocol-closure.test.ts +95 -0
  96. package/src/execution/engine/client/__tests__/remote-engine.test.ts +457 -0
  97. package/src/execution/engine/client/client-options.ts +103 -0
  98. package/src/execution/engine/client/engine-client.ts +687 -0
  99. package/src/execution/engine/client/mirror.ts +167 -0
  100. package/src/execution/engine/client/pid-file.ts +392 -0
  101. package/src/execution/engine/client/reaper.ts +95 -0
  102. package/src/execution/engine/client/remote-engine.ts +441 -0
  103. package/src/execution/engine/client/reverse-router.ts +255 -0
  104. package/src/execution/engine/common/capability-gate.ts +109 -22
  105. package/src/execution/engine/common/errors.ts +9 -25
  106. package/src/execution/engine/common/event-journal.ts +4 -50
  107. package/src/execution/engine/common/journal-replay.ts +3 -1
  108. package/src/execution/engine/common/kill-chain.ts +18 -251
  109. package/src/execution/engine/common/nesting-guard.ts +18 -114
  110. package/src/execution/engine/common/pool-manager.ts +4 -0
  111. package/src/execution/engine/common/session-view-service.ts +56 -84
  112. package/src/execution/engine/config.ts +151 -0
  113. package/src/execution/engine/d8-compat.ts +346 -0
  114. package/src/execution/engine/engine-discovery-roots.ts +107 -0
  115. package/src/execution/engine/engine-discovery-scan.ts +269 -0
  116. package/src/execution/engine/engine-discovery.ts +30 -7
  117. package/src/execution/engine/engine-inspect-package.ts +282 -0
  118. package/src/execution/engine/engine-manifest.ts +244 -0
  119. package/src/execution/engine/host/__tests__/host-askuser-endpoint.test.ts +123 -0
  120. package/src/execution/engine/host/__tests__/host-bridge.test.ts +135 -0
  121. package/src/execution/engine/host/__tests__/spawned-children.test.ts +73 -0
  122. package/src/execution/engine/host/host-bridge.ts +150 -0
  123. package/src/execution/engine/host/host-ui-endpoint.ts +47 -0
  124. package/src/execution/engine/host/pi-host-binding.ts +91 -0
  125. package/src/execution/engine/host/spawned-children.ts +139 -0
  126. package/src/execution/engine/host-task-spec.ts +29 -2
  127. package/src/execution/engine/model-validation.ts +55 -2
  128. package/src/execution/engine/paths.ts +16 -41
  129. package/src/execution/engine/port.ts +48 -2
  130. package/src/execution/engine/registry.ts +153 -34
  131. package/src/execution/engine/routing.ts +225 -52
  132. package/src/execution/engine/types.ts +25 -200
  133. package/src/execution/finalize-record.ts +25 -0
  134. package/src/execution/idle-gc.ts +89 -14
  135. package/src/execution/lifecycle-manager.ts +10 -7
  136. package/src/execution/lifecycle-predicates.ts +10 -8
  137. package/src/execution/record-store.ts +29 -2
  138. package/src/execution/relay-env.ts +19 -36
  139. package/src/execution/round-supervisor/domain.ts +89 -0
  140. package/src/execution/round-supervisor/index.ts +33 -0
  141. package/src/execution/round-supervisor/notify-accounting.ts +87 -0
  142. package/src/execution/round-supervisor/reconcile-sweep.ts +235 -0
  143. package/src/execution/round-supervisor/service-binding.test.ts +661 -0
  144. package/src/execution/round-supervisor/service-binding.ts +273 -0
  145. package/src/execution/round-supervisor/supervisor.ts +426 -0
  146. package/src/execution/session-pending.ts +68 -74
  147. package/src/execution/settled-watchdog.ts +39 -0
  148. package/src/execution/subagent-service.ts +746 -400
  149. package/src/execution/subprocess-agent-runner.ts +16 -8
  150. package/src/execution/types.ts +41 -117
  151. package/src/execution/ui-channels.ts +13 -214
  152. package/src/execution/ui-request-handler-factory.ts +7 -4
  153. package/src/execution/ui-request-observability.ts +2 -2
  154. package/src/execution/workflow-state-root.ts +67 -0
  155. package/src/execution/worktree-git-ops.ts +5 -0
  156. package/src/execution/worktree-manager.ts +11 -1
  157. package/src/index.ts +55 -26
  158. package/src/orchestration/__tests__/execute-agent-call.test.ts +1 -1
  159. package/src/orchestration/execute-agent-call.ts +1 -1
  160. package/src/orchestration/file-run-store.ts +144 -59
  161. package/src/orchestration/models/types.ts +43 -42
  162. package/src/shared/zcode-model-ref.ts +31 -0
  163. package/dist/chunk-43ONBFZX.js +0 -240
  164. package/dist/chunk-A4IVZWVX.js +0 -31
  165. package/dist/chunk-APZY4IME.js +0 -27
  166. package/dist/chunk-V3VHZ2VX.js +0 -59
  167. package/dist/execution/engine/engines/zcode/constants.cjs +0 -107
  168. package/dist/execution/engine/engines/zcode/constants.d.cts +0 -135
  169. package/dist/execution/engine/engines/zcode/constants.d.ts +0 -135
  170. package/dist/execution/engine/engines/zcode/constants.js +0 -54
  171. package/dist/execution/engine/engines/zcode/reader.cjs +0 -276
  172. package/dist/execution/engine/engines/zcode/reader.d.cts +0 -21
  173. package/dist/execution/engine/engines/zcode/reader.d.ts +0 -21
  174. package/dist/execution/engine/engines/zcode/reader.js +0 -9
  175. package/src/__tests__/append-system-prompt-assembly.test.ts +0 -243
  176. package/src/__tests__/fr4-get-state-handshake.test.ts +0 -125
  177. package/src/__tests__/session-runner.test.ts +0 -69
  178. package/src/execution/__tests__/ask-user-transit-e2e.test.ts +0 -490
  179. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +0 -191
  180. package/src/execution/__tests__/descendant-sweep-guards.test.ts +0 -220
  181. package/src/execution/__tests__/descendant-sweep.test.ts +0 -269
  182. package/src/execution/__tests__/engine-model-validation.test.ts +0 -741
  183. package/src/execution/__tests__/epipe-fallback.test.ts +0 -248
  184. package/src/execution/__tests__/execution-runtime-face.test.ts +0 -272
  185. package/src/execution/__tests__/get-state-handshake.test.ts +0 -127
  186. package/src/execution/__tests__/helpers/session-runner-mocks.ts +0 -113
  187. package/src/execution/__tests__/keep-alive-no-progress.test.ts +0 -280
  188. package/src/execution/__tests__/kill-all-escalation.test.ts +0 -196
  189. package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +0 -113
  190. package/src/execution/__tests__/output-collector.test.ts +0 -469
  191. package/src/execution/__tests__/pi-invocation.test.ts +0 -166
  192. package/src/execution/__tests__/recursive-visibility-env.test.ts +0 -149
  193. package/src/execution/__tests__/rpc-mode.test.ts +0 -89
  194. package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +0 -387
  195. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +0 -277
  196. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +0 -254
  197. package/src/execution/__tests__/run-spawn-edges.test.ts +0 -1079
  198. package/src/execution/__tests__/run-spawn-integration.test.ts +0 -933
  199. package/src/execution/__tests__/run-spawn-resume.test.ts +0 -322
  200. package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +0 -196
  201. package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +0 -207
  202. package/src/execution/__tests__/service-kill-escalation.test.ts +0 -91
  203. package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +0 -110
  204. package/src/execution/__tests__/session-runner-close-prune.test.ts +0 -181
  205. package/src/execution/__tests__/session-runner-dispatch.test.ts +0 -489
  206. package/src/execution/__tests__/session-runner-epipe.test.ts +0 -179
  207. package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +0 -227
  208. package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +0 -290
  209. package/src/execution/__tests__/session-runner-schema-env.test.ts +0 -352
  210. package/src/execution/__tests__/settled-watchdog.test.ts +0 -524
  211. package/src/execution/__tests__/spawn-args.test.ts +0 -446
  212. package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +0 -192
  213. package/src/execution/__tests__/spawn-event-adapter.test.ts +0 -167
  214. package/src/execution/__tests__/spawn-worktree-guidance.test.ts +0 -208
  215. package/src/execution/__tests__/spawned-children.test.ts +0 -236
  216. package/src/execution/__tests__/start-sync-model-guard.test.ts +0 -150
  217. package/src/execution/__tests__/stdin-writer.test.ts +0 -464
  218. package/src/execution/__tests__/subagent-service-message-close.test.ts +0 -693
  219. package/src/execution/__tests__/temp-prompt.test.ts +0 -53
  220. package/src/execution/__tests__/timeout-integration.test.ts +0 -616
  221. package/src/execution/__tests__/turn-limiter-semantics.test.ts +0 -194
  222. package/src/execution/__tests__/turn-limiter.test.ts +0 -65
  223. package/src/execution/__tests__/ui-request-handler.test.ts +0 -205
  224. package/src/execution/__tests__/ui-request-queue.test.ts +0 -298
  225. package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +0 -230
  226. package/src/execution/engine/__tests__/common/schema-emulation.test.ts +0 -128
  227. package/src/execution/engine/__tests__/common/session-view-service-zcode-dbpath.test.ts +0 -149
  228. package/src/execution/engine/__tests__/conformance/__fixtures__/pi-golden-events.json +0 -28
  229. package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +0 -128
  230. package/src/execution/engine/__tests__/paths.test.ts +0 -39
  231. package/src/execution/engine/common/schema-emulation.ts +0 -189
  232. package/src/execution/engine/common/session-view-projection.ts +0 -51
  233. package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +0 -615
  234. package/src/execution/engine/engines/pi/__tests__/reader.test.ts +0 -155
  235. package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +0 -257
  236. package/src/execution/engine/engines/pi/argv-mirror.ts +0 -135
  237. package/src/execution/engine/engines/pi/get-state-handshake.ts +0 -179
  238. package/src/execution/engine/engines/pi/output-collector.ts +0 -317
  239. package/src/execution/engine/engines/pi/pi-engine.ts +0 -811
  240. package/src/execution/engine/engines/pi/pi-invocation.ts +0 -120
  241. package/src/execution/engine/engines/pi/reader.ts +0 -59
  242. package/src/execution/engine/engines/pi/registration.ts +0 -37
  243. package/src/execution/engine/engines/pi/session-runner.ts +0 -2840
  244. package/src/execution/engine/engines/pi/spawn-event-adapter.ts +0 -404
  245. package/src/execution/engine/engines/pi/stdin-writer.ts +0 -199
  246. package/src/execution/engine/engines/pi/temp-prompt.ts +0 -62
  247. package/src/execution/engine/engines/pi/turn-limiter.ts +0 -102
  248. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +0 -301
  249. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-appserver.json +0 -36
  250. package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +0 -321
  251. package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +0 -473
  252. package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +0 -43
  253. package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +0 -124
  254. package/src/execution/engine/engines/zcode/__tests__/reader.test.ts +0 -210
  255. package/src/execution/engine/engines/zcode/__tests__/registration.test.ts +0 -71
  256. package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +0 -247
  257. package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +0 -451
  258. package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +0 -792
  259. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +0 -782
  260. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +0 -168
  261. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +0 -210
  262. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +0 -353
  263. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +0 -282
  264. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +0 -372
  265. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +0 -145
  266. package/src/execution/engine/engines/zcode/appserver-launcher.ts +0 -167
  267. package/src/execution/engine/engines/zcode/connection.ts +0 -598
  268. package/src/execution/engine/engines/zcode/constants.ts +0 -176
  269. package/src/execution/engine/engines/zcode/golden-sample.ts +0 -47
  270. package/src/execution/engine/engines/zcode/parser.ts +0 -102
  271. package/src/execution/engine/engines/zcode/preparer.ts +0 -235
  272. package/src/execution/engine/engines/zcode/reader.ts +0 -381
  273. package/src/execution/engine/engines/zcode/registration.ts +0 -37
  274. package/src/execution/engine/engines/zcode/session-channel.ts +0 -968
  275. package/src/execution/engine/engines/zcode/zcode-engine.ts +0 -1431
  276. package/src/execution/round-settlement.ts +0 -93
  277. package/src/execution/ui-request-queue.ts +0 -215
  278. package/src/shared/schema-env.ts +0 -44
@@ -1,113 +0,0 @@
1
- // src/execution/__tests__/helpers/session-runner-mocks.ts
2
- //
3
- // session-runner 系测试(keep-alive-no-progress / recursive-visibility-env 等)的
4
- // 测试文件侧装配:vi.mocked 取回 + spawn/keep-alive 前奏 + 子进程收尾。
5
- //
6
- // 与 spawn-mock.ts 的分工(依赖约束,勿混淆):
7
- // - spawn-mock.ts 会被各 vi.mock 工厂 `await import()`(求值时机 = 被 mock 模块首次
8
- // 请求),故它禁止值依赖 session-runner / alive-store / session-pending——否则形成
9
- // 「mock 工厂 → helper → session-runner → node:child_process(mock 求值中)」循环。
10
- // mock 模块工厂 + FakeChild + 纯 fixture 都在 spawn-mock.ts。
11
- // - 本文件只被测试文件顶层静态 import(vi.mock 已 hoist 注册完毕、mock 工厂已可安全
12
- // 执行),故可值依赖 session-runner / alive-store / session-pending 做 vi.mocked 取回
13
- // 与 runSpawn 前奏封装。
14
-
15
- import { spawn } from "node:child_process";
16
- import * as fs from "node:fs";
17
-
18
- import { afterEach, beforeEach, vi } from "vitest";
19
-
20
- import { runSpawn, SPAWN_WATCHDOG_ENV } from "../../engine/engines/pi/session-runner.ts";
21
- import {
22
- listActivePendingFromSessionFile,
23
- readActivePendingFromSessionFile,
24
- } from "../../session-pending.ts";
25
- import { isProcessAlive, readAliveMarker, writeAliveMarker } from "../../alive-store.ts";
26
- import {
27
- emitStdoutLine,
28
- lastSpawnedChild,
29
- makeCtx,
30
- makeOpts,
31
- makeRecord,
32
- waitForSpawn,
33
- type FakeChild,
34
- } from "./spawn-mock.ts";
35
-
36
- /** keep-alive 用例的 session header 行(stdout 首行)。 */
37
- function sessionHeaderLine(): Record<string, unknown> {
38
- return { type: "session", id: "sess-ka", timestamp: "2026-09-01T00:00:00.000Z", cwd: "/tmp/test" };
39
- }
40
-
41
- /** vi.mocked 取回超集(各测试文件按需解构;与各 vi.mock 工厂返回同一 mock 实例)。 */
42
- export function takeSessionRunnerMocks() {
43
- return {
44
- mockSpawn: vi.mocked(spawn),
45
- mockExistsSync: vi.mocked(fs.existsSync),
46
- mockReaddirSync: vi.mocked(fs.readdirSync as (path: fs.PathLike) => string[]),
47
- mockPending: vi.mocked(readActivePendingFromSessionFile),
48
- mockListPending: vi.mocked(listActivePendingFromSessionFile),
49
- mockReadAliveMarker: vi.mocked(readAliveMarker),
50
- mockWriteAliveMarker: vi.mocked(writeAliveMarker),
51
- mockIsProcessAlive: vi.mocked(isProcessAlive),
52
- };
53
- }
54
-
55
- /**
56
- * 非 chatMode 层主 + fake timers + agent_end keep-alive 落位的公共前奏。
57
- *
58
- * 返回 promise(runSpawn 原始 promise,供手动 exit/close 收尾的用例)与 finish
59
- * (标准 close 收尾)两种取用形态。
60
- */
61
- export async function spawnAndReachKeepAlive(
62
- opts = makeOpts(),
63
- task = "Task: keep-alive no-progress",
64
- ): Promise<{
65
- child: FakeChild;
66
- promise: Promise<Awaited<ReturnType<typeof runSpawn>>>;
67
- finish: (code?: number) => Promise<Awaited<ReturnType<typeof runSpawn>>>;
68
- }> {
69
- const record = makeRecord();
70
- const promise = runSpawn(record, task, opts, makeCtx());
71
- await waitForSpawn(vi.mocked(spawn));
72
- const child = lastSpawnedChild(vi.mocked(spawn));
73
- // fake timers 必须在 emit agent_end 之前启用(keep-alive timer 新建于 agent_end
74
- // 处理器内;不 fake setImmediate——stream flush 靠真实事件循环交付,见 MF-3 先例)。
75
- vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout", "Date"] });
76
- emitStdoutLine(child, sessionHeaderLine());
77
- emitStdoutLine(child, { type: "agent_end", messages: [], willRetry: false });
78
- await new Promise((r) => setImmediate(r));
79
- return {
80
- child,
81
- promise,
82
- finish: (code = 143) => {
83
- child.stdout.end();
84
- child.stderr.end();
85
- child.emit("close", code);
86
- return promise as Promise<Awaited<ReturnType<typeof runSpawn>>>;
87
- },
88
- };
89
- }
90
-
91
- /** 让最近 spawn 的 FakeChild 发 close(0) 并等 runSpawn 收尾(env 注入用例的标准收尾装配)。 */
92
- export async function closeLastChildAndAwait<T>(promise: Promise<T>): Promise<T> {
93
- const child = lastSpawnedChild(vi.mocked(spawn));
94
- child.emit("close", 0);
95
- return promise;
96
- }
97
-
98
- /** keep-alive 系测试的统一 hooks:清 mock + stub 空 watchdog env + 统一恢复(在 describe 内调用)。 */
99
- export function keepAliveTestHooks(
100
- mockPending: ReturnType<typeof takeSessionRunnerMocks>["mockPending"],
101
- ): void {
102
- beforeEach(() => {
103
- vi.clearAllMocks();
104
- vi.stubEnv(SPAWN_WATCHDOG_ENV, "");
105
- mockPending.mockReturnValue({ count: 1, recentUnregister: false });
106
- });
107
-
108
- afterEach(() => {
109
- vi.restoreAllMocks();
110
- vi.unstubAllEnvs();
111
- vi.useRealTimers();
112
- });
113
- }
@@ -1,280 +0,0 @@
1
- // src/execution/__tests__/keep-alive-no-progress.test.ts
2
- //
3
- // [T2-① / P-T2 降级路径 B] keep-alive 裸缺省无进展检测上界。
4
- //
5
- // 设计:docs/design/subagent-core-unbounded-wait-audit.md §7.2 T2-① + impl-plan §5
6
- // P-T2 裁决(probe/p-t2-report.md):历史 89 样本 96.6% keep-alive 窗口 >30min、
7
- // 长尾 95.5h 合法(parent-shutdown 收敛)——固定 30min 上限被数据否定,按设计降级
8
- // 路径 B 落地为无进展检测语义:
9
- // - 裸缺省(无 maxTurns 无 env):keep-alive 期间任何子进程 stdout 活动刷新计时,
10
- // 连续静默达 KEEP_ALIVE_NO_PROGRESS_TIMEOUT_MS(30min)且 fire 时惰性复核无存活
11
- // 后代(A1-2)才处置;
12
- // - 显式 maxTurns / env:行为不变(既有固定时长等待,不挂无进展 timer——opt-out
13
- // 语义保留,A1-1:显式 maxTurns<=0 与 fail-fast 降级也不挂)。
14
- //
15
- // mock 布局与 run-spawn-edges.test.ts 一致(FakeChild + mock session-pending)。
16
- // mock 工厂与 loggerMock 单源收敛 helpers/spawn-mock.ts;vi.mocked 取回与
17
- // spawn/keep-alive 前奏收敛 helpers/session-runner-mocks.ts(分工见后者头注释)。
18
-
19
- import { describe, expect, it, vi } from "vitest";
20
-
21
- // [A1-2③] Mock 共享 logger:复核失败的 warn 留痕可被断言(对齐 run-spawn-edges 模式)。
22
- vi.mock("../../core/logger.ts", async () =>
23
- (await import("./helpers/spawn-mock.ts")).coreLoggerModule());
24
-
25
- vi.mock("node:child_process", async () =>
26
- (await import("./helpers/spawn-mock.ts")).childProcessModule());
27
- vi.mock("node:fs", async () => (await import("./helpers/spawn-mock.ts")).fsModule());
28
- vi.mock("../alive-store.ts", async () =>
29
- (await import("./helpers/spawn-mock.ts")).aliveStoreModule());
30
-
31
- // keep-alive 判定:本文件统一 count>0(有活跃后代 → keep-alive 分支)。
32
- vi.mock("../session-pending.ts", async () =>
33
- (await import("./helpers/spawn-mock.ts")).sessionPendingModule());
34
-
35
- vi.mock("../engine/engines/pi/temp-prompt.ts", async () =>
36
- (await import("./helpers/spawn-mock.ts")).tempPromptModule());
37
-
38
- import {
39
- KEEP_ALIVE_NO_PROGRESS_TIMEOUT_MS,
40
- maxTurnsToWatchdogMs,
41
- SPAWN_WATCHDOG_ENV,
42
- } from "../engine/engines/pi/session-runner.ts";
43
- import {
44
- emitStdoutLine,
45
- type FakeChild,
46
- lastSpawnedChild as lastSpawnedChildOf,
47
- loggerMock,
48
- makeOpts,
49
- waitForSpawn as waitForSpawnOf,
50
- } from "./helpers/spawn-mock.ts";
51
- import { keepAliveTestHooks, spawnAndReachKeepAlive, takeSessionRunnerMocks } from "./helpers/session-runner-mocks.ts";
52
-
53
- const {
54
- mockSpawn,
55
- mockPending,
56
- mockListPending,
57
- mockReadAliveMarker,
58
- mockWriteAliveMarker,
59
- mockIsProcessAlive,
60
- mockReaddirSync,
61
- } = takeSessionRunnerMocks();
62
-
63
- /** A1-2 复核用例的后代 pid(存/死两形态,对齐 descendant-sweep.test.ts 数值风格)。 */
64
- const LIVE_DESCENDANT_PID = 424_242;
65
- const DEAD_DESCENDANT_PID = 999_999;
66
-
67
- const lastSpawnedChild = (): FakeChild => lastSpawnedChildOf(mockSpawn);
68
- const waitForSpawn = (timeoutMs = 1000): Promise<void> => waitForSpawnOf(mockSpawn, timeoutMs);
69
-
70
- /** A1-2 复核用例的差集后代装配:pending 项 + session 文件名 + marker + pid 存活形态。 */
71
- function mockDescendant(sessionId: string, pid: number, alive: boolean): void {
72
- mockListPending.mockReturnValue({
73
- items: [{ id: "bg-1", sessionId, type: "session" }],
74
- });
75
- mockReaddirSync.mockReturnValue([`20260901T000000-000_${sessionId}.jsonl`]);
76
- mockReadAliveMarker.mockImplementation((f: unknown) =>
77
- String(f).endsWith(`_${sessionId}.jsonl`)
78
- ? { pid, id: sessionId, startedAt: 0 }
79
- : undefined,
80
- );
81
- mockIsProcessAlive.mockImplementation((p: number) => (p === pid ? alive : false));
82
- }
83
-
84
- describe("[T2-①] keep-alive 裸缺省无进展检测上界(P-T2 降级路径 B)", () => {
85
- keepAliveTestHooks(mockPending);
86
-
87
- it("裸缺省:agent_end keep-alive 挂无进展 timer,连续静默 30min 触发 SIGTERM 处置", async () => {
88
- const { child, finish } = await spawnAndReachKeepAlive();
89
-
90
- // keep-alive 落位:进程保活
91
- expect(child.killed).toBe(false);
92
-
93
- // 静默 30min-1ms:未达阈值,不处置
94
- await vi.advanceTimersByTimeAsync(KEEP_ALIVE_NO_PROGRESS_TIMEOUT_MS - 1);
95
- expect(child.killed).toBe(false);
96
-
97
- // 连续静默达 30min:处置(SIGTERM,升级由 killChildWithEscalation 承接)
98
- await vi.advanceTimersByTimeAsync(1);
99
- expect(child.killed).toBe(true);
100
- expect(child.killSignal).toBe("SIGTERM");
101
-
102
- const result = await finish();
103
- expect(result.success).toBe(true); // 信号终止视为正常完成(既有语义,T2-③ 形态除外)
104
- });
105
-
106
- it("裸缺省:keep-alive 期间 stdout 活动刷新计时——持续输出的层主不被时长上限误杀", async () => {
107
- const { child, finish } = await spawnAndReachKeepAlive();
108
- expect(child.killed).toBe(false);
109
-
110
- // 静默 29min 后子进程产生一次输出(任意行):静默计时被刷新
111
- await vi.advanceTimersByTimeAsync(KEEP_ALIVE_NO_PROGRESS_TIMEOUT_MS - 60_000);
112
- emitStdoutLine(child, { type: "turn_end" });
113
- await new Promise((r) => setImmediate(r));
114
- expect(child.killed).toBe(false);
115
-
116
- // 再静默 29min(相对上次活动 <30min):若无刷新语义此刻已累计 58min 会被误杀
117
- await vi.advanceTimersByTimeAsync(KEEP_ALIVE_NO_PROGRESS_TIMEOUT_MS - 60_000);
118
- expect(child.killed).toBe(false);
119
-
120
- // 刷新后再静默满 30min:连续静默达阈值,处置
121
- await vi.advanceTimersByTimeAsync(60_000);
122
- expect(child.killed).toBe(true);
123
- expect(child.killSignal).toBe("SIGTERM");
124
-
125
- await finish();
126
- });
127
-
128
- it("显式 maxTurns:行为不变——无进展 timer 不挂,30min 静默点不处置,动态超时到期才 kill", async () => {
129
- const maxTurns = 20; // maxTurnsToWatchdogMs(20) = 100min
130
- const { child, finish } = await spawnAndReachKeepAlive(makeOpts({ maxTurns }));
131
-
132
- // 关键反证断言:裸缺省阈值(30min)处连续静默也不 kill——无进展 timer 未挂载
133
- await vi.advanceTimersByTimeAsync(KEEP_ALIVE_NO_PROGRESS_TIMEOUT_MS);
134
- expect(child.killed).toBe(false);
135
-
136
- // 既有 MF-4 动态超时行为不变:100min 到期才 kill
137
- await vi.advanceTimersByTimeAsync(maxTurnsToWatchdogMs(maxTurns) - KEEP_ALIVE_NO_PROGRESS_TIMEOUT_MS - 1);
138
- expect(child.killed).toBe(false);
139
- await vi.advanceTimersByTimeAsync(1);
140
- expect(child.killed).toBe(true);
141
- expect(child.killSignal).toBe("SIGTERM");
142
-
143
- await finish();
144
- });
145
-
146
- it("显式 env 兑底:行为不变——按 env 绝对时限固定等待,不挂无进展 timer", async () => {
147
- vi.stubEnv(SPAWN_WATCHDOG_ENV, "600000"); // 10min(< 30min 静默阈值,反证无进展 timer 未挂)
148
- const { child, finish } = await spawnAndReachKeepAlive();
149
-
150
- await vi.advanceTimersByTimeAsync(600_000);
151
- expect(child.killed).toBe(true);
152
- expect(child.killSignal).toBe("SIGTERM");
153
-
154
- await finish();
155
- });
156
-
157
- it("keep-alive 结束(后代全部完成 → final kill)即撤无进展 timer,无残留误杀", async () => {
158
- // 先走 keep-alive(count>0)挂无进展 timer,再被唤醒重评估为 count=0 → final kill
159
- mockPending
160
- .mockReturnValueOnce({ count: 1, recentUnregister: false })
161
- .mockReturnValueOnce({ count: 0, recentUnregister: false });
162
- const { child, promise } = await spawnAndReachKeepAlive(undefined, "Task: keep-then-final");
163
- expect(child.killed).toBe(false);
164
-
165
- // 第二次 agent_end:后代已完成 → final kill(keep-alive 结束,无进展 timer 同撤)
166
- emitStdoutLine(child, { type: "agent_end", messages: [], willRetry: false });
167
- await new Promise((r) => setImmediate(r));
168
- expect(child.killed).toBe(true);
169
-
170
- // 反证 timer 已撤:advance 30min 不再有任何残留 kill 副作用(进程已 close 收尾)
171
- // 真实 ChildProcess 语义:被 SIGTERM 杀死 → 先 exit(null,'SIGTERM') 再 close。只发
172
- // close 不发 exit 时 killChain 升级窗口不结算(waitForExit 挂 exit 事件),下方
173
- // advance 30min+ 会越过 30s grace 误触发 SIGKILL 升级——补 exit 忠实模拟进程已死。
174
- child.signalCode = "SIGTERM";
175
- child.emit("exit", null, "SIGTERM");
176
- child.stdout.end();
177
- child.stderr.end();
178
- child.emit("close", 143);
179
- const result = await promise;
180
- expect(result.success).toBe(true);
181
- await vi.advanceTimersByTimeAsync(KEEP_ALIVE_NO_PROGRESS_TIMEOUT_MS + 60_000);
182
- expect(child.killSignal).toBe("SIGTERM"); // 仅 final kill 一次,无二次信号
183
- });
184
-
185
- // ── [A1-1] 挂载面收窄:只有裸缺省(maxTurns 未传且 env 未设)挂无进展 timer ──
186
-
187
- it("A1-1: 显式 maxTurns:0(显式不限时)→ 不挂无进展 timer,静默超阈值不处置", async () => {
188
- const { child, finish } = await spawnAndReachKeepAlive(makeOpts({ maxTurns: 0 }));
189
- expect(child.killed).toBe(false);
190
-
191
- // 无进展阈值(30min)处静默不 kill:timer 未挂(旧实现 keepAliveMs === undefined
192
- // 即挂 timer,30min 处会误 kill——本断言守护挂载面收窄)
193
- await vi.advanceTimersByTimeAsync(KEEP_ALIVE_NO_PROGRESS_TIMEOUT_MS);
194
- expect(child.killed).toBe(false);
195
-
196
- // 显式 opt-out 旧语义 = 等待后代不限时:远超阈值仍无任何 timer 处置
197
- await vi.advanceTimersByTimeAsync(KEEP_ALIVE_NO_PROGRESS_TIMEOUT_MS * 3);
198
- expect(child.killed).toBe(false);
199
-
200
- await finish();
201
- });
202
-
203
- it("A1-1: env 已设 + maxTurns:0(参数显式压过 env,U5)→ 同样不挂无进展 timer", async () => {
204
- vi.stubEnv(SPAWN_WATCHDOG_ENV, "600000"); // 10min
205
- const { child, finish } = await spawnAndReachKeepAlive(makeOpts({ maxTurns: 0 }));
206
-
207
- // 同时越过 env 时限(10min)与无进展阈值(30min)仍不 kill:显式 maxTurns<=0
208
- // 压过 env(U5),env 固定 watchdog 与无进展 timer 均未挂
209
- await vi.advanceTimersByTimeAsync(Math.max(KEEP_ALIVE_NO_PROGRESS_TIMEOUT_MS, 600_000) + 60_000);
210
- expect(child.killed).toBe(false);
211
-
212
- await finish();
213
- });
214
-
215
- // ── [A1-2] fire 惰性复核:stdout 静默 ≠ 无进展,存活后代是刷新信号源的另一半 ──
216
-
217
- it("A1-2①: fire 复核有存活活跃后代 → 视为有进展重挂(30min 固定复核节奏),不处置层主", async () => {
218
- // 后代 pending 差集非空 + pid 存活(与 descendant sweep 同源判据)
219
- mockDescendant("ka-desc-sess", LIVE_DESCENDANT_PID, true);
220
-
221
- const { child, finish } = await spawnAndReachKeepAlive();
222
- expect(child.killed).toBe(false);
223
-
224
- // [A1-2 补修] 清掉 agent_end 处置写点的心跳基线:只统计 fire 重挂分支随行的心跳
225
- mockWriteAliveMarker.mockClear();
226
-
227
- // 第 1 次连续静默 30min fire:复核发现存活后代 → 重挂不处置
228
- //(「直接后代跑 >30min、层主静默」的合法形态,P-T2 数据 85/89 parent-shutdown)
229
- await vi.advanceTimersByTimeAsync(KEEP_ALIVE_NO_PROGRESS_TIMEOUT_MS);
230
- expect(child.killed).toBe(false);
231
-
232
- // 第 2 个 30min 再 fire(固定复核节奏直到后代死光):后代仍活 → 仍不处置
233
- await vi.advanceTimersByTimeAsync(KEEP_ALIVE_NO_PROGRESS_TIMEOUT_MS);
234
- expect(child.killed).toBe(false);
235
-
236
- // [A1-2 补修] 每次复核重挂随行一次心跳:合法化形态「层主静默 + 后代长跑数小时」
237
- // 期间层主无 agent_end(原心跳写点不再触达),重挂分支是唯一心跳源——缺失则
238
- // marker 超 1h 软超时判陈旧 → 透明重生放行双写 / 孤儿恢复误终态活 record
239
- expect(mockWriteAliveMarker).toHaveBeenCalledTimes(2);
240
- // 心跳写点 = 层主 sessionFile + 子进程 pid(marker 软超时基准刷新语义)
241
- expect(mockWriteAliveMarker).toHaveBeenCalledWith(
242
- expect.any(String),
243
- expect.objectContaining({ pid: child.pid }),
244
- );
245
-
246
- await finish();
247
- });
248
-
249
- it("A1-2②: fire 复核后代差集非空但 pid 全部已死 → 真静默,执行处置", async () => {
250
- mockDescendant("ka-dead-sess", DEAD_DESCENDANT_PID, false); // 差集非空但 pid 全部已死
251
-
252
- const { child, finish } = await spawnAndReachKeepAlive();
253
-
254
- // fire:差集非空但无任何存活 pid → 真静默 → 处置(SIGTERM + sweep 标志承接)
255
- await vi.advanceTimersByTimeAsync(KEEP_ALIVE_NO_PROGRESS_TIMEOUT_MS);
256
- expect(child.killed).toBe(true);
257
- expect(child.killSignal).toBe("SIGTERM");
258
-
259
- await finish();
260
- });
261
-
262
- it("A1-2③: fire 复核失败(sessionFile 读不出)→ 按无后代处置 + warn 留痕", async () => {
263
- mockListPending.mockReturnValue({
264
- items: [],
265
- error: "session file unreadable: ENOENT",
266
- });
267
-
268
- const { child, finish } = await spawnAndReachKeepAlive();
269
-
270
- await vi.advanceTimersByTimeAsync(KEEP_ALIVE_NO_PROGRESS_TIMEOUT_MS);
271
- expect(child.killed).toBe(true);
272
- expect(child.killSignal).toBe("SIGTERM");
273
- // 复核失败 warn 留痕(与既有保守分支一致的可观测性)
274
- expect(loggerMock.warn).toHaveBeenCalledWith(
275
- expect.stringContaining("re-check failed (treating as no live descendants)"),
276
- );
277
-
278
- await finish();
279
- });
280
- });
@@ -1,196 +0,0 @@
1
- // src/execution/__tests__/kill-all-escalation.test.ts
2
- //
3
- // [T2-⑤ / LC-2] killAllSpawnedChildren 的跳过条件收敛:「killed 且已确认死亡才跳过」。
4
- //
5
- // 设计:docs/design/subagent-core-unbounded-wait-audit.md §7.2 T2-⑤。旧实现按
6
- // child.killed 跳过——killed=true 只表示「发过 kill 请求」≠「已死」,SIGTERM 被无视
7
- // 的进程(卡死在不可中断 native 调用)会脱离最后一次回收窗口泄漏。新规则:
8
- // - 已确认死亡(exitCode/signalCode 任一非 null)→ 跳过;
9
- // - killed 但未确认死亡 → 补 escalation 检查:直接 SIGKILL(dispose 是最后兜底,
10
- // 没有 30s 升级窗口可等);
11
- // - 未 killed 且未确认死亡 → 发调用方指定 signal(既有行为)。
12
-
13
- import { spawn } from "node:child_process";
14
- import * as fs from "node:fs";
15
-
16
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
17
-
18
- vi.mock("node:child_process", async () => {
19
- const { FakeChild } = await import("./helpers/spawn-mock.ts");
20
- return {
21
- spawn: vi.fn(() => new FakeChild()),
22
- execFile: vi.fn(
23
- (
24
- _cmd: string,
25
- _args: readonly string[],
26
- _opts: unknown,
27
- cb: (err: Error | null, stdout?: string, stderr?: string) => void,
28
- ) => cb(new Error("execFile not configured in this test")),
29
- ),
30
- };
31
- });
32
-
33
- vi.mock("node:fs", async () => {
34
- const actual = await import("node:fs");
35
- return {
36
- default: {
37
- ...actual,
38
- mkdirSync: vi.fn(),
39
- existsSync: vi.fn(() => false),
40
- appendFileSync: vi.fn(),
41
- writeFileSync: vi.fn(),
42
- readdirSync: vi.fn(() => []),
43
- },
44
- mkdirSync: vi.fn(),
45
- existsSync: vi.fn(() => false),
46
- appendFileSync: vi.fn(),
47
- writeFileSync: vi.fn(),
48
- readdirSync: vi.fn(() => []),
49
- promises: actual.promises,
50
- };
51
- });
52
-
53
- vi.mock("../alive-store.ts", () => ({ writeAliveMarker: vi.fn() }));
54
- vi.mock("../session-pending.ts", () => ({
55
- readActivePendingFromSessionFile: vi.fn(() => ({ count: 0, recentUnregister: false })),
56
- prunePendingCursor: vi.fn(),
57
- listActivePendingFromSessionFile: vi.fn(() => ({ items: [] })),
58
- }));
59
- vi.mock("../engine/engines/pi/temp-prompt.ts", () => ({
60
- writePromptToTempFile: vi.fn(async (agent: string) => {
61
- const safeName = agent.replace(/[^\w.-]+/g, "_");
62
- return { dir: `/tmp/fake-${safeName}`, filePath: `/tmp/fake-${safeName}/prompt-${safeName}.md` };
63
- }),
64
- cleanupTempPrompt: vi.fn(async () => {}),
65
- }));
66
-
67
- import { killAllSpawnedChildren, runSpawn, spawnedChildren } from "../engine/engines/pi/session-runner.ts";
68
- import {
69
- emitStdoutLine,
70
- type FakeChild,
71
- lastSpawnedChild as lastSpawnedChildOf,
72
- makeCtx,
73
- makeOpts,
74
- makeRecord,
75
- waitForSpawn as waitForSpawnOf,
76
- } from "./helpers/spawn-mock.ts";
77
-
78
- const mockSpawn = vi.mocked(spawn);
79
- const lastSpawnedChild = (): FakeChild => lastSpawnedChildOf(mockSpawn);
80
- const waitForSpawn = (timeoutMs = 1000): Promise<void> => waitForSpawnOf(mockSpawn, timeoutMs);
81
-
82
- describe("[T2-⑤ / LC-2] killAllSpawnedChildren 跳过条件收敛", () => {
83
- beforeEach(() => {
84
- vi.clearAllMocks();
85
- killAllSpawnedChildren(); // 清可能残留的 Map 条目(前序用例/文件)
86
- spawnedChildren.clear();
87
- });
88
-
89
- afterEach(() => {
90
- vi.restoreAllMocks();
91
- });
92
-
93
- it("killed 但未 close(SIGTERM 发过、进程未确认死亡)→ 不跳过,补 SIGKILL 升级", async () => {
94
- const record = makeRecord("esc-1");
95
- const promise = runSpawn(record, "Task: esc", makeOpts(), makeCtx());
96
- await waitForSpawn();
97
- const child = lastSpawnedChild();
98
-
99
- // 模拟 SIGTERM 已发但进程仍在(LC-2 缺陷形态:killed=发过请求≠已死)
100
- child.killed = true;
101
- child.killSignal = "SIGTERM";
102
- expect(child.exitCode).toBeNull();
103
- expect(child.signalCode).toBeNull();
104
-
105
- const n = killAllSpawnedChildren();
106
- expect(n).toBe(1);
107
- // 补 escalation:dispose 是最后兜底,直接 SIGKILL(不再赌 SIGTERM 生效)
108
- expect(child.killSignal).toBe("SIGKILL");
109
-
110
- // 收尾:emit close 让 runSpawn resolve
111
- emitStdoutLine(child, { type: "session", id: "s", timestamp: "t", cwd: "/tmp" });
112
- child.stdout.end();
113
- child.emit("close", 137);
114
- const result = await promise;
115
- expect(result.success).toBe(true);
116
- });
117
-
118
- it("killed 且已确认死亡(signalCode 非 null)→ 跳过", async () => {
119
- const record = makeRecord("esc-2");
120
- const promise = runSpawn(record, "Task: dead-skip", makeOpts(), makeCtx());
121
- await waitForSpawn();
122
- const child = lastSpawnedChild();
123
-
124
- // SIGTERM 已发(经 child.kill 记录信号)且进程已确认死亡(内核回收完成、
125
- // close 事件尚未回调的窗口)
126
- child.kill("SIGTERM");
127
- child.signalCode = "SIGTERM";
128
-
129
- const n = killAllSpawnedChildren();
130
- expect(n).toBe(0); // 跳过,不重复发信号
131
- expect(child.killSignal).toBe("SIGTERM"); // 保持原信号(未被改写为 SIGKILL)
132
-
133
- child.stdout.end();
134
- child.emit("close", 143);
135
- await promise;
136
- });
137
-
138
- it("未 killed 且未确认死亡 → 发调用方 signal(既有行为不变)", async () => {
139
- const record = makeRecord("esc-3");
140
- const promise = runSpawn(record, "Task: alive", makeOpts(), makeCtx());
141
- await waitForSpawn();
142
- const child = lastSpawnedChild();
143
- expect(child.killed).toBe(false);
144
-
145
- const n = killAllSpawnedChildren("SIGTERM");
146
- expect(n).toBe(1);
147
- expect(child.killed).toBe(true);
148
- expect(child.killSignal).toBe("SIGTERM");
149
-
150
- child.stdout.end();
151
- child.emit("close", 143);
152
- await promise;
153
- });
154
-
155
- it("未 killed 但已确认死亡(自然退出、close 未到)→ 跳过不补信号", async () => {
156
- const record = makeRecord("esc-4");
157
- const promise = runSpawn(record, "Task: natural", makeOpts(), makeCtx());
158
- await waitForSpawn();
159
- const child = lastSpawnedChild();
160
- child.exitCode = 0; // 自然退出
161
-
162
- const n = killAllSpawnedChildren();
163
- expect(n).toBe(0);
164
- expect(child.killed).toBe(false);
165
-
166
- child.stdout.end();
167
- child.emit("close", 0);
168
- await promise;
169
- });
170
-
171
- it("混合形态:各分支独立判定(killed-not-dead 升级 / alive 首杀 / dead 跳过)", async () => {
172
- const mk = async (id: string): Promise<{ child: FakeChild; promise: Promise<unknown> }> => {
173
- const promise = runSpawn(makeRecord(id), `Task: ${id}`, makeOpts(), makeCtx());
174
- await waitForSpawn();
175
- return { child: lastSpawnedChild(), promise };
176
- };
177
-
178
- const a = await mk("mix-a");
179
- a.child.killed = true; // killed-not-dead → SIGKILL
180
- const b = await mk("mix-b"); // alive → SIGTERM
181
- const c = await mk("mix-c");
182
- c.child.signalCode = "SIGKILL"; // dead → skip
183
-
184
- const n = killAllSpawnedChildren();
185
- expect(n).toBe(2);
186
- expect(a.child.killSignal).toBe("SIGKILL");
187
- expect(b.child.killSignal).toBe("SIGTERM");
188
- expect(c.child.killSignal).toBeUndefined();
189
-
190
- for (const { child, promise } of [a, b, c]) {
191
- child.stdout.end();
192
- child.emit("close", 143);
193
- await promise;
194
- }
195
- });
196
- });