@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,220 +0,0 @@
1
- // src/execution/__tests__/descendant-sweep-guards.test.ts
2
- //
3
- // [S-17c / T2-②] sweepDescendantsOfSession 守卫分支补充(与 descendant-sweep.test.ts
4
- // 互补,不改动其既有用例)。
5
- //
6
- // 覆盖该文件未锁定的分支:
7
- // - register entry 缺 sessionId(TODO T5 marker fallback 锚点)→ skipped 留痕不杀;
8
- // - readProcessCmdline 两条失败边:ps 非零退出(status≠0/error)与 spawnSync 同步
9
- // 抛错 → cmdline undefined → "cmdline probe failed" 保守跳过;
10
- // - killPidWithEscalation 的 SIGTERM 发送失败(EPERM 等权限/垂死窗口)→ debug
11
- // 留痕不武装升级(30s 后无 SIGKILL);
12
- // - SIGKILL 升级窗口内目标自行退出(ESRCH)→ 吞掉不崩(目标已达成)。
13
- //
14
- // 真实 fs(临时目录)+ mock spawnSync / process.kill / logger——同 descendant-sweep
15
- // 布局,新增 logger mock 用于 SIGTERM 失败留痕断言。
16
-
17
- import { spawnSync } from "node:child_process";
18
- import * as fs from "node:fs";
19
- import * as os from "node:os";
20
- import * as path from "node:path";
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
- // 只 mock spawnSync(ps 探测受控);spawn 等其余实现保留 actual(本文件不经 runSpawn)。
30
- vi.mock("node:child_process", async () => {
31
- const actual = await import("node:child_process");
32
- return {
33
- ...actual,
34
- spawnSync: vi.fn(),
35
- };
36
- });
37
-
38
- import { sweepDescendantsOfSession } from "../engine/engines/pi/session-runner.ts";
39
- import { clearPendingCursors } from "../session-pending.ts";
40
- import { writeAliveMarker } from "../alive-store.ts";
41
-
42
- const mockSpawnSync = vi.mocked(spawnSync);
43
-
44
- const ALIVE_PID = 424_242;
45
-
46
- /** process.kill 受控:signal=0 探活与真实信号分别处置,信号调用记录到 kills。 */
47
- const kills: Array<{ pid: number; signal: string | number }> = [];
48
-
49
- function entryLine(customType: string, data: Record<string, unknown>): string {
50
- return `${JSON.stringify({
51
- type: "custom",
52
- customType,
53
- data,
54
- timestamp: "2026-09-01T00:00:00.000Z",
55
- id: `e-${Math.random().toString(36).slice(2)}`,
56
- })}\n`;
57
- }
58
-
59
- function registerData(fields: Record<string, unknown>): Record<string, unknown> {
60
- return { id: "bg-1", type: "session", name: "desc", status: "active", registeredAt: 1, ...fields };
61
- }
62
-
63
- /** ps -p <pid> -o command= 的完整 SpawnSyncReturns 形态(字段补齐省 as 链)。 */
64
- function psResult(stdout: string, status = 0): ReturnType<typeof spawnSync> {
65
- return {
66
- pid: 0,
67
- output: [null, stdout, ""],
68
- stdout,
69
- stderr: "",
70
- status,
71
- signal: null,
72
- } as ReturnType<typeof spawnSync>;
73
- }
74
-
75
- let sessionDir = "";
76
-
77
- beforeEach(() => {
78
- vi.clearAllMocks();
79
- clearPendingCursors();
80
- kills.length = 0;
81
- sessionDir = fs.mkdtempSync(path.join(os.tmpdir(), "desc-sweep-guard-"));
82
- vi.spyOn(process, "kill").mockImplementation(((pid: number, signal?: string | number) => {
83
- if (signal === undefined || signal === 0) return true; // 探活恒真(目标在窗口内存活)
84
- kills.push({ pid, signal });
85
- return true;
86
- }) as typeof process.kill);
87
- });
88
-
89
- afterEach(() => {
90
- vi.restoreAllMocks();
91
- clearPendingCursors();
92
- fs.rmSync(sessionDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
93
- });
94
-
95
- /** 层主 + 一个带 .alive marker 的直接后代(标准单层树)。 */
96
- function writeSingleLevelTree(): { rootFile: string; childFile: string } {
97
- const rootFile = path.join(sessionDir, "20260901T000000-000_root-sess.jsonl");
98
- const childFile = path.join(sessionDir, "20260901T000001-000_child-sess.jsonl");
99
- fs.writeFileSync(rootFile, entryLine("pending:register", registerData({ sessionId: "child-sess" })));
100
- fs.writeFileSync(childFile, "other entry line\n");
101
- writeAliveMarker(childFile, { pid: ALIVE_PID, id: "child-sess", startedAt: Date.now() });
102
- return { rootFile, childFile };
103
- }
104
-
105
- /** ps 探测默认桩:存活 pid 返回 pi rpc 形态。 */
106
- function stubCmdlineByPid(): void {
107
- mockSpawnSync.mockImplementation((_cmd: string, args?: readonly string[]) => {
108
- const pid = Number(args?.[1]);
109
- return psResult(`node /usr/local/bin/pi --mode rpc --session-dir /tmp/sess-${pid}\n`);
110
- });
111
- }
112
-
113
- describe("[S-17c] sweep 守卫:pending 项缺 sessionId(T5 marker fallback 锚点)", () => {
114
- it("register entry 无 sessionId → skipped 留痕(reason 指向 T5 兜底),不杀任何进程", () => {
115
- stubCmdlineByPid();
116
- const rootFile = path.join(sessionDir, "20260901T000000-000_root2.jsonl");
117
- fs.writeFileSync(rootFile, entryLine("pending:register", registerData({ sessionId: undefined })));
118
-
119
- const r = sweepDescendantsOfSession(rootFile, sessionDir, "test-nosessid");
120
-
121
- expect(r.killed).toHaveLength(0);
122
- expect(r.skipped).toHaveLength(1);
123
- // sessionId 字段回退为 pending 操作 id(诊断线索),reason 说明 marker 兜底锚点
124
- expect(r.skipped[0]).toMatchObject({
125
- sessionId: "bg-1",
126
- reason: expect.stringContaining("no sessionId (marker-based fallback pending T5)"),
127
- });
128
- expect(kills).toHaveLength(0);
129
- });
130
- });
131
-
132
- describe("[S-17c] sweep 守卫:readProcessCmdline 失败边(保守跳过不动手)", () => {
133
- it.each([
134
- [
135
- "ps 非零退出(进程刚死,ps -p 无此进程)",
136
- () =>
137
- mockSpawnSync.mockImplementation(() => psResult("", 1)),
138
- ],
139
- [
140
- "spawnSync 同步抛错(ps 不可用)",
141
- () =>
142
- mockSpawnSync.mockImplementation(() => {
143
- throw new Error("ps binary missing");
144
- }),
145
- ],
146
- ])("%s → cmdline probe failed,跳过不杀", (_label, setupProbe) => {
147
- const { rootFile } = writeSingleLevelTree();
148
- setupProbe();
149
-
150
- const r = sweepDescendantsOfSession(rootFile, sessionDir, "test-ps-fail");
151
-
152
- // [防误杀] 探测失败 = 无法证明是 pi rpc 形态 → 一律不动手
153
- expect(r.killed).toHaveLength(0);
154
- expect(r.skipped).toHaveLength(1);
155
- expect(r.skipped[0]).toMatchObject({
156
- pid: ALIVE_PID,
157
- reason: expect.stringContaining("cmdline probe failed (ps unavailable)"),
158
- });
159
- expect(kills).toHaveLength(0);
160
- });
161
- });
162
-
163
- describe("[S-17c] killPidWithEscalation:SIGTERM/ SIGKILL 失败边", () => {
164
- it("SIGTERM 发送即失败(权限/垂死窗口 EPERM)→ debug 留痕不武装升级,30s 后无 SIGKILL", async () => {
165
- vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] });
166
- stubCmdlineByPid();
167
- vi.spyOn(process, "kill").mockImplementation(((pid: number, signal?: string | number) => {
168
- if (signal === undefined || signal === 0) return true;
169
- if (signal === "SIGTERM") {
170
- const err = new Error("operation not permitted") as NodeJS.ErrnoException;
171
- err.code = "EPERM";
172
- throw err;
173
- }
174
- kills.push({ pid, signal });
175
- return true;
176
- }) as typeof process.kill);
177
-
178
- const { rootFile } = writeSingleLevelTree();
179
- const r = sweepDescendantsOfSession(rootFile, sessionDir, "test-sigterm-fail");
180
-
181
- // killed 列表记录的是处置意图;实际 SIGTERM 未送达(失败留痕,best-effort continue)
182
- expect(r.killed).toEqual([ALIVE_PID]);
183
- expect(loggerMock.debug).toHaveBeenCalledWith(
184
- expect.stringContaining(`SIGTERM to pid ${ALIVE_PID} failed (best-effort continue)`),
185
- );
186
-
187
- // 无升级 timer:SIGTERM 失败后直接 return,越过 30s 窗口也不补发 SIGKILL
188
- await vi.advanceTimersByTimeAsync(30_000);
189
- expect(kills).toHaveLength(0);
190
- });
191
-
192
- it("SIGKILL 升级窗口内目标自行退出(ESRCH)→ 吞掉不崩,SIGTERM 已达即目标达成", async () => {
193
- vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] });
194
- stubCmdlineByPid();
195
- // 探活恒真(fire 时窗口内仍活);SIGTERM 送达成功;SIGKILL 时进程已退出(ESRCH)
196
- vi.spyOn(process, "kill").mockImplementation(((pid: number, signal?: string | number) => {
197
- if (signal === undefined || signal === 0) return true;
198
- if (signal === "SIGKILL") {
199
- const err = new Error("No such process") as NodeJS.ErrnoException;
200
- err.code = "ESRCH";
201
- throw err;
202
- }
203
- kills.push({ pid, signal });
204
- return true;
205
- }) as typeof process.kill);
206
-
207
- const { rootFile } = writeSingleLevelTree();
208
- const r = sweepDescendantsOfSession(rootFile, sessionDir, "test-sigkill-esrch");
209
-
210
- expect(r.killed).toEqual([ALIVE_PID]);
211
- expect(kills).toEqual([{ pid: ALIVE_PID, signal: "SIGTERM" }]);
212
-
213
- // 升级 fire:ESRCH 是预期终态(进程在窗口内自行退出),吞掉不崩不重试
214
- await vi.advanceTimersByTimeAsync(30_000);
215
- expect(kills).toEqual([{ pid: ALIVE_PID, signal: "SIGTERM" }]);
216
- expect(loggerMock.debug).not.toHaveBeenCalledWith(
217
- expect.stringContaining("SIGTERM to pid"),
218
- );
219
- });
220
- });
@@ -1,269 +0,0 @@
1
- // src/execution/__tests__/descendant-sweep.test.ts
2
- //
3
- // [T2-② / P-T2b 主路径] 后代级联补杀 sweep + session-pending 差集清单导出。
4
- //
5
- // 设计:docs/design/subagent-core-unbounded-wait-audit.md §7.2 T2-②;P-T2b 裁决
6
- //(probe/p-t2b-report.md):pi SIGTERM 不级联孤儿后台后代(NO-CASCADE 三次稳定复现),
7
- // 补杀为主路径。覆盖:
8
- // - listActivePendingFromSessionFile:register−unregister 差集、sessionId 提取、
9
- // 与 count 口径共享增量游标(交错调用不错位)。
10
- // - sweepDescendantsOfSession:迭代展开至叶、pid 存活校验、cmdline(pi/--mode rpc)
11
- // 防误杀校验、反查失败 skip(T5 marker fallback TODO 锚点)。
12
- // - looksLikePiRpcProcess:命中 / 拒绝形态矩阵。
13
- // - killPidWithEscalation:SIGTERM 后 30s 仍活升级 SIGKILL(fake timers)。
14
- //
15
- // 真实 fs(临时目录)+ mock process.kill / spawnSync——session-pending 增量游标与
16
- // findSessionFileByHeaderId 的文件形态语义被真实执行。
17
-
18
- import { spawnSync } from "node:child_process";
19
- import * as fs from "node:fs";
20
- import * as os from "node:os";
21
- import * as path from "node:path";
22
-
23
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
24
-
25
- // 只 mock spawnSync(ps 探测受控);spawn 等其余实现保留 actual(本文件不经 runSpawn)。
26
- vi.mock("node:child_process", async () => {
27
- const actual = await import("node:child_process");
28
- return {
29
- ...actual,
30
- spawnSync: vi.fn(),
31
- };
32
- });
33
-
34
- import {
35
- looksLikePiRpcProcess,
36
- sweepDescendantsOfSession,
37
- } from "../engine/engines/pi/session-runner.ts";
38
- import {
39
- clearPendingCursors,
40
- listActivePendingFromSessionFile,
41
- readActivePendingFromSessionFile,
42
- } from "../session-pending.ts";
43
- import { writeAliveMarker } from "../alive-store.ts";
44
-
45
- const mockSpawnSync = vi.mocked(spawnSync);
46
-
47
- const ALIVE_PID_CHILD = 424_242;
48
- const ALIVE_PID_GRAND = 424_243;
49
- const DEAD_PID = 999_999;
50
-
51
- /** process.kill 受控:signal=0 探活(死 pid 抛 ESRCH),其余记录到 kills。 */
52
- const kills: Array<{ pid: number; signal: string | number }> = [];
53
-
54
- function entryLine(customType: string, data: Record<string, unknown>): string {
55
- return `${JSON.stringify({
56
- type: "custom",
57
- customType,
58
- data,
59
- timestamp: "2026-09-01T00:00:00.000Z",
60
- id: `e-${Math.random().toString(36).slice(2)}`,
61
- })}\n`;
62
- }
63
-
64
- function registerData(id: string, sessionId: string): Record<string, unknown> {
65
- return { id, type: "session", name: `desc-${id}`, status: "active", registeredAt: 1, expiresAt: undefined, sessionId };
66
- }
67
-
68
- let sessionDir = "";
69
-
70
- beforeEach(() => {
71
- clearPendingCursors();
72
- kills.length = 0;
73
- mockSpawnSync.mockReset();
74
- sessionDir = fs.mkdtempSync(path.join(os.tmpdir(), "desc-sweep-"));
75
- vi.spyOn(process, "kill").mockImplementation(((pid: number, signal?: string | number) => {
76
- if ((signal ?? 0) === 0) {
77
- if (pid === DEAD_PID) {
78
- const err = new Error("No such process") as NodeJS.ErrnoException;
79
- err.code = "ESRCH";
80
- throw err;
81
- }
82
- return true;
83
- }
84
- kills.push({ pid, signal: signal as string | number });
85
- return true;
86
- }) as typeof process.kill);
87
- });
88
-
89
- afterEach(() => {
90
- vi.restoreAllMocks();
91
- clearPendingCursors();
92
- fs.rmSync(sessionDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
93
- });
94
-
95
- /** 标准三层树:root(层主)→ child-sess → grand-sess(叶)。 */
96
- function writeThreeLevelTree(): { rootFile: string; childFile: string; grandFile: string } {
97
- const rootFile = path.join(sessionDir, "20260901T000000-000_root-sess.jsonl");
98
- const childFile = path.join(sessionDir, "20260901T000001-000_child-sess.jsonl");
99
- const grandFile = path.join(sessionDir, "20260901T000002-000_grand-sess.jsonl");
100
- // 层主:注册了 child(未 unregister)
101
- fs.writeFileSync(rootFile, entryLine("pending:register", registerData("bg-1", "child-sess")));
102
- // child:注册了 grand(未 unregister)——sweep 须迭代至叶
103
- fs.writeFileSync(childFile, entryLine("pending:register", registerData("bg-2", "grand-sess")));
104
- // grand:叶,无 pending
105
- fs.writeFileSync(grandFile, "other entry line\n");
106
- // .alive sidecar:pid 线索来源
107
- writeAliveMarker(childFile, { pid: ALIVE_PID_CHILD, id: "child-sess", startedAt: Date.now() });
108
- writeAliveMarker(grandFile, { pid: ALIVE_PID_GRAND, id: "grand-sess", startedAt: Date.now() });
109
- return { rootFile, childFile, grandFile };
110
- }
111
-
112
- /** ps -p <pid> -o command= 的完整 SpawnSyncReturns 形态(测试桩用,字段补齐省 as 链)。 */
113
- function psResult(stdout: string): ReturnType<typeof spawnSync> {
114
- return {
115
- pid: 0,
116
- output: [null, stdout, ""],
117
- stdout,
118
- stderr: "",
119
- status: 0,
120
- signal: null,
121
- } as ReturnType<typeof spawnSync>;
122
- }
123
-
124
- /** ps 探测默认桩:全部存活 pid 返回 pi rpc 形态。 */
125
- function stubCmdlineByPid(): void {
126
- mockSpawnSync.mockImplementation((_cmd: string, args?: readonly string[]) => {
127
- const pid = Number(args?.[1]);
128
- return psResult(`node /usr/local/bin/pi --mode rpc --no-extensions --session-dir /tmp/sess-${pid}\n`);
129
- });
130
- }
131
-
132
- describe("[T2-②] listActivePendingFromSessionFile(差集清单)", () => {
133
- it("register−unregister 差集 + sessionId/type 提取", () => {
134
- const f = path.join(sessionDir, "a.jsonl");
135
- fs.writeFileSync(
136
- f,
137
- entryLine("pending:register", registerData("bg-1", "sess-1")) +
138
- entryLine("pending:register", registerData("bg-2", "sess-2")) +
139
- entryLine("pending:unregister", { id: "bg-1" }),
140
- );
141
- const r = listActivePendingFromSessionFile(f);
142
- expect(r.error).toBeUndefined();
143
- expect(r.items).toHaveLength(1);
144
- expect(r.items[0]).toMatchObject({ id: "bg-2", sessionId: "sess-2", type: "session" });
145
- });
146
-
147
- it("count 与 list 交错调用共享增量游标(结果一致、不丢条目)", () => {
148
- const f = path.join(sessionDir, "b.jsonl");
149
- fs.writeFileSync(f, entryLine("pending:register", registerData("bg-1", "sess-1")));
150
- // 先 count(建立游标),后 append 再 list——增量读入新行
151
- const c1 = readActivePendingFromSessionFile(f);
152
- expect(c1.count).toBe(0); // 端口缺席时缺省 0(core notify-ports 默认实现)
153
- fs.appendFileSync(f, entryLine("pending:register", registerData("bg-2", "sess-2")));
154
- const r = listActivePendingFromSessionFile(f);
155
- expect(r.items.map((i) => i.id).sort()).toEqual(["bg-1", "bg-2"]);
156
- });
157
-
158
- it("sessionFile undefined / 不可读 → error 面空清单", () => {
159
- expect(listActivePendingFromSessionFile(undefined).error).toBeDefined();
160
- expect(listActivePendingFromSessionFile(path.join(sessionDir, "missing.jsonl")).error).toBeDefined();
161
- });
162
- });
163
-
164
- describe("[T2-②] sweepDescendantsOfSession(迭代补杀)", () => {
165
- it("层主→后代→孙迭代展开至叶,双校验通过者逐个 SIGTERM", () => {
166
- const { rootFile } = writeThreeLevelTree();
167
- stubCmdlineByPid();
168
-
169
- const r = sweepDescendantsOfSession(rootFile, sessionDir, "keep-alive watchdog");
170
- expect(r.killed.sort()).toEqual([ALIVE_PID_CHILD, ALIVE_PID_GRAND].sort());
171
- expect(r.skipped).toHaveLength(0);
172
- const sigterms = kills.filter((k) => k.signal === "SIGTERM").map((k) => k.pid);
173
- expect(sigterms.sort()).toEqual([ALIVE_PID_CHILD, ALIVE_PID_GRAND].sort());
174
- });
175
-
176
- it("SIGTERM 后 30s 仍存活 → 升级 SIGKILL(fake timers)", async () => {
177
- vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] });
178
- const { rootFile } = writeThreeLevelTree();
179
- stubCmdlineByPid();
180
-
181
- sweepDescendantsOfSession(rootFile, sessionDir, "test-escalation");
182
- expect(kills.filter((k) => k.signal === "SIGKILL")).toHaveLength(0);
183
-
184
- await vi.advanceTimersByTimeAsync(30_000);
185
- const sigkills = kills.filter((k) => k.signal === "SIGKILL").map((k) => k.pid);
186
- expect(sigkills.sort()).toEqual([ALIVE_PID_CHILD, ALIVE_PID_GRAND].sort());
187
- });
188
-
189
- it("cmdline 非 pi --mode rpc → pid 复用守卫拦截,不杀(但迭代继续展开其后代)", () => {
190
- const { rootFile, childFile, grandFile } = writeThreeLevelTree();
191
- // 伪造无关进程占住 child 的 pid;grand 正常
192
- mockSpawnSync.mockImplementation((_cmd: string, args?: readonly string[]) => {
193
- const pid = Number(args?.[1]);
194
- return psResult(
195
- pid === ALIVE_PID_CHILD ? "vim notes.txt\n" : "pi --mode rpc\n",
196
- );
197
- });
198
- void childFile;
199
- void grandFile;
200
-
201
- const r = sweepDescendantsOfSession(rootFile, sessionDir, "test-guard");
202
- expect(r.killed).toEqual([ALIVE_PID_GRAND]); // child 被拦,grand 不受影响
203
- expect(r.skipped).toHaveLength(1);
204
- expect(r.skipped[0]).toMatchObject({ sessionId: "child-sess", pid: ALIVE_PID_CHILD });
205
- expect(r.skipped[0].reason).toContain("pid reuse guard");
206
- expect(kills.map((k) => k.pid)).toEqual([ALIVE_PID_GRAND]);
207
- });
208
-
209
- it("存活校验不过(pid 已死)→ skip 不杀", () => {
210
- const { rootFile, childFile } = writeThreeLevelTree();
211
- // child 的 marker 指向死 pid
212
- writeAliveMarker(childFile, { pid: DEAD_PID, id: "child-sess", startedAt: Date.now() });
213
- stubCmdlineByPid();
214
-
215
- const r = sweepDescendantsOfSession(rootFile, sessionDir, "test-dead");
216
- expect(r.killed).toEqual([ALIVE_PID_GRAND]);
217
- expect(r.skipped[0]).toMatchObject({ pid: DEAD_PID, reason: expect.stringContaining("not alive") });
218
- });
219
-
220
- it("sessionId 反查失败 / 无 alive marker → skip 留 T5 锚点原因,不中断其余分支", () => {
221
- const rootFile = path.join(sessionDir, "20260901T000010-000_root2.jsonl");
222
- const childFile = path.join(sessionDir, "20260901T000011-000_naked-sess.jsonl");
223
- // 两条 register:missing-sess 反查不到;naked-sess 有文件但无 .alive marker
224
- fs.writeFileSync(
225
- rootFile,
226
- entryLine("pending:register", registerData("bg-m", "missing-sess")) +
227
- entryLine("pending:register", { id: "bg-n", type: "session", name: "naked", status: "active", registeredAt: 1, expiresAt: undefined, sessionId: "naked-sess" }),
228
- );
229
- fs.writeFileSync(childFile, "");
230
-
231
- const r = sweepDescendantsOfSession(rootFile, sessionDir, "test-skip");
232
- expect(r.killed).toHaveLength(0);
233
- expect(r.skipped).toHaveLength(2);
234
- const reasons = r.skipped.map((s) => s.reason).join(" | ");
235
- expect(reasons).toContain("not found in sessionDir");
236
- expect(reasons).toContain("pending T5");
237
- expect(reasons).toContain("no alive marker");
238
- });
239
-
240
- it("rootSessionFile undefined → 空 result(无层主快照不扫)", () => {
241
- stubCmdlineByPid();
242
- const r = sweepDescendantsOfSession(undefined, sessionDir, "test-empty");
243
- expect(r.killed).toHaveLength(0);
244
- expect(r.skipped).toHaveLength(0);
245
- expect(kills).toHaveLength(0);
246
- });
247
- });
248
-
249
- describe("[T2-②] looksLikePiRpcProcess(pid 复用防误杀校验)", () => {
250
- it.each([
251
- ["pi --mode rpc --no-extensions --session-dir /tmp/x", true],
252
- ["node /usr/local/bin/pi --mode rpc --session-dir /tmp/x", true],
253
- ["/opt/pi/bin/pi --mode=rpc", true],
254
- ["pi.js --mode rpc", true],
255
- ])("命中 pi rpc 形态: %s", (cmdline, expected) => {
256
- expect(looksLikePiRpcProcess(cmdline)).toBe(expected);
257
- });
258
-
259
- it.each([
260
- ["vim notes.txt"],
261
- ["nginx: master process"],
262
- ["tail -f /dev/null"],
263
- ["node server.js --mode rpc"], // 有 --mode rpc 但命令不是 pi
264
- ["pi --mode json"], // pi 但非 rpc
265
- ["api --mode rpc"], // 含 "pi" 子串但非 pi 词形
266
- ])("拒绝无关形态: %s", (cmdline) => {
267
- expect(looksLikePiRpcProcess(cmdline)).toBe(false);
268
- });
269
- });