@zhushanwen/subagent-core 0.2.0 → 0.5.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 (347) hide show
  1. package/README.md +45 -2
  2. package/agents/analyst.md +60 -0
  3. package/agents/coder.md +69 -0
  4. package/agents/debugger.md +66 -0
  5. package/agents/doc-reviewer.md +49 -0
  6. package/agents/explorer.md +63 -0
  7. package/agents/general-purpose.md +32 -0
  8. package/agents/orchestrator.md +61 -0
  9. package/agents/planner.md +53 -0
  10. package/agents/researcher.md +67 -0
  11. package/agents/reviewer.md +73 -0
  12. package/dist/chunk-43ONBFZX.js +240 -0
  13. package/dist/chunk-APZY4IME.js +27 -0
  14. package/dist/chunk-V3VHZ2VX.js +59 -0
  15. package/dist/execution/engine/engines/zcode/constants.cjs +62 -9
  16. package/dist/execution/engine/engines/zcode/constants.d.cts +116 -12
  17. package/dist/execution/engine/engines/zcode/constants.d.ts +116 -12
  18. package/dist/execution/engine/engines/zcode/constants.js +39 -7
  19. package/dist/execution/engine/engines/zcode/reader.d.cts +2 -1
  20. package/dist/execution/engine/engines/zcode/reader.d.ts +2 -1
  21. package/dist/execution/engine/engines/zcode/reader.js +4 -234
  22. package/dist/execution/engine/paths.js +7 -19
  23. package/dist/index.cjs +18234 -1277
  24. package/dist/index.d.cts +5646 -673
  25. package/dist/index.d.ts +5646 -673
  26. package/dist/index.js +17828 -1277
  27. package/dist/{types-BxyAidGf.d.cts → types-Dv4QhSJ_.d.cts} +678 -112
  28. package/dist/{types-BxyAidGf.d.ts → types-Dv4QhSJ_.d.ts} +678 -112
  29. package/package.json +7 -4
  30. package/src/__tests__/agent-opts-resolver-schema-prompt.test.ts +0 -1
  31. package/src/__tests__/append-system-prompt-assembly.test.ts +8 -7
  32. package/src/__tests__/fr4-get-state-handshake.test.ts +2 -2
  33. package/src/__tests__/m2-append-content-probe.test.ts +0 -1
  34. package/src/__tests__/manifest-store.test.ts +10 -9
  35. package/src/__tests__/record-store-cache.test.ts +0 -2
  36. package/src/__tests__/record-store-index.test.ts +1 -2
  37. package/src/__tests__/review-fix-loop-script.test.ts +93 -3
  38. package/src/__tests__/review-fix-loop-utils.test.ts +31 -10
  39. package/src/__tests__/robustness-low-batch1.test.ts +6 -5
  40. package/src/__tests__/robustness-medium-batch1.test.ts +2 -2
  41. package/src/__tests__/robustness-medium-batch2.test.ts +17 -6
  42. package/src/__tests__/robustness-medium-batch4.test.ts +2 -2
  43. package/src/__tests__/session-runner.test.ts +1 -1
  44. package/src/__tests__/smoke.test.ts +9 -2
  45. package/src/core/__tests__/host-services.test.ts +2 -2
  46. package/src/core/__tests__/logger.test.ts +1 -1
  47. package/src/core/error-message.ts +9 -0
  48. package/src/core/host-services.ts +21 -5
  49. package/src/core/notify-ports.ts +18 -5
  50. package/src/execution/__tests__/agent-profile.test.ts +232 -0
  51. package/src/execution/__tests__/agent-registry.test.ts +370 -0
  52. package/src/execution/__tests__/agent-result-mapper.test.ts +18 -2
  53. package/src/execution/__tests__/agents-assembly.test.ts +219 -0
  54. package/src/execution/__tests__/ask-user-transit-e2e.test.ts +6 -6
  55. package/src/execution/__tests__/chat-engine-routing.test.ts +136 -30
  56. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +5 -4
  57. package/src/execution/__tests__/cold-resurrect.test.ts +283 -0
  58. package/src/execution/__tests__/create-concurrency-pool.test.ts +194 -0
  59. package/src/execution/__tests__/delivery-methods.test.ts +77 -62
  60. package/src/execution/__tests__/descendant-sweep-guards.test.ts +220 -0
  61. package/src/execution/__tests__/descendant-sweep.test.ts +269 -0
  62. package/src/execution/__tests__/dialog-queue.test.ts +199 -1
  63. package/src/execution/__tests__/engine-model-validation.test.ts +741 -0
  64. package/src/execution/__tests__/epipe-fallback.test.ts +31 -24
  65. package/src/execution/__tests__/execute-and-await-worktree.test.ts +21 -91
  66. package/src/execution/__tests__/execute-nesting.test.ts +34 -188
  67. package/src/execution/__tests__/execution-record.test.ts +217 -60
  68. package/src/execution/__tests__/execution-runtime-face.test.ts +272 -0
  69. package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +3 -3
  70. package/src/execution/__tests__/finalize-record.test.ts +278 -16
  71. package/src/execution/__tests__/gc-timer.test.ts +57 -2
  72. package/src/execution/__tests__/get-record-for-action-restart.test.ts +14 -12
  73. package/src/execution/__tests__/get-state-handshake.test.ts +127 -0
  74. package/src/execution/__tests__/helpers/mock-extension-api.ts +1 -1
  75. package/src/execution/__tests__/helpers/session-runner-mocks.ts +113 -0
  76. package/src/execution/__tests__/helpers/spawn-mock.ts +150 -3
  77. package/src/execution/__tests__/helpers/subagent-service-mocks.ts +137 -0
  78. package/src/execution/__tests__/keep-alive-no-progress.test.ts +280 -0
  79. package/src/execution/__tests__/kill-all-escalation.test.ts +196 -0
  80. package/src/execution/__tests__/lifecycle-manager-idle-timer-identity.test.ts +117 -0
  81. package/src/execution/__tests__/lifecycle-manager.test.ts +39 -0
  82. package/src/execution/__tests__/lifecycle-predicates.test.ts +1 -2
  83. package/src/execution/__tests__/manifest-parentid.test.ts +4 -4
  84. package/src/execution/__tests__/manifest-store-tmp-recovery.test.ts +99 -0
  85. package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +113 -0
  86. package/src/execution/__tests__/model-resolver.test.ts +2 -2
  87. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +23 -121
  88. package/src/execution/__tests__/nested-visibility.test.ts +2 -0
  89. package/src/execution/__tests__/notify-ledger.test.ts +1249 -0
  90. package/src/execution/__tests__/output-collector.test.ts +117 -6
  91. package/src/execution/__tests__/pi-invocation.test.ts +1 -1
  92. package/src/execution/__tests__/record-store-orphan-revive.test.ts +159 -0
  93. package/src/execution/__tests__/record-store.test.ts +89 -5
  94. package/src/execution/__tests__/records-cwd-isolation.test.ts +2 -2
  95. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +29 -132
  96. package/src/execution/__tests__/recursive-visibility-env.test.ts +55 -179
  97. package/src/execution/__tests__/rpc-mode.test.ts +2 -2
  98. package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +387 -0
  99. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +19 -9
  100. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +5 -4
  101. package/src/execution/__tests__/run-spawn-edges.test.ts +418 -26
  102. package/src/execution/__tests__/run-spawn-integration.test.ts +3 -3
  103. package/src/execution/__tests__/run-spawn-resume.test.ts +2 -2
  104. package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +2 -2
  105. package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +16 -8
  106. package/src/execution/__tests__/service-kill-escalation.test.ts +91 -0
  107. package/src/execution/__tests__/session-context-resolver.test.ts +0 -2
  108. package/src/execution/__tests__/session-file-gc.test.ts +35 -0
  109. package/src/execution/__tests__/session-pending.test.ts +471 -0
  110. package/src/execution/__tests__/session-reconstructor.test.ts +366 -15
  111. package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +110 -0
  112. package/src/execution/__tests__/session-runner-close-prune.test.ts +181 -0
  113. package/src/execution/__tests__/session-runner-dispatch.test.ts +489 -0
  114. package/src/execution/__tests__/session-runner-epipe.test.ts +4 -3
  115. package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +227 -0
  116. package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +290 -0
  117. package/src/execution/__tests__/session-runner-schema-env.test.ts +7 -5
  118. package/src/execution/__tests__/settled-watchdog.test.ts +524 -0
  119. package/src/execution/__tests__/spawn-args.test.ts +11 -10
  120. package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +26 -23
  121. package/src/execution/__tests__/spawn-event-adapter.test.ts +1 -1
  122. package/src/execution/__tests__/spawn-worktree-guidance.test.ts +5 -4
  123. package/src/execution/__tests__/spawned-children.test.ts +155 -11
  124. package/src/execution/__tests__/start-sync-model-guard.test.ts +4 -4
  125. package/src/execution/__tests__/stdin-writer.test.ts +6 -4
  126. package/src/execution/__tests__/stream-sink-retirement.test.ts +6 -6
  127. package/src/execution/__tests__/subagent-actions-core.test.ts +1017 -0
  128. package/src/execution/__tests__/subagent-service-message-close.test.ts +99 -35
  129. package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +174 -0
  130. package/src/execution/__tests__/subagent-service-notify-gate.test.ts +271 -0
  131. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +33 -29
  132. package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +341 -0
  133. package/src/execution/__tests__/subagent-service.test.ts +37 -30
  134. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +88 -12
  135. package/src/execution/__tests__/subprocess-agent-runner-timeout.test.ts +75 -0
  136. package/src/execution/__tests__/subprocess-agent-runner.test.ts +22 -20
  137. package/src/execution/__tests__/temp-prompt.test.ts +1 -1
  138. package/src/execution/__tests__/timeout-integration.test.ts +13 -12
  139. package/src/execution/__tests__/turn-limiter-semantics.test.ts +1 -1
  140. package/src/execution/__tests__/turn-limiter.test.ts +1 -1
  141. package/src/execution/__tests__/ui-request-handler-factory.test.ts +107 -7
  142. package/src/execution/__tests__/ui-request-handler.test.ts +4 -3
  143. package/src/execution/__tests__/ui-request-queue.test.ts +167 -2
  144. package/src/execution/__tests__/worktree-git-ops.test.ts +571 -0
  145. package/src/execution/__tests__/worktree-manager.test.ts +14 -5
  146. package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +4 -3
  147. package/src/execution/__tests__/worktree-reconcile-aging.test.ts +204 -0
  148. package/src/execution/agent-registry.ts +298 -30
  149. package/src/execution/agent-result-mapper.ts +3 -0
  150. package/src/execution/agents-assembly.ts +88 -0
  151. package/src/execution/cold-resurrect.ts +199 -0
  152. package/src/execution/concurrency-pool.ts +71 -9
  153. package/src/execution/dialog-queue.ts +101 -3
  154. package/src/execution/engine/__tests__/common/capability-gate.test.ts +91 -0
  155. package/src/execution/engine/__tests__/common/data-dir.test.ts +1 -1
  156. package/src/execution/engine/__tests__/common/journal-wiring.test.ts +84 -0
  157. package/src/execution/engine/__tests__/common/kill-chain.test.ts +34 -7
  158. package/src/execution/engine/__tests__/common/persona-router.test.ts +4 -5
  159. package/src/execution/engine/__tests__/common/pool-manager.test.ts +211 -51
  160. package/src/execution/engine/__tests__/common/session-view-service-zcode-dbpath.test.ts +149 -0
  161. package/src/execution/engine/__tests__/common/session-view-service.test.ts +499 -0
  162. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +56 -99
  163. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +88 -15
  164. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +33 -7
  165. package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +1 -1
  166. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +75 -54
  167. package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +128 -0
  168. package/src/execution/engine/__tests__/engine-discovery.test.ts +15 -3
  169. package/src/execution/engine/__tests__/model-prompt.test.ts +15 -3
  170. package/src/execution/engine/__tests__/registry.test.ts +94 -3
  171. package/src/execution/engine/__tests__/routing.test.ts +115 -6
  172. package/src/execution/engine/common/capability-gate.ts +92 -0
  173. package/src/execution/engine/common/journal-wiring.ts +97 -0
  174. package/src/execution/engine/common/kill-chain.ts +42 -10
  175. package/src/execution/engine/common/nesting-guard.ts +66 -0
  176. package/src/execution/engine/common/persona-router.ts +18 -8
  177. package/src/execution/engine/common/pool-manager.ts +332 -78
  178. package/src/execution/engine/common/session-view-service.ts +506 -0
  179. package/src/execution/engine/common/session-view-types.ts +125 -0
  180. package/src/execution/engine/engine-discovery.ts +12 -16
  181. package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +174 -28
  182. package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +257 -0
  183. package/src/execution/{argv-mirror.ts → engine/engines/pi/argv-mirror.ts} +43 -21
  184. package/src/execution/{get-state-handshake.ts → engine/engines/pi/get-state-handshake.ts} +86 -11
  185. package/src/execution/{output-collector.ts → engine/engines/pi/output-collector.ts} +108 -19
  186. package/src/execution/engine/engines/pi/pi-engine.ts +434 -38
  187. package/src/execution/{pi-invocation.ts → engine/engines/pi/pi-invocation.ts} +2 -2
  188. package/src/execution/engine/engines/pi/reader.ts +11 -0
  189. package/src/execution/engine/engines/pi/registration.ts +4 -2
  190. package/src/execution/engine/engines/pi/session-runner.ts +2840 -0
  191. package/src/execution/{spawn-event-adapter.ts → engine/engines/pi/spawn-event-adapter.ts} +75 -34
  192. package/src/execution/{stdin-writer.ts → engine/engines/pi/stdin-writer.ts} +10 -9
  193. package/src/execution/{temp-prompt.ts → engine/engines/pi/temp-prompt.ts} +2 -2
  194. package/src/execution/{turn-limiter.ts → engine/engines/pi/turn-limiter.ts} +1 -1
  195. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +301 -0
  196. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-appserver.json +36 -0
  197. package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +321 -0
  198. package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +473 -0
  199. package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +7 -210
  200. package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +6 -110
  201. package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +247 -0
  202. package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +451 -0
  203. package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +792 -0
  204. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +782 -0
  205. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +168 -0
  206. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +210 -0
  207. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +353 -0
  208. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +282 -0
  209. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +372 -0
  210. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +65 -47
  211. package/src/execution/engine/engines/zcode/appserver-launcher.ts +167 -0
  212. package/src/execution/engine/engines/zcode/connection.ts +598 -0
  213. package/src/execution/engine/engines/zcode/constants.ts +147 -14
  214. package/src/execution/engine/engines/zcode/golden-sample.ts +42 -34
  215. package/src/execution/engine/engines/zcode/parser.ts +12 -346
  216. package/src/execution/engine/engines/zcode/preparer.ts +22 -150
  217. package/src/execution/engine/engines/zcode/session-channel.ts +968 -0
  218. package/src/execution/engine/engines/zcode/zcode-engine.ts +1023 -250
  219. package/src/execution/engine/host-task-spec.ts +32 -36
  220. package/src/execution/engine/model-validation.ts +177 -0
  221. package/src/execution/engine/port.ts +65 -7
  222. package/src/execution/engine/registry.ts +53 -0
  223. package/src/execution/engine/routing.ts +75 -1
  224. package/src/execution/engine/types.ts +30 -103
  225. package/src/execution/execution-record.ts +222 -144
  226. package/src/execution/finalize-record.ts +181 -85
  227. package/src/execution/idle-gc.ts +28 -0
  228. package/src/execution/lifecycle-manager.ts +27 -3
  229. package/src/execution/lifecycle-predicates.ts +1 -1
  230. package/src/execution/manifest-store.ts +53 -62
  231. package/src/execution/notifier.ts +10 -10
  232. package/src/execution/notify-host.ts +212 -0
  233. package/src/execution/notify-ledger.ts +117 -16
  234. package/src/execution/record-entry.ts +8 -2
  235. package/src/execution/record-store.ts +148 -88
  236. package/src/execution/round-settlement.ts +93 -0
  237. package/src/execution/session-file-gc.ts +96 -62
  238. package/src/execution/session-pending.ts +213 -62
  239. package/src/execution/session-reconstructor.ts +256 -143
  240. package/src/execution/sessions-index.ts +127 -102
  241. package/src/execution/settled-watchdog.ts +311 -0
  242. package/src/execution/subagent-actions-core.ts +686 -0
  243. package/src/execution/subagent-service.ts +1106 -907
  244. package/src/execution/subprocess-agent-runner.ts +144 -97
  245. package/src/execution/types.ts +9 -0
  246. package/src/execution/ui-request-handler-factory.ts +126 -75
  247. package/src/execution/ui-request-queue.ts +50 -19
  248. package/src/execution/worktree-git-ops.ts +397 -0
  249. package/src/execution/worktree-manager.ts +72 -10
  250. package/src/execution/worktree-registry.ts +6 -12
  251. package/src/index.ts +542 -9
  252. package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +42 -3
  253. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +3 -5
  254. package/src/orchestration/__tests__/agent-call-stream.test.ts +2 -2
  255. package/src/orchestration/__tests__/args-meta.test.ts +358 -0
  256. package/src/orchestration/__tests__/args-validator.test.ts +14 -0
  257. package/src/orchestration/__tests__/error-recovery-rebuild-failure.test.ts +312 -0
  258. package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +384 -0
  259. package/src/orchestration/__tests__/execute-agent-call.test.ts +151 -11
  260. package/src/orchestration/__tests__/file-run-store-prune.test.ts +168 -0
  261. package/src/orchestration/__tests__/file-run-store-throttle.test.ts +168 -0
  262. package/src/orchestration/__tests__/file-run-store.test.ts +390 -0
  263. package/src/orchestration/__tests__/helpers/flush-microtasks.ts +13 -0
  264. package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +34 -0
  265. package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +357 -0
  266. package/src/orchestration/__tests__/lifecycle-recover-crashed.test.ts +289 -0
  267. package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +3 -3
  268. package/src/orchestration/__tests__/lifecycle.test.ts +84 -5
  269. package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +1 -1
  270. package/src/orchestration/__tests__/run-snapshot.test.ts +356 -0
  271. package/src/orchestration/__tests__/script-generate.test.ts +314 -0
  272. package/src/orchestration/__tests__/script-lint.test.ts +17 -0
  273. package/src/orchestration/__tests__/test-mocks.ts +2 -0
  274. package/src/orchestration/__tests__/worker-exit-without-result.test.ts +7 -6
  275. package/src/orchestration/__tests__/worker-message-pump-finalize-run.test.ts +229 -0
  276. package/src/orchestration/__tests__/{error-recovery-handlers.test.ts → worker-message-pump-handlers.test.ts} +28 -27
  277. package/src/orchestration/__tests__/{error-recovery-postmessage-defense.test.ts → worker-message-pump-postmessage-defense.test.ts} +8 -8
  278. package/src/orchestration/__tests__/{error-recovery-serialize-failed-result.test.ts → worker-message-pump-serialize-failed-result.test.ts} +2 -2
  279. package/src/orchestration/__tests__/{error-recovery-workflow-call.test.ts → worker-message-pump-workflow-call.test.ts} +1 -1
  280. package/src/orchestration/__tests__/worker-pending-timeout-abort.test.ts +275 -0
  281. package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +78 -1
  282. package/src/orchestration/__tests__/worker-script-builder.test.ts +1 -1
  283. package/src/orchestration/__tests__/workflow-files.test.ts +186 -0
  284. package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +1 -1
  285. package/src/orchestration/__tests__/workflow-run-summary.test.ts +119 -0
  286. package/src/orchestration/__tests__/workflow-script-registry-impl.test.ts +124 -0
  287. package/src/orchestration/agent-opts-resolver.ts +8 -8
  288. package/src/orchestration/args-meta.ts +198 -0
  289. package/src/orchestration/args-validator.ts +53 -31
  290. package/src/orchestration/execute-agent-call.ts +25 -79
  291. package/src/orchestration/file-run-store.ts +327 -0
  292. package/src/orchestration/launcher.ts +160 -104
  293. package/src/orchestration/lifecycle.ts +371 -97
  294. package/src/orchestration/models/agent-call.ts +7 -7
  295. package/src/orchestration/models/budget.ts +5 -5
  296. package/src/orchestration/models/ports.ts +4 -4
  297. package/src/orchestration/models/run-runtime.ts +15 -15
  298. package/src/orchestration/models/trace.ts +9 -9
  299. package/src/orchestration/models/types.ts +111 -22
  300. package/src/orchestration/models/workflow-run.ts +28 -28
  301. package/src/orchestration/models/workflow-script.ts +4 -4
  302. package/src/orchestration/run-snapshot.ts +266 -0
  303. package/src/orchestration/script-generate.ts +154 -0
  304. package/src/orchestration/script-lint.ts +123 -90
  305. package/src/orchestration/worker-handle.ts +10 -10
  306. package/src/orchestration/worker-host.ts +10 -10
  307. package/src/orchestration/{error-recovery.ts → worker-message-pump.ts} +498 -221
  308. package/src/orchestration/worker-script-builder.ts +401 -346
  309. package/src/orchestration/workflow-files.ts +37 -11
  310. package/src/orchestration/workflow-run-summary.ts +69 -0
  311. package/src/orchestration/workflow-script-registry-impl.ts +31 -9
  312. package/src/shared/__tests__/agent-ref.test.ts +209 -2
  313. package/src/shared/__tests__/atomic-write.test.ts +267 -0
  314. package/src/shared/__tests__/bounded-serialize.test.ts +236 -0
  315. package/src/shared/__tests__/injection-render.test.ts +518 -0
  316. package/src/shared/__tests__/meta-parser.test.ts +151 -0
  317. package/src/shared/__tests__/resource-discovery-host-roots.test.ts +474 -0
  318. package/src/shared/__tests__/resource-discovery.test.ts +261 -217
  319. package/src/shared/agent-ref.ts +143 -2
  320. package/src/shared/atomic-write.ts +323 -0
  321. package/src/shared/bounded-serialize.ts +154 -0
  322. package/src/shared/injection-render.ts +279 -0
  323. package/src/shared/meta-parser.ts +182 -68
  324. package/src/shared/model-ref.ts +15 -2
  325. package/src/shared/resource-discovery.ts +97 -204
  326. package/src/shared/resource-meta.ts +14 -0
  327. package/src/shared/schema-jsonify.ts +1 -1
  328. package/src/shared/xml-injection.ts +9 -9
  329. package/workflows/README.md +9 -9
  330. package/workflows/chain.js +4 -2
  331. package/workflows/map-reduce.js +5 -3
  332. package/workflows/parallel.js +5 -3
  333. package/workflows/review-fix-loop-utils.cjs +165 -92
  334. package/workflows/review-fix-loop-utils.d.cts +287 -0
  335. package/workflows/review-fix-loop.js +18 -7
  336. package/workflows/scatter-gather.js +4 -2
  337. package/dist/chunk-3VOERJPJ.js +0 -22
  338. package/src/execution/__tests__/execute-options-mapper.test.ts +0 -200
  339. package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +0 -164
  340. package/src/execution/engine/engines/pi/task-spec-mapper.ts +0 -100
  341. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +0 -39
  342. package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +0 -150
  343. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +0 -580
  344. package/src/execution/engine/engines/zcode/launcher.ts +0 -161
  345. package/src/execution/execute-options-mapper.ts +0 -115
  346. package/src/execution/session-runner.ts +0 -1789
  347. package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +0 -288
