@zhushanwen/subagent-core 0.2.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 (330) hide show
  1. package/README.md +114 -0
  2. package/dist/chunk-3VOERJPJ.js +22 -0
  3. package/dist/chunk-A4IVZWVX.js +31 -0
  4. package/dist/execution/engine/engines/zcode/constants.cjs +54 -0
  5. package/dist/execution/engine/engines/zcode/constants.d.cts +31 -0
  6. package/dist/execution/engine/engines/zcode/constants.d.ts +31 -0
  7. package/dist/execution/engine/engines/zcode/constants.js +22 -0
  8. package/dist/execution/engine/engines/zcode/reader.cjs +276 -0
  9. package/dist/execution/engine/engines/zcode/reader.d.cts +20 -0
  10. package/dist/execution/engine/engines/zcode/reader.d.ts +20 -0
  11. package/dist/execution/engine/engines/zcode/reader.js +239 -0
  12. package/dist/execution/engine/paths.cjs +55 -0
  13. package/dist/execution/engine/paths.d.cts +8 -0
  14. package/dist/execution/engine/paths.d.ts +8 -0
  15. package/dist/execution/engine/paths.js +26 -0
  16. package/dist/execution/relay-env.cjs +62 -0
  17. package/dist/execution/relay-env.d.cts +33 -0
  18. package/dist/execution/relay-env.d.ts +33 -0
  19. package/dist/execution/relay-env.js +20 -0
  20. package/dist/index.cjs +1852 -0
  21. package/dist/index.d.cts +1184 -0
  22. package/dist/index.d.ts +1184 -0
  23. package/dist/index.js +1810 -0
  24. package/dist/types-BxyAidGf.d.cts +875 -0
  25. package/dist/types-BxyAidGf.d.ts +875 -0
  26. package/package.json +99 -0
  27. package/src/__tests__/agent-opts-resolver-schema-prompt.test.ts +142 -0
  28. package/src/__tests__/append-system-prompt-assembly.test.ts +242 -0
  29. package/src/__tests__/builtin-workflows-structure.test.ts +180 -0
  30. package/src/__tests__/derive-closed-display-parity.test.ts +93 -0
  31. package/src/__tests__/fr4-get-state-handshake.test.ts +125 -0
  32. package/src/__tests__/fr8-orphan-recovery.test.ts +259 -0
  33. package/src/__tests__/m2-append-content-probe.test.ts +73 -0
  34. package/src/__tests__/manifest-store.test.ts +368 -0
  35. package/src/__tests__/record-store-cache.test.ts +335 -0
  36. package/src/__tests__/record-store-index.test.ts +560 -0
  37. package/src/__tests__/record-store-last-line.test.ts +129 -0
  38. package/src/__tests__/review-fix-loop-script.test.ts +288 -0
  39. package/src/__tests__/review-fix-loop-utils.test.ts +2377 -0
  40. package/src/__tests__/robustness-low-batch1.test.ts +286 -0
  41. package/src/__tests__/robustness-medium-batch1.test.ts +69 -0
  42. package/src/__tests__/robustness-medium-batch2.test.ts +65 -0
  43. package/src/__tests__/robustness-medium-batch3.test.ts +30 -0
  44. package/src/__tests__/robustness-medium-batch4.test.ts +358 -0
  45. package/src/__tests__/session-runner.test.ts +69 -0
  46. package/src/__tests__/smoke.test.ts +9 -0
  47. package/src/__tests__/stream-sink.test.ts +203 -0
  48. package/src/core/__tests__/host-services.test.ts +144 -0
  49. package/src/core/__tests__/logger.test.ts +78 -0
  50. package/src/core/__tests__/notify-ports.test.ts +104 -0
  51. package/src/core/host-services.ts +97 -0
  52. package/src/core/logger.ts +47 -0
  53. package/src/core/notify-ports.ts +126 -0
  54. package/src/execution/__tests__/__fixtures__/notifier-golden-snapshots.json +53 -0
  55. package/src/execution/__tests__/__fixtures__/truncline.snapshot.json +1 -0
  56. package/src/execution/__tests__/agent-result-mapper.test.ts +150 -0
  57. package/src/execution/__tests__/alive-store.test.ts +147 -0
  58. package/src/execution/__tests__/ask-user-transit-e2e.test.ts +490 -0
  59. package/src/execution/__tests__/channel-registry-handshake.test.ts +242 -0
  60. package/src/execution/__tests__/chat-engine-routing.test.ts +610 -0
  61. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +190 -0
  62. package/src/execution/__tests__/concurrency-pool.test.ts +250 -0
  63. package/src/execution/__tests__/config.test.ts +302 -0
  64. package/src/execution/__tests__/delivery-methods.test.ts +424 -0
  65. package/src/execution/__tests__/dialog-queue.test.ts +299 -0
  66. package/src/execution/__tests__/epipe-fallback.test.ts +241 -0
  67. package/src/execution/__tests__/execute-and-await-worktree.test.ts +243 -0
  68. package/src/execution/__tests__/execute-nesting.test.ts +323 -0
  69. package/src/execution/__tests__/execute-options-mapper.test.ts +200 -0
  70. package/src/execution/__tests__/execution-record.test.ts +1394 -0
  71. package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +171 -0
  72. package/src/execution/__tests__/finalize-record.test.ts +367 -0
  73. package/src/execution/__tests__/finalized-marker.test.ts +104 -0
  74. package/src/execution/__tests__/format-schema-instruction.test.ts +169 -0
  75. package/src/execution/__tests__/gc-timer.test.ts +184 -0
  76. package/src/execution/__tests__/get-record-for-action-restart.test.ts +254 -0
  77. package/src/execution/__tests__/helpers/mock-extension-api.ts +30 -0
  78. package/src/execution/__tests__/helpers/spawn-mock.ts +242 -0
  79. package/src/execution/__tests__/host-mode.test.ts +87 -0
  80. package/src/execution/__tests__/lifecycle-manager-lock.test.ts +211 -0
  81. package/src/execution/__tests__/lifecycle-manager.test.ts +383 -0
  82. package/src/execution/__tests__/lifecycle-predicates.test.ts +116 -0
  83. package/src/execution/__tests__/manifest-parentid.test.ts +117 -0
  84. package/src/execution/__tests__/model-resolver.test.ts +465 -0
  85. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +287 -0
  86. package/src/execution/__tests__/nested-visibility.test.ts +325 -0
  87. package/src/execution/__tests__/output-collector.test.ts +358 -0
  88. package/src/execution/__tests__/path-encoding.test.ts +104 -0
  89. package/src/execution/__tests__/pi-invocation.test.ts +134 -0
  90. package/src/execution/__tests__/record-store.test.ts +1057 -0
  91. package/src/execution/__tests__/records-cwd-isolation.test.ts +91 -0
  92. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +339 -0
  93. package/src/execution/__tests__/recursive-visibility-env.test.ts +273 -0
  94. package/src/execution/__tests__/relay-env.test.ts +42 -0
  95. package/src/execution/__tests__/resource-policy.test.ts +109 -0
  96. package/src/execution/__tests__/rpc-mode.test.ts +89 -0
  97. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +267 -0
  98. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +253 -0
  99. package/src/execution/__tests__/run-spawn-edges.test.ts +687 -0
  100. package/src/execution/__tests__/run-spawn-integration.test.ts +933 -0
  101. package/src/execution/__tests__/run-spawn-resume.test.ts +322 -0
  102. package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +196 -0
  103. package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +199 -0
  104. package/src/execution/__tests__/session-context-resolver.test.ts +167 -0
  105. package/src/execution/__tests__/session-file-gc.test.ts +293 -0
  106. package/src/execution/__tests__/session-reconstructor.test.ts +379 -0
  107. package/src/execution/__tests__/session-runner-epipe.test.ts +178 -0
  108. package/src/execution/__tests__/session-runner-schema-env.test.ts +350 -0
  109. package/src/execution/__tests__/spawn-args.test.ts +445 -0
  110. package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +189 -0
  111. package/src/execution/__tests__/spawn-event-adapter.test.ts +167 -0
  112. package/src/execution/__tests__/spawn-worktree-guidance.test.ts +207 -0
  113. package/src/execution/__tests__/spawned-children.test.ts +92 -0
  114. package/src/execution/__tests__/start-sync-model-guard.test.ts +150 -0
  115. package/src/execution/__tests__/stdin-writer.test.ts +462 -0
  116. package/src/execution/__tests__/stream-sink-retirement.test.ts +261 -0
  117. package/src/execution/__tests__/subagent-service-abort.test.ts +60 -0
  118. package/src/execution/__tests__/subagent-service-message-close.test.ts +629 -0
  119. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +180 -0
  120. package/src/execution/__tests__/subagent-service.test.ts +788 -0
  121. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +310 -0
  122. package/src/execution/__tests__/subprocess-agent-runner.test.ts +612 -0
  123. package/src/execution/__tests__/temp-prompt.test.ts +53 -0
  124. package/src/execution/__tests__/timeout-integration.test.ts +615 -0
  125. package/src/execution/__tests__/tombstone-store.test.ts +73 -0
  126. package/src/execution/__tests__/turn-limiter-semantics.test.ts +194 -0
  127. package/src/execution/__tests__/turn-limiter.test.ts +65 -0
  128. package/src/execution/__tests__/ui-channels.test.ts +187 -0
  129. package/src/execution/__tests__/ui-interaction-model.test.ts +67 -0
  130. package/src/execution/__tests__/ui-request-handler-factory.test.ts +369 -0
  131. package/src/execution/__tests__/ui-request-handler.test.ts +204 -0
  132. package/src/execution/__tests__/ui-request-observability.test.ts +113 -0
  133. package/src/execution/__tests__/ui-request-queue.test.ts +133 -0
  134. package/src/execution/__tests__/worktree-manager.test.ts +633 -0
  135. package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +229 -0
  136. package/src/execution/__tests__/worktree-reconcile.integration.test.ts +181 -0
  137. package/src/execution/__tests__/worktree-registry.test.ts +199 -0
  138. package/src/execution/agent-registry.ts +199 -0
  139. package/src/execution/agent-result-mapper.ts +90 -0
  140. package/src/execution/alive-store.ts +119 -0
  141. package/src/execution/argv-mirror.ts +113 -0
  142. package/src/execution/best-effort.ts +38 -0
  143. package/src/execution/channel-registry-access.ts +144 -0
  144. package/src/execution/concurrency-pool.ts +116 -0
  145. package/src/execution/config.ts +165 -0
  146. package/src/execution/dialog-queue.ts +330 -0
  147. package/src/execution/engine/__tests__/common/data-dir.test.ts +78 -0
  148. package/src/execution/engine/__tests__/common/errors.test.ts +132 -0
  149. package/src/execution/engine/__tests__/common/event-journal.test.ts +177 -0
  150. package/src/execution/engine/__tests__/common/kill-chain.test.ts +192 -0
  151. package/src/execution/engine/__tests__/common/nesting-guard.test.ts +81 -0
  152. package/src/execution/engine/__tests__/common/persona-router.test.ts +123 -0
  153. package/src/execution/engine/__tests__/common/pool-manager.test.ts +154 -0
  154. package/src/execution/engine/__tests__/common/schema-emulation.test.ts +128 -0
  155. package/src/execution/engine/__tests__/conformance/__fixtures__/pi-golden-events.json +28 -0
  156. package/src/execution/engine/__tests__/conformance/agent-event-invariants.ts +141 -0
  157. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +109 -0
  158. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +101 -0
  159. package/src/execution/engine/__tests__/conformance/contract.probe.test.ts +77 -0
  160. package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +104 -0
  161. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +201 -0
  162. package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +76 -0
  163. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +79 -0
  164. package/src/execution/engine/__tests__/engine-discovery.test.ts +87 -0
  165. package/src/execution/engine/__tests__/model-prompt.test.ts +197 -0
  166. package/src/execution/engine/__tests__/paths.test.ts +39 -0
  167. package/src/execution/engine/__tests__/registry.test.ts +120 -0
  168. package/src/execution/engine/__tests__/routing.test.ts +231 -0
  169. package/src/execution/engine/common/data-dir.ts +66 -0
  170. package/src/execution/engine/common/errors.ts +183 -0
  171. package/src/execution/engine/common/event-journal.ts +254 -0
  172. package/src/execution/engine/common/journal-replay.ts +62 -0
  173. package/src/execution/engine/common/kill-chain.ts +221 -0
  174. package/src/execution/engine/common/nesting-guard.ts +50 -0
  175. package/src/execution/engine/common/persona-router.ts +108 -0
  176. package/src/execution/engine/common/pool-manager.ts +226 -0
  177. package/src/execution/engine/common/schema-emulation.ts +189 -0
  178. package/src/execution/engine/common/session-view-projection.ts +51 -0
  179. package/src/execution/engine/engine-discovery.ts +65 -0
  180. package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +469 -0
  181. package/src/execution/engine/engines/pi/__tests__/reader.test.ts +155 -0
  182. package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +164 -0
  183. package/src/execution/engine/engines/pi/pi-engine.ts +415 -0
  184. package/src/execution/engine/engines/pi/reader.ts +48 -0
  185. package/src/execution/engine/engines/pi/registration.ts +35 -0
  186. package/src/execution/engine/engines/pi/task-spec-mapper.ts +100 -0
  187. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +39 -0
  188. package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +150 -0
  189. package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +246 -0
  190. package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +228 -0
  191. package/src/execution/engine/engines/zcode/__tests__/reader.test.ts +210 -0
  192. package/src/execution/engine/engines/zcode/__tests__/registration.test.ts +71 -0
  193. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +127 -0
  194. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +580 -0
  195. package/src/execution/engine/engines/zcode/constants.ts +43 -0
  196. package/src/execution/engine/engines/zcode/golden-sample.ts +39 -0
  197. package/src/execution/engine/engines/zcode/launcher.ts +161 -0
  198. package/src/execution/engine/engines/zcode/parser.ts +436 -0
  199. package/src/execution/engine/engines/zcode/preparer.ts +363 -0
  200. package/src/execution/engine/engines/zcode/reader.ts +381 -0
  201. package/src/execution/engine/engines/zcode/registration.ts +37 -0
  202. package/src/execution/engine/engines/zcode/zcode-engine.ts +658 -0
  203. package/src/execution/engine/host-task-spec.ts +47 -0
  204. package/src/execution/engine/model-prompt.ts +158 -0
  205. package/src/execution/engine/paths.ts +42 -0
  206. package/src/execution/engine/port.ts +153 -0
  207. package/src/execution/engine/registry.ts +133 -0
  208. package/src/execution/engine/routing.ts +218 -0
  209. package/src/execution/engine/types.ts +309 -0
  210. package/src/execution/execute-options-mapper.ts +115 -0
  211. package/src/execution/execution-record.ts +984 -0
  212. package/src/execution/finalize-record.ts +254 -0
  213. package/src/execution/finalized-marker.ts +70 -0
  214. package/src/execution/get-state-handshake.ts +104 -0
  215. package/src/execution/host-mode.ts +56 -0
  216. package/src/execution/idle-gc.ts +47 -0
  217. package/src/execution/lifecycle-manager.ts +513 -0
  218. package/src/execution/lifecycle-predicates.ts +65 -0
  219. package/src/execution/manifest-store.ts +256 -0
  220. package/src/execution/model-config-service.ts +250 -0
  221. package/src/execution/model-resolver.ts +248 -0
  222. package/src/execution/notifier.ts +372 -0
  223. package/src/execution/notify-ledger.ts +580 -0
  224. package/src/execution/output-collector.ts +228 -0
  225. package/src/execution/path-encoding.ts +60 -0
  226. package/src/execution/pi-invocation.ts +120 -0
  227. package/src/execution/record-entry.ts +132 -0
  228. package/src/execution/record-store.ts +1265 -0
  229. package/src/execution/relay-env.ts +37 -0
  230. package/src/execution/session-context-resolver.ts +64 -0
  231. package/src/execution/session-file-gc.ts +120 -0
  232. package/src/execution/session-pending.ts +170 -0
  233. package/src/execution/session-reconstructor.ts +678 -0
  234. package/src/execution/session-runner.ts +1789 -0
  235. package/src/execution/sessions-index.ts +304 -0
  236. package/src/execution/spawn-event-adapter.ts +363 -0
  237. package/src/execution/stdin-writer.ts +198 -0
  238. package/src/execution/stream-sink.ts +126 -0
  239. package/src/execution/subagent-service.ts +2268 -0
  240. package/src/execution/subprocess-agent-runner.ts +317 -0
  241. package/src/execution/temp-prompt.ts +62 -0
  242. package/src/execution/tombstone-store.ts +72 -0
  243. package/src/execution/turn-limiter.ts +102 -0
  244. package/src/execution/types.ts +1023 -0
  245. package/src/execution/ui-channels.ts +216 -0
  246. package/src/execution/ui-interaction-model.ts +48 -0
  247. package/src/execution/ui-request-handler-factory.ts +243 -0
  248. package/src/execution/ui-request-observability.ts +81 -0
  249. package/src/execution/ui-request-queue.ts +184 -0
  250. package/src/execution/worktree-manager.ts +688 -0
  251. package/src/execution/worktree-registry.ts +279 -0
  252. package/src/index.ts +87 -0
  253. package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +349 -0
  254. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +202 -0
  255. package/src/orchestration/__tests__/agent-call-stream.test.ts +152 -0
  256. package/src/orchestration/__tests__/args-validator.test.ts +143 -0
  257. package/src/orchestration/__tests__/config-loader.test.ts +503 -0
  258. package/src/orchestration/__tests__/error-recovery-handlers.test.ts +809 -0
  259. package/src/orchestration/__tests__/error-recovery-postmessage-defense.test.ts +404 -0
  260. package/src/orchestration/__tests__/error-recovery-serialize-failed-result.test.ts +56 -0
  261. package/src/orchestration/__tests__/error-recovery-workflow-call.test.ts +166 -0
  262. package/src/orchestration/__tests__/execute-agent-call.test.ts +451 -0
  263. package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +600 -0
  264. package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +96 -0
  265. package/src/orchestration/__tests__/lifecycle.test.ts +659 -0
  266. package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +95 -0
  267. package/src/orchestration/__tests__/review-fix-loop-scriptpath-failfast.test.ts +183 -0
  268. package/src/orchestration/__tests__/script-lint.test.ts +831 -0
  269. package/src/orchestration/__tests__/skill-discovery.test.ts +210 -0
  270. package/src/orchestration/__tests__/test-mocks.ts +197 -0
  271. package/src/orchestration/__tests__/worker-exit-without-result.test.ts +368 -0
  272. package/src/orchestration/__tests__/worker-host.test.ts +120 -0
  273. package/src/orchestration/__tests__/worker-returnmeta-passthrough.test.ts +164 -0
  274. package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +563 -0
  275. package/src/orchestration/__tests__/worker-script-builder.test.ts +309 -0
  276. package/src/orchestration/__tests__/worker-script-template-snapshot.test.ts +129 -0
  277. package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +317 -0
  278. package/src/orchestration/__tests__/workflow-script-lint-memo.test.ts +110 -0
  279. package/src/orchestration/agent-opts-resolver.ts +174 -0
  280. package/src/orchestration/args-validator.ts +127 -0
  281. package/src/orchestration/config-loader.ts +316 -0
  282. package/src/orchestration/error-recovery.ts +1018 -0
  283. package/src/orchestration/execute-agent-call.ts +256 -0
  284. package/src/orchestration/launcher.ts +455 -0
  285. package/src/orchestration/lifecycle.ts +399 -0
  286. package/src/orchestration/models/__tests__/budget.test.ts +307 -0
  287. package/src/orchestration/models/agent-call.ts +83 -0
  288. package/src/orchestration/models/budget.ts +118 -0
  289. package/src/orchestration/models/ports.ts +164 -0
  290. package/src/orchestration/models/run-runtime.ts +104 -0
  291. package/src/orchestration/models/run-spec.ts +83 -0
  292. package/src/orchestration/models/run-state.ts +44 -0
  293. package/src/orchestration/models/trace.ts +183 -0
  294. package/src/orchestration/models/types.ts +301 -0
  295. package/src/orchestration/models/workflow-run.ts +254 -0
  296. package/src/orchestration/models/workflow-script-registry.ts +35 -0
  297. package/src/orchestration/models/workflow-script.ts +117 -0
  298. package/src/orchestration/script-lint.ts +766 -0
  299. package/src/orchestration/skill-discovery.ts +135 -0
  300. package/src/orchestration/worker-handle.ts +115 -0
  301. package/src/orchestration/worker-host.ts +98 -0
  302. package/src/orchestration/worker-script-builder.ts +421 -0
  303. package/src/orchestration/workflow-files.ts +85 -0
  304. package/src/orchestration/workflow-script-registry-impl.ts +133 -0
  305. package/src/shared/__tests__/agent-ref.test.ts +34 -0
  306. package/src/shared/__tests__/meta-parser.test.ts +304 -0
  307. package/src/shared/__tests__/model-ref.test.ts +306 -0
  308. package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +288 -0
  309. package/src/shared/__tests__/resource-discovery.test.ts +749 -0
  310. package/src/shared/__tests__/resource-meta.test.ts +51 -0
  311. package/src/shared/__tests__/schema-jsonify.test.ts +81 -0
  312. package/src/shared/__tests__/timer-delay.test.ts +61 -0
  313. package/src/shared/agent-event.ts +13 -0
  314. package/src/shared/agent-ref.ts +57 -0
  315. package/src/shared/meta-parser.ts +261 -0
  316. package/src/shared/model-ref.ts +286 -0
  317. package/src/shared/resource-discovery.ts +835 -0
  318. package/src/shared/resource-meta.ts +65 -0
  319. package/src/shared/schema-env.ts +44 -0
  320. package/src/shared/schema-jsonify.ts +58 -0
  321. package/src/shared/timer-delay.ts +54 -0
  322. package/src/shared/xml-injection.ts +35 -0
  323. package/workflows/README.md +81 -0
  324. package/workflows/_shared/agent-refs.cjs +40 -0
  325. package/workflows/chain.js +137 -0
  326. package/workflows/map-reduce.js +180 -0
  327. package/workflows/parallel.js +154 -0
  328. package/workflows/review-fix-loop-utils.cjs +1542 -0
  329. package/workflows/review-fix-loop.js +1605 -0
  330. package/workflows/scatter-gather.js +184 -0
