@zhushanwen/subagent-core 0.6.0 → 0.7.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 (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,227 +0,0 @@
1
- // src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts
2
- //
3
- // [u-svc / T5② + T4②] session-runner 侧两措施(真实模块):
4
- // - T5②/PS-7a:keep-alive 期 agent_end 心跳——覆盖写 .alive marker 刷新软超时基准
5
- // (P-T5 探针裁决写盘开销可忽略,主路径落地);
6
- // - T4②/PS-4:agent_settled 的 armIdleTimer fail-fast 降级语义修正——「挂
7
- // DEFAULT_IDLE_TIMEOUT_MS + warn」而非旧「不挂」(保住通知放行门 + 进程回收两个
8
- // 不变量,兜底可见)。
9
- //
10
- // mock 结构与 keep-alive-no-progress.test.ts / run-spawn-chatmode-settled.test.ts 一致
11
- //(FakeChild + 受控 session-pending;lifecycle-manager partial mock:armIdleTimer 可注入
12
- // fail-fast,其余导出(hasIdleTimer/_resetLifecycleState/DEFAULT)为真实实现)。
13
-
14
- import { spawn } from "node:child_process";
15
- import * as fs from "node:fs";
16
-
17
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
18
-
19
- const { loggerMock } = vi.hoisted(() => ({
20
- loggerMock: { debug: vi.fn(), warn: vi.fn(), error: vi.fn() },
21
- }));
22
- vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
23
-
24
- const { writeAliveMarkerMock, readAliveMarkerMock, armIdleTimerMock } = vi.hoisted(() => ({
25
- writeAliveMarkerMock: vi.fn(),
26
- readAliveMarkerMock: vi.fn<() => undefined>(() => undefined),
27
- armIdleTimerMock: vi.fn(),
28
- }));
29
-
30
- vi.mock("node:child_process", async () => {
31
- const { FakeChild } = await import("./helpers/spawn-mock.ts");
32
- return {
33
- spawn: vi.fn(() => new FakeChild()),
34
- execFile: vi.fn(
35
- (
36
- _cmd: string,
37
- _args: readonly string[],
38
- _opts: unknown,
39
- cb: (err: Error | null, stdout?: string, stderr?: string) => void,
40
- ) => cb(new Error("execFile not configured in this test")),
41
- ),
42
- };
43
- });
44
-
45
- vi.mock("node:fs", async () => {
46
- const actual = await import("node:fs");
47
- return {
48
- default: {
49
- ...actual,
50
- mkdirSync: vi.fn(),
51
- existsSync: vi.fn(() => false),
52
- appendFileSync: vi.fn(),
53
- writeFileSync: vi.fn(),
54
- readdirSync: vi.fn(() => []),
55
- },
56
- mkdirSync: vi.fn(),
57
- existsSync: vi.fn(() => false),
58
- appendFileSync: vi.fn(),
59
- writeFileSync: vi.fn(),
60
- readdirSync: vi.fn(() => []),
61
- promises: actual.promises,
62
- };
63
- });
64
-
65
- vi.mock("../alive-store.ts", () => ({
66
- writeAliveMarker: writeAliveMarkerMock,
67
- readAliveMarker: readAliveMarkerMock,
68
- isProcessAlive: vi.fn(() => false),
69
- ALIVE_SOFT_TIMEOUT_MS: 3_600_000,
70
- }));
71
-
72
- // keep-alive 心跳用例:count=1(有活跃后代 → keep-alive 分支)。
73
- vi.mock("../session-pending.ts", () => ({
74
- readActivePendingFromSessionFile: vi.fn(() => ({ count: 1, recentUnregister: false })),
75
- prunePendingCursor: vi.fn(),
76
- listActivePendingFromSessionFile: vi.fn(() => ({ items: [] })),
77
- }));
78
-
79
- vi.mock("../engine/engines/pi/temp-prompt.ts", () => ({
80
- writePromptToTempFile: vi.fn(async (agent: string) => {
81
- const safeName = agent.replace(/[^\w.-]+/g, "_");
82
- return { dir: `/tmp/fake-${safeName}`, filePath: `/tmp/fake-${safeName}/prompt-${safeName}.md` };
83
- }),
84
- cleanupTempPrompt: vi.fn(async () => {}),
85
- }));
86
-
87
- vi.mock("../lifecycle-manager.ts", async (importOriginal) => {
88
- const actual = await importOriginal<typeof import("../lifecycle-manager.ts")>();
89
- return { ...actual, armIdleTimer: armIdleTimerMock };
90
- });
91
-
92
- import { DEFAULT_IDLE_TIMEOUT_MS, hasIdleTimer, _resetLifecycleState } from "../lifecycle-manager.ts";
93
- import { runSpawn } from "../engine/engines/pi/session-runner.ts";
94
- import {
95
- emitStdoutLine,
96
- type FakeChild,
97
- lastSpawnedChild as lastSpawnedChildOf,
98
- makeCtx,
99
- makeOpts,
100
- makeRecord,
101
- waitForSpawn as waitForSpawnOf,
102
- } from "./helpers/spawn-mock.ts";
103
-
104
- const mockSpawn = vi.mocked(spawn);
105
-
106
- const lastSpawnedChild = (): FakeChild => lastSpawnedChildOf(mockSpawn);
107
- const waitForSpawn = (timeoutMs = 1000): Promise<void> => waitForSpawnOf(mockSpawn, timeoutMs);
108
-
109
- const flushPump = async (): Promise<void> => {
110
- await new Promise((r) => setTimeout(r, 20));
111
- };
112
-
113
- describe("T5② alive marker heartbeat on keep-alive agent_end", () => {
114
- beforeEach(() => {
115
- vi.clearAllMocks();
116
- writeAliveMarkerMock.mockClear();
117
- readAliveMarkerMock.mockReturnValue(undefined);
118
- _resetLifecycleState();
119
- });
120
-
121
- afterEach(() => {
122
- vi.restoreAllMocks();
123
- vi.useRealTimers();
124
- _resetLifecycleState();
125
- });
126
-
127
- it("rewrites the alive marker each keep-alive agent_end (soft-timeout base refreshed)", async () => {
128
- const record = makeRecord("sa-heartbeat");
129
- // 预设 sessionFile(跳过 lazy get_state 回补;不 emit header——RPC mode 无 header,
130
- // emit 会经 deriveSessionFilePath 覆盖预设路径)
131
- record.sessionFile = "/tmp/fake-heartbeat-session.jsonl";
132
- const promise = runSpawn(record, "Task: heartbeat", makeOpts(), makeCtx());
133
- await waitForSpawn();
134
- const child = lastSpawnedChild();
135
-
136
- // 第一次 keep-alive agent_end → 心跳(保 pid/id、刷新 startedAt)
137
- emitStdoutLine(child, { type: "agent_end", willRetry: false });
138
- await flushPump();
139
- expect(writeAliveMarkerMock).toHaveBeenCalledTimes(1);
140
- expect(writeAliveMarkerMock).toHaveBeenCalledWith(
141
- "/tmp/fake-heartbeat-session.jsonl",
142
- expect.objectContaining({ pid: child.pid, id: record.id, startedAt: expect.any(Number) }),
143
- );
144
-
145
- // 第二次 agent_end(后代仍在)→ 再次心跳(软超时基准持续推新)
146
- emitStdoutLine(child, { type: "agent_end", willRetry: false });
147
- await flushPump();
148
- expect(writeAliveMarkerMock).toHaveBeenCalledTimes(2);
149
-
150
- child.stdout.end();
151
- child.stderr.end();
152
- child.emit("close", 143);
153
- await promise;
154
- });
155
-
156
- it("keeps the existing marker id when refreshing (readAliveMarker hit)", async () => {
157
- readAliveMarkerMock.mockReturnValue({ pid: 1, id: "sess-orig-id", startedAt: 1 } as never);
158
- const record = makeRecord("sa-heartbeat-id");
159
- record.sessionFile = "/tmp/fake-heartbeat-session-2.jsonl";
160
- const promise = runSpawn(record, "Task: heartbeat id", makeOpts(), makeCtx());
161
- await waitForSpawn();
162
- const child = lastSpawnedChild();
163
-
164
- emitStdoutLine(child, { type: "agent_end", willRetry: false });
165
- await flushPump();
166
- expect(writeAliveMarkerMock).toHaveBeenLastCalledWith(
167
- "/tmp/fake-heartbeat-session-2.jsonl",
168
- expect.objectContaining({ id: "sess-orig-id" }),
169
- );
170
-
171
- child.stdout.end();
172
- child.stderr.end();
173
- child.emit("close", 143);
174
- await promise;
175
- });
176
- });
177
-
178
- describe("T4② armIdleTimer fail-fast falls back to DEFAULT + warn", () => {
179
- beforeEach(() => {
180
- vi.clearAllMocks();
181
- _resetLifecycleState();
182
- });
183
-
184
- afterEach(() => {
185
- vi.restoreAllMocks();
186
- vi.useRealTimers();
187
- _resetLifecycleState();
188
- });
189
-
190
- it("agent_settled with failing armIdleTimer still arms the timer at DEFAULT duration with a warning", async () => {
191
- // 首次调用(record.idleTimeoutMs 通道)注入 fail-fast,后续转发真实实现
192
- const actual = await vi.importActual<typeof import("../lifecycle-manager.ts")>("../lifecycle-manager.ts");
193
- armIdleTimerMock.mockImplementationOnce(() => {
194
- throw new Error("idleTimeoutMs = 3000000000 exceeds the Node setTimeout limit");
195
- });
196
- armIdleTimerMock.mockImplementation(
197
- (recordId: string, onTimeout: () => void, timeoutMs?: number) =>
198
- actual.armIdleTimer(recordId, onTimeout, timeoutMs),
199
- );
200
-
201
- const record = makeRecord("sa-settled-fallback");
202
- const mutable = record as { chatMode: boolean; idleTimeoutMs: number };
203
- mutable.chatMode = true;
204
- mutable.idleTimeoutMs = 600_000;
205
- const promise = runSpawn(record, "Task: settled fallback", makeOpts(), makeCtx());
206
- await waitForSpawn();
207
- const child = lastSpawnedChild();
208
-
209
- emitStdoutLine(child, { type: "agent_settled" });
210
- await flushPump();
211
-
212
- // 两次 arm:第一次(非法/失败通道)+ 降级(DEFAULT)
213
- expect(armIdleTimerMock).toHaveBeenCalledTimes(2);
214
- expect(armIdleTimerMock).toHaveBeenLastCalledWith(record.id, expect.any(Function), DEFAULT_IDLE_TIMEOUT_MS);
215
- // 不变量保住:timer 确实 armed(通知放行门 + 进程回收有效)
216
- expect(hasIdleTimer(record.id)).toBe(true);
217
- // 降级可见
218
- expect(loggerMock.warn).toHaveBeenCalledWith(
219
- expect.stringContaining("fell back to DEFAULT_IDLE_TIMEOUT_MS"),
220
- );
221
-
222
- child.stdout.end();
223
- child.stderr.end();
224
- child.emit("close", 143);
225
- await promise;
226
- });
227
- });
@@ -1,290 +0,0 @@
1
- // src/execution/__tests__/session-runner-lifecycle-helpers.test.ts
2
- //
3
- // [S-17b / runSpawn 阶段拆分] 重构提取辅助函数的可观察行为锁定(生命周期/kill 决策域)。
4
- //
5
- // 背景:session-runner.ts 按执行阶段拆分后,以下新提取辅助函数残留分支无断言锁定:
6
- // - hasLiveActiveDescendant(keep-alive no-progress fire 惰性复核)的两个 continue
7
- // 盲区分支:pending 项无 sessionId / sessionId 反查不到 session 文件——两者均
8
- // 「不计入存活」→ 真静默处置(与 A1-2② 同向,但走的是不同 continue 边);
9
- // - sweepDescendantsOnChildClose 的整体失败 catch:sweep 抛错不掩盖层主自身的
10
- // 收尾结果(best-effort 可见 + runSpawn 照常 resolve);
11
- // - registerSpawnedChildForRecord 的 close/error 双通道按句移除(M4 竞态守卫);
12
- // - killRecordChildWithEscalation 同 record 换新 child(resume spawn 覆盖注册)时
13
- // 先清旧升级 timer 防叠加——与 service-kill-escalation.test.ts 的「同 child
14
- // 重复调用 no-op」互补,覆盖的是 1396-1397 的 clearTimeout 真路径。
15
- //
16
- // mock 布局与 keep-alive-no-progress.test.ts 一致(FakeChild + fs/alive-store/
17
- // session-pending/temp-prompt mock + 共享 logger mock)。
18
-
19
- import { spawn } from "node:child_process";
20
- import type { ChildProcess } from "node:child_process";
21
-
22
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
23
-
24
- const { loggerMock } = vi.hoisted(() => ({
25
- loggerMock: { debug: vi.fn(), warn: vi.fn(), error: vi.fn() },
26
- }));
27
- vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
28
-
29
- vi.mock("node:child_process", async () => {
30
- const { FakeChild } = await import("./helpers/spawn-mock.ts");
31
- return {
32
- spawn: vi.fn(() => new FakeChild()),
33
- execFile: vi.fn(
34
- (
35
- _cmd: string,
36
- _args: readonly string[],
37
- _opts: unknown,
38
- cb: (err: Error | null, stdout?: string, stderr?: string) => void,
39
- ) => cb(new Error("execFile not configured in this test")),
40
- ),
41
- };
42
- });
43
-
44
- vi.mock("node:fs", async () => {
45
- const actual = await import("node:fs");
46
- return {
47
- default: {
48
- ...actual,
49
- mkdirSync: vi.fn(),
50
- existsSync: vi.fn(() => false),
51
- appendFileSync: vi.fn(),
52
- writeFileSync: vi.fn(),
53
- readdirSync: vi.fn(() => []),
54
- },
55
- mkdirSync: vi.fn(),
56
- existsSync: vi.fn(() => false),
57
- appendFileSync: vi.fn(),
58
- writeFileSync: vi.fn(),
59
- readdirSync: vi.fn(() => []),
60
- promises: actual.promises,
61
- };
62
- });
63
-
64
- vi.mock("../alive-store.ts", () => ({
65
- writeAliveMarker: vi.fn(),
66
- readAliveMarker: vi.fn(() => undefined),
67
- isProcessAlive: vi.fn(() => false),
68
- }));
69
-
70
- // keep-alive 判定:本文件统一 count>0(有活跃后代 → keep-alive 分支)。
71
- vi.mock("../session-pending.ts", () => ({
72
- readActivePendingFromSessionFile: vi.fn(() => ({ count: 1, recentUnregister: false })),
73
- prunePendingCursor: vi.fn(),
74
- listActivePendingFromSessionFile: vi.fn(() => ({ items: [] })),
75
- }));
76
-
77
- vi.mock("../engine/engines/pi/temp-prompt.ts", () => ({
78
- writePromptToTempFile: vi.fn(async (agent: string) => {
79
- const safeName = agent.replace(/[^\w.-]+/g, "_");
80
- return { dir: `/tmp/fake-${safeName}`, filePath: `/tmp/fake-${safeName}/prompt-${safeName}.md` };
81
- }),
82
- cleanupTempPrompt: vi.fn(async () => {}),
83
- }));
84
-
85
- import {
86
- KEEP_ALIVE_NO_PROGRESS_TIMEOUT_MS,
87
- killRecordChildWithEscalation,
88
- registerSpawnedChildForRecord,
89
- runSpawn,
90
- SPAWN_WATCHDOG_ENV,
91
- spawnedChildren,
92
- _resetServiceKillStateForTest,
93
- getChildByRecord,
94
- } from "../engine/engines/pi/session-runner.ts";
95
- import { listActivePendingFromSessionFile, readActivePendingFromSessionFile } from "../session-pending.ts";
96
- import { _resetLifecycleState } from "../lifecycle-manager.ts";
97
- import {
98
- emitStdoutLine,
99
- FakeChild,
100
- lastSpawnedChild as lastSpawnedChildOf,
101
- makeCtx,
102
- makeOpts,
103
- makeRecord,
104
- waitForSpawn as waitForSpawnOf,
105
- } from "./helpers/spawn-mock.ts";
106
-
107
- const mockSpawn = vi.mocked(spawn);
108
- const mockPending = vi.mocked(readActivePendingFromSessionFile);
109
- const mockListPending = vi.mocked(listActivePendingFromSessionFile);
110
- const lastSpawnedChild = (): FakeChild => lastSpawnedChildOf(mockSpawn);
111
- const waitForSpawn = (timeoutMs = 1000): Promise<void> => waitForSpawnOf(mockSpawn, timeoutMs);
112
-
113
- /** keep-alive 30min 无进展阈值(与源码常量一致,测试可读性用)。 */
114
- const SIGKILL_ESCALATION_MS = 30_000;
115
-
116
- /** FakeChild → 注册 API 的 ChildProcess 形状收窄(结构缺私有字段;记账/kill 只消费 once/kill 形状)。 */
117
- const asChildProcess = (c: FakeChild): ChildProcess => c as unknown as ChildProcess;
118
-
119
- function sessionHeaderLine(): Record<string, unknown> {
120
- return { type: "session", id: "sess-ka", timestamp: "2026-09-01T00:00:00.000Z", cwd: "/tmp/test" };
121
- }
122
-
123
- /** 非 chatMode 层主 + fake timers + agent_end keep-alive 落位的公共前奏。 */
124
- async function spawnAndReachKeepAlive(): Promise<{
125
- child: FakeChild;
126
- finish: (code?: number) => Promise<Awaited<ReturnType<typeof runSpawn>>>;
127
- }> {
128
- const record = makeRecord();
129
- const promise = runSpawn(record, "Task: keep-alive re-check", makeOpts(), makeCtx());
130
- await waitForSpawn();
131
- const child = lastSpawnedChild();
132
- // fake timers 必须在 emit agent_end 之前启用(keep-alive timer 新建于 agent_end 处理器内)
133
- vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout", "Date"] });
134
- emitStdoutLine(child, sessionHeaderLine());
135
- emitStdoutLine(child, { type: "agent_end", messages: [], willRetry: false });
136
- await new Promise((r) => setImmediate(r));
137
- return {
138
- child,
139
- finish: (code = 143) => {
140
- child.stdout.end();
141
- child.stderr.end();
142
- child.emit("close", code);
143
- return promise as Promise<Awaited<ReturnType<typeof runSpawn>>>;
144
- },
145
- };
146
- }
147
-
148
- describe("[S-17b] hasLiveActiveDescendant:fire 复核的 T5 盲区 continue 边", () => {
149
- beforeEach(() => {
150
- vi.clearAllMocks();
151
- vi.stubEnv(SPAWN_WATCHDOG_ENV, "");
152
- mockPending.mockReturnValue({ count: 1, recentUnregister: false });
153
- });
154
-
155
- afterEach(() => {
156
- vi.restoreAllMocks();
157
- vi.unstubAllEnvs();
158
- vi.useRealTimers();
159
- });
160
-
161
- it("复核发现 pending 项缺 sessionId(marker 反查无门)→ 不计入存活,真静默处置", async () => {
162
- mockListPending.mockReturnValue({
163
- items: [{ id: "bg-nosess", sessionId: undefined, type: "session" }],
164
- });
165
-
166
- const { child, finish } = await spawnAndReachKeepAlive();
167
- expect(child.killed).toBe(false);
168
-
169
- // 连续静默 30min fire:盲区后代不豁免层主(归 T5 marker 机制兜底)
170
- await vi.advanceTimersByTimeAsync(KEEP_ALIVE_NO_PROGRESS_TIMEOUT_MS);
171
- expect(child.killed).toBe(true);
172
- expect(child.killSignal).toBe("SIGTERM");
173
-
174
- await finish();
175
- });
176
-
177
- it("复核发现 sessionId 反查不到 session 文件(未 flush / 非本树)→ 不计入存活,真静默处置", async () => {
178
- mockListPending.mockReturnValue({
179
- items: [{ id: "bg-lost", sessionId: "ka-missing-sess", type: "session" }],
180
- });
181
- // readdirSync 默认 mock 返回 [] → findSessionFileByHeaderId 反查失败
182
-
183
- const { child, finish } = await spawnAndReachKeepAlive();
184
- expect(child.killed).toBe(false);
185
-
186
- await vi.advanceTimersByTimeAsync(KEEP_ALIVE_NO_PROGRESS_TIMEOUT_MS);
187
- expect(child.killed).toBe(true);
188
- expect(child.killSignal).toBe("SIGTERM");
189
-
190
- await finish();
191
- });
192
- });
193
-
194
- describe("[S-17b] sweepDescendantsOnChildClose:sweep 整体失败不掩盖层主收尾", () => {
195
- beforeEach(() => {
196
- vi.clearAllMocks();
197
- vi.stubEnv(SPAWN_WATCHDOG_ENV, "");
198
- mockPending.mockReturnValue({ count: 1, recentUnregister: false });
199
- });
200
-
201
- afterEach(() => {
202
- vi.restoreAllMocks();
203
- vi.unstubAllEnvs();
204
- vi.useRealTimers();
205
- });
206
-
207
- it("no-progress 处置后 close 收尾时 sweep 抛错 → best-effort error 留痕,runSpawn 照常 resolve 成功", async () => {
208
- // 第一次 list 调用(fire 惰性复核):无存活后代 → 处置
209
- // 第二次 list 调用(close 后 sweep 采集):整体抛错 → sweepDescendantsOnChildClose catch
210
- mockListPending
211
- .mockReturnValueOnce({ items: [] })
212
- .mockImplementation(() => {
213
- throw new Error("pending scan exploded");
214
- });
215
-
216
- const { child, finish } = await spawnAndReachKeepAlive();
217
- await vi.advanceTimersByTimeAsync(KEEP_ALIVE_NO_PROGRESS_TIMEOUT_MS);
218
- expect(child.killed).toBe(true);
219
-
220
- const result = await finish();
221
- // 层主自身收尾结果不被 sweep 失败掩盖(信号终止视为正常完成,既有语义)
222
- expect(result.success).toBe(true);
223
- expect(loggerMock.error).toHaveBeenCalledWith(
224
- expect.stringContaining("descendant sweep (keep-alive watchdog) failed"),
225
- expect.objectContaining({ detail: "pending scan exploded" }),
226
- );
227
- });
228
- });
229
-
230
- describe("[S-17b] registerSpawnedChildForRecord:close/error 双通道按句移除", () => {
231
- beforeEach(() => {
232
- vi.clearAllMocks();
233
- spawnedChildren.clear();
234
- });
235
-
236
- afterEach(() => {
237
- spawnedChildren.clear();
238
- });
239
-
240
- it("注册后 child 异步 'error'(spawn 失败)→ 移出 spawnedChildren(dispose 兜底不再 kill 它)", () => {
241
- const child = new FakeChild();
242
- registerSpawnedChildForRecord("rec-reg-error", asChildProcess(child));
243
- expect(getChildByRecord("rec-reg-error")).toBe(child);
244
-
245
- child.emit("error", new Error("spawn ENOENT"));
246
- expect(getChildByRecord("rec-reg-error")).toBeUndefined();
247
- });
248
-
249
- it("注册后 child 'close'(正常退出)→ 同样移出(close/error 两监听同构)", () => {
250
- const child = new FakeChild();
251
- registerSpawnedChildForRecord("rec-reg-close", asChildProcess(child));
252
- expect(getChildByRecord("rec-reg-close")).toBe(child);
253
-
254
- child.emit("close", 0);
255
- expect(getChildByRecord("rec-reg-close")).toBeUndefined();
256
- });
257
- });
258
-
259
- describe("[S-17b] killRecordChildWithEscalation:同 record 换新 child 的升级 timer 防叠加", () => {
260
- beforeEach(() => {
261
- vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] });
262
- _resetServiceKillStateForTest();
263
- spawnedChildren.clear();
264
- });
265
-
266
- afterEach(() => {
267
- vi.useRealTimers();
268
- _resetServiceKillStateForTest();
269
- spawnedChildren.clear();
270
- });
271
-
272
- it("resume spawn 覆盖注册后重复 kill → 旧 child 的升级 timer 被清,30s 后仅新 child 收到 SIGKILL", async () => {
273
- const oldChild = new FakeChild();
274
- spawnedChildren.set("rec-resume-kill", asChildProcess(oldChild));
275
- killRecordChildWithEscalation("rec-resume-kill", "closeChatIdle#1");
276
- expect(oldChild.killSignal).toBe("SIGTERM");
277
-
278
- // resume:同 record 换新 child(Map 条目被覆盖,旧 child 句柄仍在旧 timer 闭包里)
279
- const newChild = new FakeChild();
280
- spawnedChildren.set("rec-resume-kill", asChildProcess(newChild));
281
- killRecordChildWithEscalation("rec-resume-kill", "closeChatIdle#2");
282
- expect(newChild.killSignal).toBe("SIGTERM");
283
-
284
- await vi.advanceTimersByTimeAsync(SIGKILL_ESCALATION_MS);
285
-
286
- // 只有新 child 的升级 timer 存活:旧 timer 已被 clearTimeout,不产生第二次 SIGKILL
287
- expect(newChild.killSignal).toBe("SIGKILL");
288
- expect(oldChild.killSignal).toBe("SIGTERM");
289
- });
290
- });