@@ -5,8 +5,17 @@
5
5
  // (usage 收口进 getTotalUsage,text 收口进 getFullText,均在 execution-record.test 测)。
6
6
  import { describe, expect, it } from "vitest";
7
7
 
8
- import { isStaleContextErrorMsg, DETERMINISTIC_SCHEMA_FAILURE_PREFIX, isDeterministicSchemaFailureMsg, STALE_CONTEXT_PATTERNS } from "../../orchestration/execute-agent-call.ts";
9
- import { collectResult, describeMissingParsedOutput, extractParsedOutput, neutralizeStalePatterns } from "../output-collector.ts";
8
+ import {
9
+ classifyFailureKind,
10
+ collectResult,
11
+ describeMissingParsedOutput,
12
+ DETERMINISTIC_SCHEMA_FAILURE_PREFIX,
13
+ extractParsedOutput,
14
+ isDeterministicSchemaFailureMsg,
15
+ isStaleContextErrorMsg,
16
+ neutralizeStalePatterns,
17
+ STALE_CONTEXT_PATTERNS,
18
+ } from "../engine/engines/pi/output-collector.ts";
10
19
  import type { ExecutionRecord, ToolCall } from "../types.ts";
11
20
 
12
21
  // ============================================================
@@ -162,9 +171,9 @@ describe("describeMissingParsedOutput", () => {
162
171
  // ── [F-R1] 态2(isError 分支)错误摘要拼接段中和 ──
163
172
  //
164
173
  // 动态错误文本(模型/provider 原始错误)可能携带 "aborted"/"ctx is stale" 等
165
- // STALE_CONTEXT_PATTERNS 词;不中和则归因 error 经 collectResult → result.error
166
- // 流到 execute-agent-call 的 isStaleContextErrorMsg 分诊,被误诊为 stale-context
167
- // 跳过重试。固定前缀静态无命中(上一用例锁定),中和只针对动态段。
174
+ // STALE_CONTEXT_PATTERNS 词;不中和则归因 error 经 collectResult
175
+ // classifyFailureKind 分诊被误标 stale_context(归因语义被污染)。
176
+ // 固定前缀静态无命中(上一用例锁定),中和只针对动态段。
168
177
  describe("F-R1: 态2 错误摘要 STALE_CONTEXT_PATTERNS 中和", () => {
169
178
  function failedSoCallsWith(text: string): ToolCall[] {
170
179
  return [
@@ -233,7 +242,7 @@ describe("describeMissingParsedOutput", () => {
233
242
  // ============================================================
234
243
 
235
244
  describe("MF-1: 三态可重试性矩阵(确定性失败标记)", () => {
236
- // 矩阵(SSOT 注释在 execute-agent-call DETERMINISTIC_SCHEMA_FAILURE_PREFIX 与
245
+ // 矩阵(SSOT 注释在本模块 DETERMINISTIC_SCHEMA_FAILURE_PREFIX 与
237
246
  // describeMissingParsedOutput JSDoc,本 describe 是执行面锁定):
238
247
  // 态① 从未调用 SO → 带标记 → 不可重试(环境确定性,C1 安装盲区)
239
248
  // 态② SO isError(gate 终止/不可满足 schema)→ 带标记 → 不可重试(同 schema 重试必同结果)
@@ -356,3 +365,105 @@ describe("collectResult — F-1 schemaExpected 失败标注", () => {
356
365
  expect(isDeterministicSchemaFailureMsg(result.error)).toBe(true);
357
366
  });
358
367
  });
368
+
369
+ // ============================================================
370
+ // D5-③: failureKind 分类(产出侧单点识别)
371
+ // ============================================================
372
+
373
+ describe("classifyFailureKind(词表 → 结构化标签)", () => {
374
+ it("pi 真实 stale 文案 → stale_context", () => {
375
+ const piRealStale =
376
+ "This extension ctx is stale after session replacement or reload. Do not use a captured pi or command ctx after ctx.newSession().";
377
+ expect(classifyFailureKind(piRealStale)).toBe("stale_context");
378
+ });
379
+
380
+ it("abort 族(context canceled / aborted)→ stale_context", () => {
381
+ expect(classifyFailureKind("Request context canceled by provider")).toBe("stale_context");
382
+ expect(classifyFailureKind("Subprocess aborted by runtime shutdown")).toBe("stale_context");
383
+ });
384
+
385
+ it("确定性 schema 失败标记前缀 → schema_deterministic", () => {
386
+ expect(classifyFailureKind(
387
+ `${DETERMINISTIC_SCHEMA_FAILURE_PREFIX} Agent finished without producing a structured output`,
388
+ )).toBe("schema_deterministic");
389
+ });
390
+
391
+ it("未知文案(词表零命中,pi 升级改写文案后的形态)→ unknown(安全默认,可重试)", () => {
392
+ expect(classifyFailureKind("provider 503 service unavailable")).toBe("unknown");
393
+ expect(classifyFailureKind("spawn EAGAIN")).toBe("unknown");
394
+ expect(classifyFailureKind("extension runtime was superseded by a newer orchestration epoch")).toBe("unknown");
395
+ });
396
+
397
+ it("undefined(成功路径)→ undefined(不落失败分诊)", () => {
398
+ expect(classifyFailureKind(undefined)).toBeUndefined();
399
+ });
400
+
401
+ it("标记词与 stale 词表零交集(分诊优先级无歧义)", () => {
402
+ expect(classifyFailureKind(DETERMINISTIC_SCHEMA_FAILURE_PREFIX)).toBe("schema_deterministic");
403
+ });
404
+ });
405
+
406
+ describe("collectResult — D5-③ failureKind 产出", () => {
407
+ /** 最小 ExecutionRecord stub(collectResult 只读 turns/toolCalls 派生面)。 */
408
+ function makeRecordWithCalls(calls: ToolCall[]): ExecutionRecord {
409
+ return {
410
+ id: "rec-fk",
411
+ turns: [{ toolCalls: calls, text: "done", turnCount: 1, closed: true }],
412
+ turnCount: 1,
413
+ } as unknown as ExecutionRecord;
414
+ }
415
+
416
+ const baseArgs = {
417
+ startTime: Date.now(),
418
+ sessionId: "s-fk",
419
+ sessionFile: undefined,
420
+ };
421
+
422
+ it("error 命中 stale 词表 → failureKind=stale_context", () => {
423
+ const record = makeRecordWithCalls([]);
424
+ const result = collectResult(record, {
425
+ ...baseArgs,
426
+ success: false,
427
+ error: "This extension ctx is stale after session replacement or reload.",
428
+ });
429
+ expect(result.failureKind).toBe("stale_context");
430
+ });
431
+
432
+ it("F-1 归因覆写(态①②确定性标记)→ failureKind=schema_deterministic", () => {
433
+ const record = makeRecordWithCalls([]);
434
+ const result = collectResult(record, { ...baseArgs, success: true, error: undefined, schemaExpected: true });
435
+ expect(result.error).toBeDefined();
436
+ expect(result.failureKind).toBe("schema_deterministic");
437
+ });
438
+
439
+ it("态③归因(调用过但无 details,无标记)→ failureKind=unknown(可重试,矩阵保留)", () => {
440
+ const record = makeRecordWithCalls([
441
+ { toolName: "structured-output", result: { content: [] } },
442
+ ]);
443
+ const result = collectResult(record, { ...baseArgs, success: true, error: undefined, schemaExpected: true });
444
+ expect(result.error).toBeDefined();
445
+ expect(result.failureKind).toBe("unknown");
446
+ });
447
+
448
+ it("普通 provider 错误 → failureKind=unknown", () => {
449
+ const record = makeRecordWithCalls([]);
450
+ const result = collectResult(record, { ...baseArgs, success: false, error: "provider boom 5xx" });
451
+ expect(result.failureKind).toBe("unknown");
452
+ });
453
+
454
+ it("成功路径(error undefined)→ failureKind 不写(缺省 = unknown = 可重试,消费侧语义)", () => {
455
+ const record = makeRecordWithCalls([]);
456
+ const result = collectResult(record, { ...baseArgs, success: true, error: undefined });
457
+ expect(result.failureKind).toBeUndefined();
458
+ });
459
+
460
+ it("词表漂移失效模式:未知文案(未来 pi 形态)→ failureKind=unknown(保守重试,非静默漏诊)", () => {
461
+ const record = makeRecordWithCalls([]);
462
+ const result = collectResult(record, {
463
+ ...baseArgs,
464
+ success: false,
465
+ error: "extension runtime was superseded by a newer orchestration epoch",
466
+ });
467
+ expect(result.failureKind).toBe("unknown");
468
+ });
469
+ });
@@ -5,7 +5,7 @@ import * as path from "node:path";
5
5
 
6
6
  import { afterEach, describe, expect, it } from "vitest";
7
7
 
8
- import { getPiInvocation } from "../pi-invocation.ts";
8
+ import { getPiInvocation } from "../engine/engines/pi/pi-invocation.ts";
9
9
  import { RELAY_ENV_NODE, RELAY_ENV_SCRIPT, RELAY_ENV_SOCKET } from "../relay-env.ts";
10
10
 
11
11
  describe("getPiInvocation", () => {
@@ -0,0 +1,159 @@
1
+ // src/execution/__tests__/record-store-orphan-revive.test.ts
2
+ //
3
+ // [PS-10/T6④] revive() 复位 orphanJudged——落实 recoverOrphanRecords 注释承诺
4
+ // 「IO 恢复后重开可重判」。
5
+ //
6
+ // 缺陷(设计 §4.3 PS-10):resumable 形态(IO-error 保守分支)无 .finalized sidecar
7
+ // 锚,重判资格完全由 orphanJudged 实例级缓存承载;dispose 有 clear 但 /new 复活路径
8
+ // revive() 此前不复位 → 同进程内曾经的 IO 失败记录永久停留 resumable。
9
+ //
10
+ // 覆盖(验收④:IO 恢复记录 revive 后再次扫描会重新判定,不再停留 resumable):
11
+ // 阶段 1 IO 失败(openSync EACCES 注入)→ 判定落 resumable entry(保守分支真实触发)
12
+ // 阶段 2 IO 恢复 + revive() → 再次 recoverOrphanRecords 重新判定收敛 closed+gc
13
+ // (未修复时 orphanJudged 残留 → 605 行 continue 零 entry,本用例红)
14
+ //
15
+ // fs mock:只劫持 openSync 的读模式("r")按目标路径计数放行——扫描侧
16
+ // readIdentityHeader(session-reconstructor)与判定侧 readLastJsonlLine(record-store
17
+ // 私有)走同一原语,放行计数 = 让扫描成功、判定失败的最小注入面;写模式(sidecar)
18
+ // 与其余 fs 全部透传 actual。
19
+
20
+ import * as fs from "node:fs";
21
+ import * as os from "node:os";
22
+ import * as path from "node:path";
23
+
24
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
25
+
26
+ const { openSyncMock, fsActualHolder, loggerMock } = vi.hoisted(() => ({
27
+ openSyncMock: vi.fn(),
28
+ fsActualHolder: {} as { fs?: typeof import("node:fs") },
29
+ loggerMock: { debug: vi.fn(), warn: vi.fn(), error: vi.fn() },
30
+ }));
31
+ vi.mock("node:fs", async (importOriginal) => {
32
+ const actual = await importOriginal<typeof import("node:fs")>();
33
+ fsActualHolder.fs = actual;
34
+ return { ...actual, openSync: openSyncMock };
35
+ });
36
+ vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
37
+
38
+ import { RecordStore } from "../record-store.ts";
39
+
40
+ let tmpDir = "";
41
+
42
+ beforeEach(() => {
43
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "rs-revive-"));
44
+ loggerMock.debug.mockClear();
45
+ loggerMock.warn.mockClear();
46
+ loggerMock.error.mockClear();
47
+ });
48
+
49
+ afterEach(() => {
50
+ fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
51
+ openSyncMock.mockReset();
52
+ });
53
+
54
+ /** 最小合法 session.jsonl:session header + subagent-identity + 完整 assistant 末行。 */
55
+ function writeOrphanSession(filePath: string, id: string): void {
56
+ const header = JSON.stringify({
57
+ type: "session", version: 3, id: "sess-uuid", timestamp: "2026-01-01T00:00:00.000Z", cwd: "/tmp",
58
+ });
59
+ const identityEntry = JSON.stringify({
60
+ type: "custom", id: "id-1", parentId: null,
61
+ timestamp: "2026-01-01T00:00:01.000Z", customType: "subagent-identity",
62
+ data: { id, agent: "worker", mode: "background", task: "orphan revive", startedAt: 1000, rootSessionId: "sess-orphan" },
63
+ });
64
+ const assistantMsg = JSON.stringify({
65
+ type: "message", id: "msg-1", parentId: "id-1",
66
+ timestamp: "2026-01-01T00:00:02.000Z",
67
+ message: {
68
+ role: "assistant",
69
+ content: [{ type: "text", text: "result" }],
70
+ usage: { input: 10, output: 20, cacheRead: 0, cacheWrite: 0, totalTokens: 30, cost: { total: 0 } },
71
+ stopReason: "stop", timestamp: 2000,
72
+ },
73
+ });
74
+ fs.writeFileSync(filePath, `${header}\n${identityEntry}\n${assistantMsg}\n`, "utf-8");
75
+ }
76
+
77
+ function makeStore(): { store: RecordStore; appended: Array<{ customType: string; data: Record<string, unknown> }> } {
78
+ const appended: Array<{ customType: string; data: Record<string, unknown> }> = [];
79
+ const store = new RecordStore(tmpDir, undefined, {
80
+ appendEntry: (customType: string, data: unknown) => {
81
+ appended.push({ customType, data: data as Record<string, unknown> });
82
+ },
83
+ } as never);
84
+ return { store, appended };
85
+ }
86
+
87
+ describe("[PS-10] revive() 复位 orphanJudged(IO 恢复后重开可重判)", () => {
88
+ it("IO 失败落 resumable → revive 后 IO 恢复,再次扫描重新判定收敛 closed+gc", () => {
89
+ const sessionFile = path.join(tmpDir, "orphan-revive.jsonl");
90
+ writeOrphanSession(sessionFile, "sa-revive-1");
91
+ const realOpenSync = fsActualHolder.fs!.openSync;
92
+
93
+ // 目标路径读模式 open 的放行计数:阶段 1 只放行 1 次(扫描侧 readIdentityHeader),
94
+ // 判定侧 readLastJsonlLine 的 open 注入 EACCES(IO 暂时失败的保守形态)。
95
+ let allowReads = Infinity;
96
+ openSyncMock.mockImplementation((p: unknown, flags: unknown) => {
97
+ if (p === sessionFile && typeof flags === "string" && flags.includes("r")) {
98
+ if (allowReads <= 0) {
99
+ const err = new Error(`EACCES: permission denied, open '${sessionFile}'`) as NodeJS.ErrnoException;
100
+ err.code = "EACCES";
101
+ throw err;
102
+ }
103
+ allowReads--;
104
+ }
105
+ return realOpenSync(p as Parameters<typeof realOpenSync>[0], flags as Parameters<typeof realOpenSync>[1]);
106
+ });
107
+
108
+ // ── 阶段 1:IO 失败 → 保守落 resumable(注释承诺的形态)──
109
+ allowReads = 1;
110
+ const { store, appended } = makeStore();
111
+ store.recoverOrphanRecords("sess-orphan");
112
+
113
+ expect(appended).toHaveLength(1);
114
+ expect(appended[0]?.customType).toBe("subagent-record");
115
+ expect(appended[0]?.data.status).toBe("running");
116
+ expect(appended[0]?.data.resumable).toBe(true);
117
+ expect(fs.existsSync(`${sessionFile}.finalized`)).toBe(false); // 保守分支无 sidecar 锚
118
+
119
+ // ── 阶段 2:IO 恢复 + /new 复活(revive 复位 orphanJudged)→ 重判收敛终态 ──
120
+ allowReads = Infinity;
121
+ store.revive();
122
+ store.recoverOrphanRecords("sess-orphan");
123
+
124
+ expect(appended).toHaveLength(2); // 未修复时 orphanJudged 残留 → 零新 entry,此处红
125
+ const rejudged = appended[1];
126
+ expect(rejudged?.data.status).toBe("closed");
127
+ expect(rejudged?.data.closedReason).toBe("gc");
128
+ expect(rejudged?.data.resumable).toBeUndefined(); // 不再停留 resumable
129
+ expect(fs.existsSync(`${sessionFile}.finalized`)).toBe(true); // 终态防重锚落盘
130
+ });
131
+
132
+ it("未 revive 时重判资格保持(防重缓存语义不回归):重复 recover 零新 entry", () => {
133
+ const sessionFile = path.join(tmpDir, "orphan-norevive.jsonl");
134
+ writeOrphanSession(sessionFile, "sa-revive-2");
135
+ const realOpenSync = fsActualHolder.fs!.openSync;
136
+ let allowReads = Infinity;
137
+ openSyncMock.mockImplementation((p: unknown, flags: unknown) => {
138
+ if (p === sessionFile && typeof flags === "string" && flags.includes("r")) {
139
+ if (allowReads <= 0) {
140
+ const err = new Error(`EACCES: permission denied, open '${sessionFile}'`) as NodeJS.ErrnoException;
141
+ err.code = "EACCES";
142
+ throw err;
143
+ }
144
+ allowReads--;
145
+ }
146
+ return realOpenSync(p as Parameters<typeof realOpenSync>[0], flags as Parameters<typeof realOpenSync>[1]);
147
+ });
148
+
149
+ allowReads = 1;
150
+ const { store, appended } = makeStore();
151
+ store.recoverOrphanRecords("sess-orphan");
152
+ expect(appended).toHaveLength(1);
153
+
154
+ // 同进程内不经历 revive(未重开):IO 已恢复也不重判——同 session 防重语义保持
155
+ allowReads = Infinity;
156
+ store.recoverOrphanRecords("sess-orphan");
157
+ expect(appended).toHaveLength(1);
158
+ });
159
+ });
@@ -17,6 +17,24 @@ import * as path from "node:path";
17
17
 
18
18
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
19
19
 
20
+ // [PS-14/T7③ / LC-9 等 T7 可观测性] Mock 共享 logger:sessions-index 写失败的
21
+ // warn 留痕可被断言(对齐 channel-registry-handshake.test.ts 模式)。
22
+ const { loggerMock } = vi.hoisted(() => ({
23
+ loggerMock: { debug: vi.fn(), warn: vi.fn(), error: vi.fn() },
24
+ }));
25
+ vi.mock("../../core/logger.ts", () => ({
26
+ getLogger: () => loggerMock,
27
+ }));
28
+
29
+ // [PS-14] saveIndex 可注入失败(默认 resolve,零影响既有用例;loadIndex/常量保留 actual)。
30
+ const { saveIndexMock } = vi.hoisted(() => ({
31
+ saveIndexMock: vi.fn(() => Promise.resolve()),
32
+ }));
33
+ vi.mock("../sessions-index.ts", async (importOriginal) => {
34
+ const actual = await importOriginal<typeof import("../sessions-index.ts")>();
35
+ return { ...actual, saveIndex: saveIndexMock };
36
+ });
37
+
20
38
  import { writeAliveMarker } from "../alive-store.ts";
21
39
  import { completeRecord, createRecord, tryTransition } from "../execution-record.ts";
22
40
  import { writeFinalized } from "../finalized-marker.ts";
@@ -34,8 +52,9 @@ function makeRecord(over: Partial<ExecutionRecord> = {}): ExecutionRecord {
34
52
  const base = createRecord("r1", {
35
53
  agent: "worker",
36
54
  model: "m",
37
- mode: "sync",
55
+ mode: "background",
38
56
  task: "t",
57
+ slug: "test",
39
58
  startedAt: 1000,
40
59
  rootSessionId: "sess-current",
41
60
  // 对齐生产 register 路径(subagent-service createRecord:one-shot 显式 false)
@@ -50,7 +69,7 @@ function makeRecord(over: Partial<ExecutionRecord> = {}): ExecutionRecord {
50
69
  */
51
70
  function writeSessionJsonl(
52
71
  filePath: string,
53
- identity: { id: string; agent: string; mode: "sync" | "background"; task: string; startedAt: number; rootSessionId?: string; parentRecordId?: string; depth?: number; lastTs?: number; chatMode?: boolean },
72
+ identity: { id: string; agent: string; mode: "background"; task: string; startedAt: number; rootSessionId?: string; parentRecordId?: string; depth?: number; lastTs?: number; chatMode?: boolean },
54
73
  assistantText = "result text",
55
74
  ): void {
56
75
  const lastTs = identity.lastTs ?? identity.startedAt + 1000;
@@ -106,6 +125,12 @@ describe("RecordStore", () => {
106
125
  afterEach(() => {
107
126
  // maxRetries:fire-and-forget 的 sessions-index 写可能与删除并发(ENOTEMPTY 竞态)
108
127
  fs.rmSync(rootDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
128
+ // [PS-14] 恢复 saveIndex 默认 resolve 实现 + 清 logger 断言(用例间隔离)
129
+ saveIndexMock.mockReset();
130
+ saveIndexMock.mockImplementation(() => Promise.resolve());
131
+ loggerMock.debug.mockClear();
132
+ loggerMock.warn.mockClear();
133
+ loggerMock.error.mockClear();
109
134
  });
110
135
 
111
136
  // ============================================================
@@ -114,7 +139,7 @@ describe("RecordStore", () => {
114
139
  describe("archive 立即移除", () => {
115
140
  it("archive 后 record 立即从内存移除(不再 linger)", () => {
116
141
  const store = new RecordStore(tmpDir);
117
- const r = makeRecord({ id: "sync-1", mode: "sync", status: "closed" });
142
+ const r = makeRecord({ id: "sync-1", mode: "background", status: "closed" });
118
143
  store.register(r);
119
144
  expect(store.getMutable("sync-1")).toBeDefined();
120
145
  store.archive(r);
@@ -988,7 +1013,7 @@ describe("RecordStore", () => {
988
1013
  // chatMode 必须显式在场(one-shot=false)——renderer isDone 判据依赖它。
989
1014
  expect(Object.keys(data).sort()).toEqual([
990
1015
  "agent", "chatMode", "depth", "displayItems", "eventLog",
991
- "id", "mode", "model", "rootSessionId", "round", "startedAt",
1016
+ "id", "mode", "model", "rootSessionId", "round", "slug", "startedAt",
992
1017
  "status", "task", "totalTokens", "turns", "v", "worktree",
993
1018
  ]);
994
1019
  expect(data).toMatchObject({
@@ -997,7 +1022,7 @@ describe("RecordStore", () => {
997
1022
  agent: "worker",
998
1023
  task: "t",
999
1024
  status: "running",
1000
- mode: "sync",
1025
+ mode: "background",
1001
1026
  startedAt: 1000,
1002
1027
  rootSessionId: "sess-current",
1003
1028
  chatMode: false,
@@ -1054,4 +1079,63 @@ describe("RecordStore", () => {
1054
1079
  expect(() => store.reportRecordTransition(r)).not.toThrow();
1055
1080
  });
1056
1081
  });
1082
+
1083
+ // ============================================================
1084
+ // [PS-14/T7③] sessions-index 写失败可观测(warn 而非仅 debug)
1085
+ // ============================================================
1086
+ // flushIndexAfterScan 触发链:collectRecords 全量扫描(首扫 dirStamp===null)→
1087
+ // scanFile 探测置 indexDirty → 扫描尾 flush(lastIndexWriteAt=0 天然过 60s 节流窗)
1088
+ // → saveIndex(mock reject)→ .catch 恢复 dirty + warn。
1089
+ describe("PS-14: sessions-index 写失败可观测", () => {
1090
+ it("saveIndex reject → warn(不再仅 debug),detail 含 dir 与 error", async () => {
1091
+ saveIndexMock.mockRejectedValueOnce(new Error("EACCES: permission denied"));
1092
+ writeSessionJsonl(path.join(tmpDir, "sa-ps14.jsonl"), {
1093
+ id: "ps14",
1094
+ agent: "worker",
1095
+ mode: "background",
1096
+ task: "t",
1097
+ startedAt: 1000,
1098
+ });
1099
+
1100
+ const store = new RecordStore(tmpDir);
1101
+ const records = store.collectRecords(100);
1102
+ expect(records).toHaveLength(1); // 扫描本身不受索引写失败影响
1103
+
1104
+ // fire-and-forget saveIndex:等 .catch 执行
1105
+ await vi.waitFor(() => {
1106
+ expect(loggerMock.warn).toHaveBeenCalledWith(
1107
+ expect.stringContaining("sessions-index write failed"),
1108
+ expect.objectContaining({
1109
+ detail: expect.objectContaining({
1110
+ error: expect.stringContaining("EACCES: permission denied"),
1111
+ }),
1112
+ }),
1113
+ );
1114
+ });
1115
+ // 升级语义:warn 而非 debug(debug 层不再承载该信号)
1116
+ const debugIndexMsgs = loggerMock.debug.mock.calls.filter((c) =>
1117
+ String(c[0]).includes("sessions-index write failed"),
1118
+ );
1119
+ expect(debugIndexMsgs).toHaveLength(0);
1120
+ });
1121
+
1122
+ it("saveIndex 成功 → 零 warn(正常路径不制造噪音)", async () => {
1123
+ writeSessionJsonl(path.join(tmpDir, "sa-ps14-ok.jsonl"), {
1124
+ id: "ps14ok",
1125
+ agent: "worker",
1126
+ mode: "background",
1127
+ task: "t",
1128
+ startedAt: 1000,
1129
+ });
1130
+
1131
+ const store = new RecordStore(tmpDir);
1132
+ store.collectRecords(100);
1133
+ await vi.waitFor(() => expect(saveIndexMock).toHaveBeenCalled());
1134
+
1135
+ const warnIndexMsgs = loggerMock.warn.mock.calls.filter((c) =>
1136
+ String(c[0]).includes("sessions-index write failed"),
1137
+ );
1138
+ expect(warnIndexMsgs).toHaveLength(0);
1139
+ });
1140
+ });
1057
1141
  });
@@ -26,7 +26,7 @@ beforeEach(() => {
26
26
  });
27
27
 
28
28
  afterEach(() => {
29
- fs.rmSync(tmpAgentDir, { recursive: true, force: true });
29
+ fs.rmSync(tmpAgentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
30
30
  });
31
31
 
32
32
  /** 写一个最小合法的 manifest 到指定 records 目录。 */
@@ -36,7 +36,7 @@ function writeManifest(recordsDir: string, id: string): string {
36
36
  id,
37
37
  rootSessionId: "sess-1",
38
38
  agentName: "worker",
39
- status: "completed",
39
+ status: "closed",
40
40
  createdAt: Date.now(),
41
41
  };
42
42
  const filePath = path.join(recordsDir, `${id}.json`);