@@ -0,0 +1,254 @@
1
+ // src/execution/finalize-record.ts
2
+ //
3
+ // 时序收尾逻辑(从 subagent-service.ts 提取,降低主文件行数 < 1000 上限)。
4
+ //
5
+ // D-017 时序:collectPatch → completeRecord → archive → cleanup(finalized+worktree+
6
+ // aliveMarker+pending注销) → manifest(最后 best-effort)。
7
+ //
8
+ // [Critical #1 / PR #85] cleanup 全部在 manifest 写之前执行——manifest 是诊断辅助
9
+ //(orphan recovery),写失败仅记录不阻断。旧实现 Step 2.5 throw 会跳过 Step 3 cleanup,
10
+ // 导致磁盘满/权限错时 worktree 泄漏 + finalized marker 不写 + alive marker 残留 +
11
+ // pending 记账错乱。现 manifest 写移到 Step 4(最后),best-effort(console.error +
12
+ // appendEntry,不 throw)。
13
+ //
14
+ // B9 兜底:completeRecord/archive 抛错→后续 cleanup/manifest 仍执行。
15
+
16
+ import * as fs from "node:fs";
17
+ import * as path from "node:path";
18
+
19
+ import { getLogger } from "../core/logger.ts";
20
+
21
+ import { removeAliveMarker } from "./alive-store.ts";
22
+ import { bestEffort } from "./best-effort.ts";
23
+ import { completeRecord } from "./execution-record.ts";
24
+ import { writeFinalized } from "./finalized-marker.ts";
25
+ import type { ManifestStore } from "./manifest-store.ts";
26
+ import type { ModelConfigService } from "./model-config-service.ts";
27
+ import { getSubagentSessionDir } from "./path-encoding.ts";
28
+ import type { RecordStore } from "./record-store.ts";
29
+ import { writeCancelledTombstone } from "./tombstone-store.ts";
30
+ import type { AgentResult, ClosedReason, ExecutionRecord } from "./types.ts";
31
+ import type { WorktreeManager } from "./worktree-manager.ts";
32
+
33
+ const logger = getLogger("subagents");
34
+
35
+ /** doFinalizeRecord 的依赖(从 SubagentService 注入,避免 this 绑定 + 解耦可测试)。 */
36
+ export interface FinalizeDeps {
37
+ manifestStore: ManifestStore;
38
+ worktreeManager: WorktreeManager;
39
+ store: RecordStore;
40
+ modelService: ModelConfigService;
41
+ /** Pi ExtensionAPI(仅用 appendEntry 记录 manifest 写失败事件)。null 在 dispose 后。 */
42
+ pi: { appendEntry?: (type: string, data: unknown) => void } | null;
43
+ /** pending-notifications 终态注销(绑定 pi.events.emit,由调用方闭包提供)。 */
44
+ emitUnregister(id: string, status: string): void;
45
+ // [review 修复] 已删除 redeliverPending 回调(MF-1 消费确认制补投):pendingMessages
46
+ // 三段消费链随 deliverToRunning 一并移除(无生产调用方,死机制)。
47
+ }
48
+
49
+ /**
50
+ * 时序收尾(D-017)。步骤 0→4 全部 best-effort 互不阻断(除 manifest 外都幂等)。
51
+ *
52
+ * [Critical #1] Step 3 cleanup 全部在 Step 4 manifest 之前——manifest 写失败仅 console.error +
53
+ * appendEntry,不 throw 不跳过 cleanup。task/slug/model 从 ExecutionRecord 抓取(配合 ManifestRecord
54
+ * 补字段),manifestToSubagent 投影真实值而非硬编码空串。
55
+ */
56
+ export async function doFinalizeRecord(
57
+ deps: FinalizeDeps,
58
+ record: ExecutionRecord,
59
+ result: AgentResult,
60
+ status: "closed",
61
+ closedReason?: ClosedReason,
62
+ ): Promise<void> {
63
+ // ── Step 0: collectPatch(best-effort)──
64
+ // [MF#3] patchFile 写到 worktree 之外(sessionsDir/<branch>.patch),避免被 cleanup 删除;
65
+ // 路径回填 record.patchFile,供调用方(tool result / /subagents list)应用。
66
+ if (record.worktreeHandle) {
67
+ try {
68
+ const sessionsDir = getSubagentSessionDir(
69
+ deps.modelService.getAgentDir(),
70
+ record.worktreeHandle.mainCwd,
71
+ );
72
+ fs.mkdirSync(sessionsDir, { recursive: true });
73
+ const patchFile = path.join(sessionsDir, `${record.worktreeHandle.branch}.patch`);
74
+ const patch = await deps.worktreeManager.collectPatch(record.worktreeHandle, patchFile);
75
+ if (patch.written) record.patchFile = patchFile;
76
+ } catch (pe: unknown) {
77
+ bestEffort(pe, "collectPatch (finalizeRecord Step0)");
78
+ }
79
+ }
80
+
81
+ // ── Step 1: completeRecord(B9: 抛错→后续仍执行)──
82
+ try {
83
+ completeRecord(record, result, status, closedReason);
84
+ } catch (err) {
85
+ bestEffort(err, "completeRecord (finalizeRecord B9)", "error");
86
+ }
87
+
88
+ // ── Step 2: archive(B9: 抛错→后续仍执行)──
89
+ try {
90
+ deps.store.archive(record);
91
+ } catch (err) {
92
+ bestEffort(err, "store.archive (finalizeRecord B9)", "error");
93
+ }
94
+
95
+ // ── Step 3: finalized + cleanup + aliveMarker + pending注销(全部先执行,幂等)──
96
+ // [Critical] 清理必须在 manifest 写入之前:worktree cleanup / finalized marker / aliveMarker
97
+ // 都是幂等且不可跳过的副作用。绝不能因 manifest 写失败而跳过 worktree cleanup
98
+ // (否则 worktree 泄漏)。各件独立 try/catch,互不阻断。
99
+ if (record.sessionFile) {
100
+ try {
101
+ // MF-1 fix / v4 B-1: cancelled(closedReason='cancelled')写 tombstone 而非 finalized,防重建丢失 cancelled
102
+ if (closedReason === "cancelled") {
103
+ writeCancelledTombstone(record.sessionFile, {
104
+ id: record.id,
105
+ status: "cancelled",
106
+ agent: record.agent,
107
+ startedAt: record.startedAt,
108
+ endedAt: record.endedAt ?? Date.now(),
109
+ });
110
+ } else {
111
+ // [v8.5 A2] 真实 reason 写入 sidecar 内容(旧格式是空文件):磁盘重建时用它
112
+ // 还原 closedReason,不再一律硬编码 gc。cancelled 之外的全部原因(gc /
113
+ // user-close / parent-*)都经此路径持久化。
114
+ writeFinalized(record.sessionFile, closedReason);
115
+ }
116
+ } catch (err) {
117
+ bestEffort(err, "writeFinalized/tombstone (finalizeRecord Step3)");
118
+ }
119
+ }
120
+ if (record.worktreeHandle) {
121
+ try {
122
+ await deps.worktreeManager.cleanup(record.worktreeHandle);
123
+ } catch (err) {
124
+ bestEffort(err, "worktree cleanup (finalizeRecord Step3)");
125
+ }
126
+ }
127
+ if (record.sessionFile) {
128
+ try {
129
+ removeAliveMarker(record.sessionFile);
130
+ } catch (err) {
131
+ bestEffort(err, "removeAliveMarker (finalizeRecord Step3)");
132
+ }
133
+ }
134
+
135
+ // pending-notifications:终态注销(只记 registry 状态,通知由 BgNotifier 发)
136
+ deps.emitUnregister(record.id, status);
137
+
138
+ // ── Step 4 (last): manifest 持久化(best-effort,不阻断、不 throw)──
139
+ // [Critical #1] manifest 是诊断辅助(orphan recovery),不是正确性依赖。写失败时
140
+ // 仅记录(console.error + appendEntry),绝不让 manifest 写失败跳过上面的 worktree
141
+ // cleanup 或抛出打断 finalize 链。旧实现 Step 2.5 throw 会跳过 Step 3 cleanup。
142
+ // task/slug/model 从 ExecutionRecord 抓取(配合 ManifestRecord 补字段),
143
+ // manifestToSubagent 投影时用真实值而非硬编码空串。
144
+ try {
145
+ await deps.manifestStore.writeManifest({
146
+ id: record.id,
147
+ rootSessionId: record.rootSessionId ?? "",
148
+ parentRecordId: record.parentRecordId,
149
+ agentName: record.agent,
150
+ // v4 B-1: manifest status 统一为 closed(cancelled 折入 closed,区分靠 tombstone sidecar)
151
+ status: "closed",
152
+ createdAt: record.startedAt,
153
+ completedAt: record.endedAt ?? Date.now(),
154
+ sessionFile: record.sessionFile,
155
+ task: record.task,
156
+ slug: record.slug,
157
+ model: record.model,
158
+ });
159
+ } catch (err) {
160
+ const msg = err instanceof Error ? err.message : String(err);
161
+ logger.error(`[subagent] manifest 写入失败 (record=${record.id}): ${msg}`);
162
+ deps.pi?.appendEntry?.("subagent:manifest-write-failed", {
163
+ id: record.id,
164
+ error: msg,
165
+ });
166
+ }
167
+ }
168
+
169
+ /**
170
+ * 对话模式轮次完成收尾:record 进 idle 态(非终态化,等待续聊)。
171
+ *
172
+ * 与 doFinalizeRecord 的关键区别(M2-A idle 语义):
173
+ * - 不调 completeRecord(record 不冻结,保留 turns[] 等运行时状态供续聊累积)
174
+ * - 不调 store.archive(record 留内存,getMutable 可查、list 可见)
175
+ * - 不 cleanup worktree(保留对话模式工作目录)
176
+ * - 不写 manifest(idle 非终态,manifest 是终态诊断辅助)
177
+ * - 删 .alive marker(进程已 SIGTERM 回收,不再是活进程)
178
+ * - emitUnregister(进程已死,从 pending 活跃后代差集移除;record 留内存不 archive)
179
+ *
180
+ * MF-2:设 record.result = result.text(否则 notifier idle 回复正文恒为 "(empty)",
181
+ * G1/G2 多轮回复送达不成立)。失败轮次(result.success=false,MF-6 回退 idle 路径)
182
+ * 的 result.text 可能为空,用 result.error 兜底让 notify 可读。
183
+ *
184
+ * 状态:record.status = "idle"(覆盖 tryTransition 设的 done/failed),record.round += 1。
185
+ * 各步骤 best-effort 互不阻断(参照 doFinalizeRecord 的 bestEffort 用法)。
186
+ *
187
+ * @param deps 与 doFinalizeRecord 同源(从 SubagentService 注入)
188
+ * @param result 本轮 AgentResult(MF-2:result.text 写入 record.result 供 notifier idle 回复)
189
+ */
190
+ export async function doFinalizeRoundToIdle(
191
+ deps: FinalizeDeps,
192
+ record: ExecutionRecord,
193
+ result: AgentResult,
194
+ ): Promise<void> {
195
+ // MF-2:设 record.result 供 notifier idle 回复正文(否则恒 "(empty)",G1/G2 不成立)。
196
+ // MF-6 兜底:失败轮次(success=false)的 result.text 可能为空,用 error 让 notify 可读。
197
+ // [R2-1] 轮终写点恒写非空:one-shot 空文本成功完成(collectResult getFullText 返回 ""、
198
+ // success=true、真实可达,本写点被 subagent-service runAndFinalize 的成功分支共用)首轮
199
+ // result 前值 undefined,兜底补 "(empty)" 占位——措辞与 notifier buildLlmContent 的
200
+ // `record.result ?? "(empty)"` 兜底同款,通知文案逐字节不变([增量 G2] G4 取舍保持)。
201
+ // 轮终信号优先:record.result 非 undefined 是 renderer hasRunning 排除「轮终
202
+ // running-resumable」的判据(shared SubagentRecord.result 契约),保持 undefined 会让
203
+ // 完成注入后末位 turn 永久「工作中」。续轮(前值存在)沿用前值:one-shot 无增量语义,
204
+ // record.result = 该 subagent 最终输出。chatMode 空增量轮 → 固定占位
205
+ // "(no output this round)"(D5:增量语义下沿用旧 record.result = 上一轮增量,本轮通知
206
+ // 正文 = 上一轮内容,父 agent 误读为原样重复回复)。
207
+ let nextResult: string | undefined;
208
+ if (result.text) {
209
+ nextResult = result.text;
210
+ } else if (result.error) {
211
+ nextResult = `round did not complete: ${result.error}`;
212
+ } else if (record.chatMode) {
213
+ nextResult = "(no output this round)";
214
+ } else {
215
+ nextResult = record.result ?? "(empty)";
216
+ }
217
+ record.result = nextResult;
218
+
219
+ // 删 .alive marker(进程已 SIGTERM 回收)。
220
+ // sessionFile 窗口期可能 undefined(极少——对话模式轮次完成意味着 session 已跑过),
221
+ // 缺失时跳过但仍设内存 idle(重启后磁盘重建会落到 crashed,边界可接受)。
222
+ if (record.sessionFile) {
223
+ try {
224
+ removeAliveMarker(record.sessionFile);
225
+ } catch (err) {
226
+ bestEffort(err, "removeAliveMarker (doFinalizeRoundToIdle)");
227
+ }
228
+ }
229
+
230
+ // pending-notifications:进程已死,从活跃后代差集移除(record 留内存不 archive,
231
+ // v4 B-1:record 现为 running-resumable,但 pending 注册的是进程活跃性,进程死了需注销)。
232
+ deps.emitUnregister(record.id, "running");
233
+
234
+ // 状态机(v4 B-1):record 保持 running(旧 idle 折入 running,覆盖 tryTransition 设的 closed,
235
+ // 可冷路径 resume),轮次计数 +1。idleSince 时间戳独立保留供 GC 判据。
236
+ // [S10] closedReason 同步清除:调用方(runAndFinalize catch / MF-6 分支)先 tryTransition
237
+ // 设了 closed+closedReason 再回退 running——不清则 "gc"/"cancelled" 残留在 running record 上,
238
+ // 泄漏进 list 投影与后续 notify 载荷(toNotifyRecord 透传 record.closedReason),让一个
239
+ // 活跃 record 看起来像已被某原因关闭过。
240
+ record.status = "running";
241
+ record.closedReason = undefined;
242
+ record.round = (record.round ?? 0) + 1;
243
+ record.idleSince = Date.now();
244
+ // 执行态信号(residual-fixes):轮终回 running-resumable = 无活进程驱动(idle timer
245
+ // 回收/保活等待续聊),GUI 侧据此判 waiting(非 streaming)。冷路径续轮(进程启动)清除。
246
+ record.resumable = true;
247
+
248
+ // W16 [D4]:轮终回 running-resumable 是类外状态写点(record 留内存不走 archive),
249
+ // 显式上报迁移——entry 携带新 round 与本轮 result,pi 文件的重建源不滞后。
250
+ deps.store.reportRecordTransition(record);
251
+
252
+ // [review 修复] 已删除残留 pendingMessages 的 redeliverPending 补投段(MF-1 消费
253
+ // 确认安全网):三段消费链随 deliverToRunning 一并移除,本段不可达。
254
+ }
@@ -0,0 +1,70 @@
1
+ // src/runtime/execution/finalized-marker.ts
2
+ //
3
+ // Finalized 状态的 sidecar 持久化。
4
+ //
5
+ // 背景:subagent 执行完成(done/failed)后,session.jsonl 的最终写入可能因进程
6
+ // 异常终止而丢失。`.finalized` sidecar 标记该 session 已正常结束,collectRecords
7
+ // 重建时可用它区分「正常结束但文件截断」与「执行中断」。
8
+ //
9
+ // 设计:对称 cancelled sidecar(tombstone-store.ts)。`.finalized` 内容为可选的
10
+ // 关闭原因字符串(v8.5 起 writeFinalized 第二参数写入),旧格式为空文件——空内容
11
+ // 表示死因不可考,磁盘重建兜底 closedReason=disconnected(不再误导为 gc)。
12
+ // 与 `.cancelled` 互斥——一个 session 要么 finalized 要么 cancelled,不可能两者兼有。
13
+ //
14
+ // best-effort:写 IO 错静默,不阻断主流程。
15
+
16
+ import * as fs from "node:fs";
17
+
18
+ // ============================================================
19
+ // 公开函数
20
+ // ============================================================
21
+
22
+ /**
23
+ * 在 session.jsonl 旁写 `.finalized` sidecar。
24
+ * best-effort:任何 I/O 错误静默(finalize 标记是次要信号,status 已在内存 record 上设好)。
25
+ *
26
+ * 与 `.cancelled` 互斥(BC-4):写前删除 `.cancelled`(如存在),确保不会两者共存。
27
+ *
28
+ * @param sessionFile session.jsonl 绝对路径
29
+ * @param reason 可选的 L2 关闭原因写入 sidecar 内容(v8.5 起):磁盘重建时用它区分
30
+ * 真实死因(user-close / parent-* 等),替代旧的「一律 gc」误导标签。传
31
+ * undefined = 空文件(旧格式兼容:未知死因,重建兜底 disconnected)。
32
+ */
33
+ export function writeFinalized(sessionFile: string, reason?: string): void {
34
+ try {
35
+ // BC-4 互斥:清理可能存在的 .cancelled。force:true 静默 ENOENT——
36
+ // 未 cancel 过的 session(done/failed/aborted)本就无 .cancelled,属正常路径。
37
+ // 此前用 unlinkSync+bestEffort 记 console.debug,取消嵌套链条时每层 ENOENT 刷屏。
38
+ fs.rmSync(`${sessionFile}.cancelled`, { force: true });
39
+ fs.writeFileSync(`${sessionFile}.finalized`, reason ?? "", "utf-8");
40
+ } catch (_e) {
41
+ void _e; // 静默:写失败不阻断 finalize 主流程。
42
+ }
43
+ }
44
+
45
+ /**
46
+ * 读 session.jsonl 旁的 `.finalized` sidecar 内容携带的关闭原因。
47
+ * 返回 trim 后的字符串:sidecar 存在时可能为空串(旧格式空文件 / 未携 reason)。
48
+ * 返回 undefined:sidecar 不存在 / 读取失败(与空串区分「没正常结束过」)。
49
+ */
50
+ export function readFinalizedReason(sessionFile: string): string | undefined {
51
+ try {
52
+ return fs.readFileSync(`${sessionFile}.finalized`, "utf-8").trim();
53
+ } catch {
54
+ return undefined;
55
+ }
56
+ }
57
+
58
+ /**
59
+ * 读 session.jsonl 旁的 `.finalized` sidecar 是否存在。
60
+ * 返回 true:sidecar 存在(内容不校验,存在性即信号)。
61
+ * 返回 false:sidecar 不存在 / 读取失败。
62
+ */
63
+ export function readFinalized(sessionFile: string): boolean {
64
+ try {
65
+ fs.accessSync(`${sessionFile}.finalized`);
66
+ return true;
67
+ } catch {
68
+ return false;
69
+ }
70
+ }
@@ -0,0 +1,104 @@
1
+ // src/execution/get-state-handshake.ts
2
+ //
3
+ // FR-4: get_state RPC 握手逻辑。
4
+ //
5
+ // 从 session-runner.ts 提取(保持文件 < 1000 行)。职责单一:通过 get_state RPC
6
+ // 查询子进程 sessionFile/sessionId,带超时重试。session-runner spawn 后无条件调用。
7
+ //
8
+ // 设计要点:
9
+ // - 重试节奏:单次超时 GET_STATE_TIMEOUT_MS(2s)后,等 GET_STATE_RETRY_INTERVAL_MS(500ms)
10
+ // 再发起下一次 get_state,最多 GET_STATE_MAX_RETRIES(3)次。修复点:旧实现超时后
11
+ // 立即递归 tryOnce(),GET_STATE_RETRY_INTERVAL_MS 声明了却从未使用(eslint error 阻断),
12
+ // 现在让常量名与行为一致——重试前真的等间隔。
13
+ // - 加速路径:sessionFile 一旦拿到立即 resolve(不等剩余重试)。
14
+ // - 全部超时:resolve 空对象(调用方走兜底查找)。
15
+
16
+ import type { ChildProcess } from "node:child_process";
17
+
18
+ import { sendGetStateCommand } from "./stdin-writer.ts";
19
+
20
+ /** FR-4: get_state RPC 握手最大重试次数。 */
21
+ const GET_STATE_MAX_RETRIES = 3;
22
+ /** FR-4: get_state RPC 握手重试间隔(ms)——单次超时后等待此间隔再重试。 */
23
+ const GET_STATE_RETRY_INTERVAL_MS = 500;
24
+ /** FR-4: get_state RPC 握手单次超时(ms)。 */
25
+ const GET_STATE_TIMEOUT_MS = 2000;
26
+
27
+ /** get_state 握手结果。 */
28
+ export interface GetStateResult {
29
+ sessionFile?: string;
30
+ sessionId?: string;
31
+ }
32
+
33
+ /**
34
+ * FR-4: 通过 get_state RPC 查询子进程获取 sessionFile/sessionId。
35
+ *
36
+ * 当 stdout header 未获取到 sessionFile 时,尝试通过 get_state RPC 查询。
37
+ * 最多重试 GET_STATE_MAX_RETRIES 次,单次超时 GET_STATE_TIMEOUT_MS 后等待
38
+ * GET_STATE_RETRY_INTERVAL_MS 再发起下一次重试。
39
+ *
40
+ * @param child 子进程(stdin 写入 get_state 命令)
41
+ * @param addResponseListener 注册 response 监听器的函数(stdout pump 中调用)
42
+ * @returns 握手结果(可能为空——所有重试均超时/失败)
43
+ */
44
+ export function performGetStateHandshake(
45
+ child: ChildProcess,
46
+ addResponseListener: (id: string, resolver: (data: unknown) => void) => void,
47
+ ): Promise<GetStateResult> {
48
+ return new Promise<GetStateResult>((resolve) => {
49
+ const collected: GetStateResult = {};
50
+ let attempts = 0;
51
+ let resolved = false;
52
+
53
+ function tryOnce(): void {
54
+ if (resolved) return;
55
+ attempts++;
56
+ const reqId = sendGetStateCommand(child);
57
+
58
+ // [#15] 本次 tryOnce 私有的 timer(2s 超时 + 超时后派生的 retry)。
59
+ // 关键:response 回调通过闭包引用的是"本次 tryOnce 对应的 timer",而非某个
60
+ // 外层共享变量——即便后续 tryOnce(#2) 重新发起请求,旧 reqId 的迟到 response 回调
61
+ // 闭包仍指向它自己那次 tryOnce 的 timer,不会误清新 reqId 的 timer。retry 句柄也
62
+ // 一并捕获,response 到达时同步取消"已在排队但尚未触发的下一次重试"。
63
+ let pendingRetry: ReturnType<typeof setTimeout> | undefined;
64
+ const timer: ReturnType<typeof setTimeout> = setTimeout(() => {
65
+ pendingRetry = undefined;
66
+ // 单次超时:等待间隔后重试,或放弃
67
+ if (attempts < GET_STATE_MAX_RETRIES && !resolved) {
68
+ // [Bug fix] 旧实现直接 tryOnce() 立即重试,GET_STATE_RETRY_INTERVAL_MS 声明却
69
+ // 从未使用(eslint error 阻断 commit)。现在重试前等待间隔,让常量名与行为一致。
70
+ pendingRetry = setTimeout(() => tryOnce(), GET_STATE_RETRY_INTERVAL_MS);
71
+ pendingRetry.unref();
72
+ } else if (!resolved) {
73
+ resolved = true;
74
+ resolve(collected);
75
+ }
76
+ }, GET_STATE_TIMEOUT_MS);
77
+ timer.unref();
78
+
79
+ addResponseListener(reqId, (data: unknown) => {
80
+ if (resolved) return;
81
+ // [#15] 闭包清理本次 tryOnce 的 timer(2s 超时 + 排队中的 retry),不碰其他 reqId 的 timer。
82
+ clearTimeout(timer);
83
+ if (pendingRetry) clearTimeout(pendingRetry);
84
+ if (data && typeof data === "object") {
85
+ const d = data as Record<string, unknown>;
86
+ if (typeof d.sessionFile === "string" && d.sessionFile.length > 0) {
87
+ collected.sessionFile = d.sessionFile;
88
+ }
89
+ if (typeof d.sessionId === "string" && d.sessionId.length > 0) {
90
+ collected.sessionId = d.sessionId;
91
+ }
92
+ }
93
+ // sessionFile 已获取——立即 resolve(无需更多重试)
94
+ if (collected.sessionFile) {
95
+ resolved = true;
96
+ resolve(collected);
97
+ }
98
+ // 否则等待超时重试
99
+ });
100
+ }
101
+
102
+ tryOnce();
103
+ });
104
+ }
@@ -0,0 +1,56 @@
1
+ // src/execution/host-mode.ts
2
+ //
3
+ // 主进程运行模式分类工具。
4
+ //
5
+ // 将 Pi 的 ExtensionMode("tui"|"rpc"|"json"|"print")聚合为业务语义的
6
+ // HostMode("tui"|"gui"|"headless"),供 session-runner 的 W4 提示词守卫、
7
+ // handler 工厂分流、stdio 选择等消费点统一调用。
8
+ //
9
+ // 判定依据见 AGENTS.md「运行时环境区分」章节 +
10
+ // docs/extensions/pi-tui-development-guide.md 第四部分第 8 节。
11
+ // ExtensionMode 来自 Pi 源码 packages/coding-agent/src/core/extensions/types.ts:299
12
+ // (dist 中 core/extensions/types.d.ts:207)。
13
+ //
14
+ // 设计动机(.fix-plans/00-master-summary.md §一冲突 4):
15
+ // - 把散落多处的 `ctx.mode === "tui" || ctx.mode === "rpc"` 字面量比较
16
+ // 集中到单一修改点,未来 Pi 新增 mode 值时只改本文件。
17
+ // - 业务语义命名("gui"/"headless")比原始枚举值更清晰表达意图。
18
+
19
+ // ExtensionMode 在 Pi SDK 的 core/extensions/types.d.ts 中定义为
20
+ // `"tui" | "rpc" | "json" | "print"`(ExtensionContext.mode 的类型),但未从包根
21
+ // 导出。这里定义与 SDK 同构的本地别名(ctx.mode 的字面量联合),由同包其他模块复用,
22
+ // 避免依赖未导出的内部类型。
23
+ export type ExtensionMode = "tui" | "rpc" | "json" | "print";
24
+
25
+ /** 主进程运行模式分类。基于 ExtensionMode 聚合为业务语义。
26
+ * - "tui":纯 Pi TUI,ctx.ui.custom 可用,用户在终端交互
27
+ * - "gui":xyz-agent GUI,通过 rpc sidecar 通道与前端 Vue 组件交互
28
+ * - "headless":无交互 UI 通道(json/print 输出模式,或 mode 未穿透) */
29
+ export type HostMode = "tui" | "gui" | "headless";
30
+
31
+ /** 从 ExtensionContext.mode 解析主进程模式分类。
32
+ * - "tui" → tui(纯 Pi TUI,ctx.ui.custom 可用)
33
+ * - "rpc" → gui(xyz-agent GUI,sidecar 通道可用)
34
+ * - "json"/"print"/undefined → headless(无交互通道)
35
+ *
36
+ * undefined 归入 headless 是向后兼容:mode 未穿透 SessionRunnerContext 时
37
+ * 按「无 UI」保守处理,避免误注入依赖 UI 的逻辑。 */
38
+ export function resolveHostMode(mode: ExtensionMode | undefined): HostMode {
39
+ if (mode === "tui") return "tui";
40
+ if (mode === "rpc") return "gui";
41
+ return "headless"; // json/print/undefined
42
+ }
43
+
44
+ /** 主进程是否会响应子进程的 ask_user(UI 透传)。
45
+ * tui + gui 都会(冲突 3 裁决:TUI 必须注入 handler;GUI 透传所有 UI),
46
+ * headless 不会(无 UI 通道,注入 W4 提示词会误导 LLM)。 */
47
+ export function willRespondToAskUser(mode: ExtensionMode | undefined): boolean {
48
+ const host = resolveHostMode(mode);
49
+ return host === "tui" || host === "gui";
50
+ }
51
+
52
+ /** 主进程是否有交互 UI 通道(TUI 组件 / GUI sidecar)。
53
+ * 非 headless 即有。用于 stdio 选择等不区分 tui/gui 的决策点。 */
54
+ export function hasInteractiveUI(mode: ExtensionMode | undefined): boolean {
55
+ return resolveHostMode(mode) !== "headless";
56
+ }
@@ -0,0 +1,47 @@
1
+ /**
2
+ * SP-4 idle record GC:30 天 TTL 定时归档,防 idle record 永久驻留内存。
3
+ * 从 subagent-service.ts 抽出(文件超 max-lines;逻辑自包含:interval + TTL 扫描)。
4
+ * 启动幂等由调用方(service.startGcTimer)守卫;返回 stop 函数供 dispose 清理。
5
+ */
6
+ import { getLogger } from "../core/logger.ts";
7
+ import { bestEffort } from "./best-effort.ts";
8
+ import { isResumable } from "./lifecycle-predicates.ts";
9
+ import type { RecordStore } from "./record-store.ts";
10
+
11
+ const logger = getLogger("subagents");
12
+
13
+ /** GC 扫描间隔:1 小时。 */
14
+ // eslint-disable-next-line no-magic-numbers -- 60*60*1000 = 1h 的毫秒换算常数
15
+ const GC_INTERVAL_MS = 60 * 60 * 1000;
16
+ /** idle record TTL:30 天(超龄归档)。 */
17
+ // eslint-disable-next-line no-magic-numbers -- 30*24*60*60*1000 = 30d 的毫秒换算常数
18
+ const IDLE_TTL_MS = 30 * 24 * 60 * 60 * 1000;
19
+ /** 毫秒/天(GC 日志的 d 换算)。 */
20
+ // eslint-disable-next-line no-magic-numbers -- 24*60*60*1000 = 1d 的毫秒换算常数
21
+ const MS_PER_DAY = 24 * 60 * 60 * 1000;
22
+
23
+ /**
24
+ * 启动 idle record GC 定时器,返回 stop 函数(清理 interval;幂等)。
25
+ * 每个扫描周期:对 store 内全部 active record 中 resumable 且带 idleSince 的,
26
+ * 超过 IDLE_TTL_MS 的归档(archive 单条失败不阻断其余,bestEffort 留痕)。
27
+ */
28
+ export function startIdleGc(store: RecordStore): () => void {
29
+ const timer = setInterval(() => {
30
+ const now = Date.now();
31
+ for (const record of store.listAllActive()) {
32
+ if (isResumable(record) && record.idleSince) {
33
+ const age = now - record.idleSince;
34
+ if (age > IDLE_TTL_MS) {
35
+ logger.warn(`[subagents] GC: archiving idle record ${record.id} (idle for ${Math.round(age / MS_PER_DAY)}d)`);
36
+ try {
37
+ store.archive(record);
38
+ } catch (err) {
39
+ bestEffort(err, `GC archive record ${record.id}`);
40
+ }
41
+ }
42
+ }
43
+ }
44
+ }, GC_INTERVAL_MS);
45
+ timer.unref?.();
46
+ return () => clearInterval(timer);
47
+ }