@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,164 @@
1
+ // src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts
2
+ //
3
+ // AgentTaskSpec ↔ ExecuteOptions 映射测试(P1 验收 3:往返保真)。
4
+ // 覆盖:effort/thinkingLevel 双向映射、persona/skillPath 归并还原、
5
+ // conversation/idleTimeoutMs 透传、schemaEnv 派生逐字节等值(D-A6 bridge)、
6
+ // 运行期字段(signal/ctxModel/onComplete)不入任务声明。
7
+
8
+ import { describe, expect, it } from "vitest";
9
+
10
+ import type { ExecuteOptions } from "../../../../types.ts";
11
+ import type { AgentTaskSpec } from "../../../types.ts";
12
+ import { executeOptionsToTaskSpec, taskSpecToExecuteOptions } from "../task-spec-mapper.ts";
13
+
14
+ describe("task-spec-mapper(ExecuteOptions ↔ AgentTaskSpec 往返保真)", () => {
15
+ /** 全字段样例(含全部泛化点)。 */
16
+ function makeFullOpts(): ExecuteOptions {
17
+ return {
18
+ task: "review the diff",
19
+ slug: "review-diff",
20
+ agent: "/agents/reviewer.md",
21
+ model: "zai-coding-cn/glm-5.2",
22
+ thinkingLevel: "high",
23
+ skillPath: "/skills/code-review/SKILL.md",
24
+ appendSystemPrompt: ["extra system prompt line"],
25
+ schema: { type: "object", properties: { verdict: { type: "string" } }, required: ["verdict"] },
26
+ maxTurns: 20,
27
+ graceTurns: 3,
28
+ fork: true,
29
+ worktree: true,
30
+ cwd: "/repo/worktree-x",
31
+ conversation: true,
32
+ idleTimeoutMs: 600_000,
33
+ };
34
+ }
35
+
36
+ it("全字段往返恒等(P1 行为零变化的映射层证据)", () => {
37
+ const opts = makeFullOpts();
38
+ const round = taskSpecToExecuteOptions(executeOptionsToTaskSpec(opts));
39
+ // schemaEnv 是派生字段(派生正确性单独断言),其余逐字段恒等
40
+ const { schemaEnv, ...rest } = round;
41
+ expect(rest).toEqual(opts);
42
+ expect(schemaEnv).toBe(JSON.stringify(opts.schema));
43
+ });
44
+
45
+ it("effort ↔ thinkingLevel 双向映射(pi 恒等透传,含 7 档外的缺省)", () => {
46
+ for (const level of ["off", "low", "medium", "high", "max", undefined]) {
47
+ const opts: ExecuteOptions = { task: "t", slug: "s", thinkingLevel: level };
48
+ const spec = executeOptionsToTaskSpec(opts);
49
+ expect(spec.effort).toBe(level);
50
+ expect(taskSpecToExecuteOptions(spec).thinkingLevel).toBe(level);
51
+ }
52
+ });
53
+
54
+ it("persona 归并:skillPath + appendSystemPrompt 收拢进 persona", () => {
55
+ const spec = executeOptionsToTaskSpec({
56
+ task: "t",
57
+ slug: "s",
58
+ skillPath: "/skills/x/SKILL.md",
59
+ appendSystemPrompt: ["line1", "line2"],
60
+ });
61
+ expect(spec.persona).toEqual({ skillPath: "/skills/x/SKILL.md", appendSystemPrompt: ["line1", "line2"] });
62
+ // 还原
63
+ const back = taskSpecToExecuteOptions(spec);
64
+ expect(back.skillPath).toBe("/skills/x/SKILL.md");
65
+ expect(back.appendSystemPrompt).toEqual(["line1", "line2"]);
66
+ });
67
+
68
+ it("persona 缺省:无 skillPath/appendSystemPrompt 时 persona 不存在(无空对象噪声)", () => {
69
+ const spec = executeOptionsToTaskSpec({ task: "t", slug: "s" });
70
+ expect(spec.persona).toBeUndefined();
71
+ expect(taskSpecToExecuteOptions(spec).skillPath).toBeUndefined();
72
+ expect(taskSpecToExecuteOptions(spec).appendSystemPrompt).toBeUndefined();
73
+ });
74
+
75
+ it("persona 单字段归并:仅 skillPath(appendSystemPrompt 保持 undefined)", () => {
76
+ const spec = executeOptionsToTaskSpec({ task: "t", slug: "s", skillPath: "/s" });
77
+ expect(spec.persona).toEqual({ skillPath: "/s" });
78
+ const back = taskSpecToExecuteOptions(spec);
79
+ expect(back.skillPath).toBe("/s");
80
+ expect(back.appendSystemPrompt).toBeUndefined();
81
+ });
82
+
83
+ it("conversation / idleTimeoutMs 原名透传(interact 控制面的 task 标志)", () => {
84
+ const opts: ExecuteOptions = { task: "t", slug: "s", conversation: true, idleTimeoutMs: 12345 };
85
+ const spec = executeOptionsToTaskSpec(opts);
86
+ expect(spec.conversation).toBe(true);
87
+ expect(spec.idleTimeoutMs).toBe(12345);
88
+ const back = taskSpecToExecuteOptions(spec);
89
+ expect(back.conversation).toBe(true);
90
+ expect(back.idleTimeoutMs).toBe(12345);
91
+ });
92
+
93
+ it("schemaEnv 派生:与 agent-opts-resolver 同函数(stringifySchemaCached compact),逐字节等值", () => {
94
+ const schema = { type: "object", properties: { a: { type: "number" } } };
95
+ const spec = executeOptionsToTaskSpec({ task: "t", slug: "s", schema });
96
+ // 无 schema 时 schemaEnv 不设(BC-6:childEnv 不注入)
97
+ expect(taskSpecToExecuteOptions({ task: "t", slug: "s" }).schemaEnv).toBeUndefined();
98
+ // 有 schema 时派生 = JSON.stringify(schema)(resolver 的 compact 形态)
99
+ expect(taskSpecToExecuteOptions(spec).schemaEnv).toBe(JSON.stringify(schema));
100
+ // 原 schemaEnv(resolver 产物)经往返后逐字节保持
101
+ const optsWithEnv: ExecuteOptions = {
102
+ task: "t",
103
+ slug: "s",
104
+ schema,
105
+ schemaEnv: JSON.stringify(schema),
106
+ };
107
+ const round = taskSpecToExecuteOptions(executeOptionsToTaskSpec(optsWithEnv));
108
+ expect(round.schemaEnv).toBe(optsWithEnv.schemaEnv);
109
+ });
110
+
111
+ it("解耦形态(有 schemaEnv 无 schema,生产不可达):经 RunContext 兜底透传,派生优先", () => {
112
+ const spec = executeOptionsToTaskSpec({ task: "t", slug: "s", schemaEnv: '{"type":"object"}' });
113
+ // 声明侧无 schema(schemaEnv 不入任务声明,§3.3.5 删字段去向)
114
+ expect(spec.schema).toBeUndefined();
115
+ // 兜底通道(RunContext.schemaEnv → mapper schemaEnvFallback)
116
+ const back = taskSpecToExecuteOptions(spec, { schemaEnvFallback: '{"type":"object"}' });
117
+ expect(back.schemaEnv).toBe('{"type":"object"}');
118
+ // 派生优先:schema 存在时 fallback 被忽略(派生值 = JSON.stringify(schema))
119
+ const coupled = { ...spec, schema: { type: "object" } };
120
+ expect(taskSpecToExecuteOptions(coupled, { schemaEnvFallback: "STALE" }).schemaEnv).toBe('{"type":"object"}');
121
+ });
122
+
123
+ it("运行期字段不入任务声明:signal/ctxModel/onComplete 从 ExecuteOptions 剥离(移入 RunContext)", () => {
124
+ const ctxModel = { id: "m", name: "Test Model", provider: "p", reasoning: false };
125
+ const opts: ExecuteOptions = {
126
+ task: "t",
127
+ slug: "s",
128
+ ctxModel,
129
+ onComplete: () => {},
130
+ };
131
+ const spec = executeOptionsToTaskSpec(opts);
132
+ expect("ctxModel" in spec).toBe(false);
133
+ expect("onComplete" in spec).toBe(false);
134
+ expect("signal" in spec).toBe(false);
135
+ // ctxModel 从 RunContext 回填(SAR 接线的 D-008 兼底链路)
136
+ expect(taskSpecToExecuteOptions(spec, { ctxModel }).ctxModel).toBe(ctxModel);
137
+ expect(taskSpecToExecuteOptions(spec).ctxModel).toBeUndefined();
138
+ });
139
+
140
+ it("中立新字段 denyTools/permissionMode:pi 侧映射回 ExecuteOptions 时忽略(无对应面)", () => {
141
+ const spec: AgentTaskSpec = { task: "t", slug: "s", denyTools: ["bash"], permissionMode: "yolo" };
142
+ const opts = taskSpecToExecuteOptions(spec);
143
+ expect("denyTools" in opts).toBe(false);
144
+ expect("permissionMode" in opts).toBe(false);
145
+ // 声明侧字段保真(不因 pi 不支持而丢弃——P2 公共层消费)
146
+ expect(spec.denyTools).toEqual(["bash"]);
147
+ expect(spec.permissionMode).toBe("yolo");
148
+ });
149
+
150
+ it("归一化:appendSystemPrompt: [] 与 undefined 往返后等价(下游 spread 空数组为 no-op)", () => {
151
+ const spec = executeOptionsToTaskSpec({ task: "t", slug: "s", appendSystemPrompt: [] });
152
+ expect(spec.persona).toEqual({ appendSystemPrompt: [] });
153
+ const back = taskSpecToExecuteOptions(spec);
154
+ expect(back.appendSystemPrompt).toEqual([]);
155
+ });
156
+
157
+ it("worktree 三态(true/false/handle)往返保持", () => {
158
+ const handle = { path: "/wt", branch: "b", baseCommit: "c", mainCwd: "/repo" };
159
+ for (const wt of [true, false, handle] as const) {
160
+ const opts: ExecuteOptions = { task: "t", slug: "s", worktree: wt };
161
+ expect(taskSpecToExecuteOptions(executeOptionsToTaskSpec(opts)).worktree).toBe(wt);
162
+ }
163
+ });
164
+ });
@@ -0,0 +1,415 @@
1
+ // src/execution/engine/engines/pi/pi-engine.ts
2
+ //
3
+ // PiEngine(P1 回填):现有 pi spawn 执行链的引擎适配器。设计权威源:
4
+ // docs/architecture/subagent-engine-abstraction.md §3.3.1(PiEngine = 现有 spawn 链回填,
5
+ // 行为零变化)、D1(四面 + handle 契约 + abort 分级)、D3(capabilities 链路接通口径)。
6
+ //
7
+ // 定位是「薄适配层」而非重新实现:不物理移动 session-runner.ts / pi-invocation.ts 等
8
+ // 现有文件(现有 40+ 测试的 import 路径零变化),run 委托 SubagentService.executeAndAwait
9
+ // (其内 runSpawn 即 pi 引擎本体),interact 委托现有 chatMode 交互面
10
+ // (getRecordForAction + deliverMessage / closeSubagent / cancel 直通),read 第①级委托
11
+ // 共享 reader readPiSessionView(engines/pi/reader.ts——W3B 双端复用面)。
12
+ //
13
+ // pi 专有语义的隔离点在 task-spec-mapper.ts(effort↔thinkingLevel、persona↔skillPath、
14
+ // schemaEnv 派生)——本文件只做委托与形态映射,不出现第二个 pi 语义翻译点。
15
+
16
+ import { execFile } from "node:child_process";
17
+ import * as fs from "node:fs";
18
+ import * as path from "node:path";
19
+
20
+ import { getLogger } from "../../../../core/logger.ts";
21
+
22
+ import type { AgentResult as WorkflowAgentResult } from "../../../../orchestration/models/types.ts";
23
+ import type { PiInvocation } from "../../../pi-invocation.ts";
24
+ import { getPiInvocation } from "../../../pi-invocation.ts";
25
+ import type { StatusFilter } from "../../../record-store.ts";
26
+ import type { SubagentStream } from "../../../stream-sink.ts";
27
+ import type { AgentEvent, ExecutionRecord, ExecuteOptions, SubagentRecord } from "../../../types.ts";
28
+ import type { EnginePort, EngineRunResult, RunContext } from "../../port.ts";
29
+ import { replayJournalToSessionView } from "../../common/journal-replay.ts";
30
+ import type {
31
+ AgentOutcome,
32
+ AgentTaskSpec,
33
+ EngineCapabilities,
34
+ EngineHandle,
35
+ InteractAction,
36
+ InteractResult,
37
+ ProbeReport,
38
+ SessionView,
39
+ } from "../../types.ts";
40
+ import { taskSpecToExecuteOptions } from "./task-spec-mapper.ts";
41
+ import { readPiSessionView } from "./reader.ts";
42
+
43
+ const logger = getLogger("subagents");
44
+
45
+ /** pi 引擎的 registry key(D9:缺省引擎 = 'pi',回填期零风险默认)。 */
46
+ export const PI_ENGINE_ID = "pi";
47
+
48
+ /** pi 适配器版本(handle.adapterVersion 数据源——golden 样本对齐排查锚点)。 */
49
+ export const PI_ADAPTER_VERSION = "1.0.0";
50
+
51
+ /** pi 无隔离池(PI_CODING_AGENT_DIR 全局一份,设计 §3.3.9),poolKey 恒 'shared'。 */
52
+ export const PI_POOL_KEY = "shared";
53
+
54
+ /** probe 的版本探测超时(ms)。二进制无响应时按探针失败处理,不静默挂死(目标 4)。 */
55
+ const PROBE_VERSION_TIMEOUT_MS = 10_000;
56
+
57
+ /** interact 经 sessionFile 兜底定位 record 时的 collectRecords 扫描上限。 */
58
+ const INTERACT_SCAN_LIMIT = 1000;
59
+
60
+ /**
61
+ * PiEngine 委托的编排服务面——SubagentService 的结构子集(鸭子类型:生产实现是
62
+ * SubagentService 单例;测试可注入 fake,不必构造整个 Service)。
63
+ * 为什么用结构接口而非直接 import SubagentService 类型:pi-engine 只依赖它实际消费的
64
+ * 方法面,防 Service 内部演进(增删私有方法)连锁影响引擎适配层。
65
+ */
66
+ export interface PiEngineService {
67
+ executeAndAwait(
68
+ opts: ExecuteOptions,
69
+ signal?: AbortSignal,
70
+ onEvent?: (event: AgentEvent) => void,
71
+ stream?: SubagentStream,
72
+ ): Promise<WorkflowAgentResult>;
73
+ getRecordForAction(id: string): ExecutionRecord;
74
+ deliverMessage(record: ExecutionRecord, text: string, interrupt: boolean): Promise<void>;
75
+ closeSubagent(record: ExecutionRecord, force: boolean): Promise<void>;
76
+ cancel(id: string): boolean;
77
+ collectRecords(limit: number, statusFilter?: StatusFilter): SubagentRecord[];
78
+ }
79
+
80
+ /** PiEngine 构造依赖。 */
81
+ export interface PiEngineDeps {
82
+ /**
83
+ * 编排服务定位器(惰性求值——服务在 session_start 才注入,构造期可能尚不可用)。
84
+ * 组合根(index.ts registerPiEngine)绑 getSubagentService();per-session DI 场景
85
+ * (SAR)绑自身持有的服务引用。
86
+ */
87
+ getService: () => PiEngineService | null;
88
+ /**
89
+ * 版本探测执行器(probe check "version" 的执行体)。默认实现 spawn `<command>
90
+ * --version`——测试注入 fake 避免真实子进程(vitest 下 command=node 会真的跑
91
+ * vitest --version,秒级开销且与被测逻辑无关)。
92
+ */
93
+ probeVersion?: (invocation: PiInvocation) => Promise<string | undefined>;
94
+ }
95
+
96
+ /**
97
+ * pi 引擎适配器(EnginePort 实现)。
98
+ *
99
+ * run 的错误语义(设计 §3.3.5 三条,pi 回填口径):
100
+ * ① 服务不可用(prepare 期)→ reject,不产生 handle;
101
+ * ② 运行中失败不 reject——现有链路(runSpawn → collectResult)已把失败收口为
102
+ * error 字段的 AgentResult,本层照直透传(record 收尾语义不变);
103
+ * executeAndAwait 的 throw(嵌套超限 ForkDepthExceededError / worktree 创建失败)
104
+ * 属创建期异常,按 ① 语义向上传播——与 SAR 接线前的 catch 行为闭环一致;
105
+ * ③ abort:ctx.signal 直通 executeAndAwait 第 2 参(下游 signal → proc.kill,
106
+ * D1 abort 分级的现链路形态)。
107
+ */
108
+ export class PiEngine implements EnginePort {
109
+ readonly id = PI_ENGINE_ID;
110
+
111
+ private readonly deps: PiEngineDeps;
112
+ /** probe 结果缓存(版本变化检测由 P4 接线;force 强探跳过)。 */
113
+ private probeCache: ProbeReport | undefined;
114
+
115
+ constructor(deps: PiEngineDeps) {
116
+ this.deps = deps;
117
+ }
118
+
119
+ /**
120
+ * pi 链路实际接通的能力(D3 链路接通口径,非 pi RPC 理论能力)。
121
+ * 声明升级(如 steer 接通 rpc stdin 通道后 unsupported → native)必须先改链路再改声明。
122
+ */
123
+ capabilities(): EngineCapabilities {
124
+ return {
125
+ // PI_WORKFLOW_SCHEMA env 注入 + structured-output 扩展(方案 A 唯一校验权威)
126
+ schemaEnforcement: "native",
127
+ // pi RPC 有 steer,但 spawn 链路未接通(session-runner turn-limiter steer no-op)
128
+ steer: "unsupported",
129
+ // chatMode idle 复用 + message/close/cancel 交互面已接通
130
+ conversation: "native",
131
+ // persona 经 --skill / --append-system-prompt flag 通道注入
132
+ personaInjection: "flag",
133
+ // 30+ 事件流(六引擎最细粒度)
134
+ eventGranularity: "stream",
135
+ // 无 OS sandbox;worktree 隔离(公共层)= emulated
136
+ sandbox: "emulated",
137
+ // pi session JSONL 完整重建(session-reconstructor)
138
+ sessionRead: "full",
139
+ // chatMode 同进程 idle 复用(热)+ --session 冷续写
140
+ resume: "native",
141
+ // 现链路 abort = SIGTERM(pi 子进程 trap 后 graceful shutdown);rpc abort 命令未接通
142
+ interrupt: "kill-only",
143
+ // argv-mirror 镜像主进程 --approve 等 flag
144
+ permissionMode: "native",
145
+ };
146
+ }
147
+
148
+ /** 探针(D7):invocation 可解析(二进制/脚本存在)+ 版本解析。pi 契约稳定(rpc.md
149
+ * 官方),探针取轻量两级——不做干跑回归(那是 zcode 类逆向契约引擎的需求)。 */
150
+ async probe(opts?: { force?: boolean }): Promise<ProbeReport> {
151
+ if (!opts?.force && this.probeCache) return this.probeCache;
152
+
153
+ // check 1:invocation 可解析(node 脚本存在 / standalone binary / PATH 可见)。
154
+ // relay:false 显式直连——探针测 pi 本体可解析性,经 relay 探到的是 runtime 健康,语义错位(§5.1)。
155
+ const invocation = getPiInvocation(["--version"], { relay: false });
156
+ const invocationOk = isInvocationResolvable(invocation);
157
+ const checks: ProbeReport["checks"] = [
158
+ {
159
+ name: "invocation",
160
+ ok: invocationOk,
161
+ detail: invocationOk
162
+ ? `${invocation.command} ${invocation.args.join(" ")}`
163
+ : `cannot resolve pi executable: ${invocation.command} (script missing and not on PATH)`,
164
+ },
165
+ ];
166
+
167
+ // check 2:版本解析(可解析才尝试——invocation 已失败时不再 spawn 必败进程)
168
+ let engineVersion = "";
169
+ if (invocationOk) {
170
+ const runVersion = this.deps.probeVersion ?? defaultProbeVersion;
171
+ const version = await runVersion(invocation);
172
+ const versionOk = version !== undefined && version.length > 0;
173
+ checks.push({
174
+ name: "version",
175
+ ok: versionOk,
176
+ detail: versionOk ? version : "pi --version returned empty or failed",
177
+ });
178
+ engineVersion = version ?? "";
179
+ }
180
+
181
+ const ok = checks.every((c) => c.ok);
182
+ const report: ProbeReport = {
183
+ ok,
184
+ engineVersion,
185
+ checks,
186
+ ...(ok ? {} : {
187
+ // 恢复指引(§3.3.3 终态四):版本确认命令 + 探针重跑命令
188
+ error: {
189
+ code: "engine_probe_failed",
190
+ recovery:
191
+ `Run \`${invocation.command} --version\` to confirm the pi binary works, then retry the probe. ` +
192
+ `If the binary is missing, reinstall pi (npm i -g @earendil-works/pi-coding-agent) or fix PATH.`,
193
+ },
194
+ }),
195
+ };
196
+ this.probeCache = report;
197
+ return report;
198
+ }
199
+
200
+ /** D1 主语义:映射中立声明 → ExecuteOptions,委托 executeAndAwait(行为零变化)。 */
201
+ async run(task: AgentTaskSpec, ctx: RunContext): Promise<EngineRunResult> {
202
+ const service = this.requireService();
203
+ const opts = taskSpecToExecuteOptions(task, {
204
+ ctxModel: ctx.ctxModel,
205
+ // 解耦形态兜底(schema 缺失时才生效,派生优先——见 mapper 注释)
206
+ ...(ctx.schemaEnv !== undefined ? { schemaEnvFallback: ctx.schemaEnv } : {}),
207
+ });
208
+ // P4 引擎留痕(D9①):record 侧投影——实际执行引擎 id 恒 pi(本引擎身份);
209
+ // engineFallback 由路由层经 RunContext 透传(无 fallback 缺省,record 字段零噪声)
210
+ opts.engine = PI_ENGINE_ID;
211
+ if (ctx.engineFallback !== undefined) opts.engineFallback = ctx.engineFallback;
212
+ // pi 无隔离池(PI_CODING_AGENT_DIR 全局一份,poolKey 恒 'shared')——恒值声明,
213
+ // 宿主 journal writer 无需重定向(对齐点③的 pi 侧闭合)
214
+ ctx.onPoolResolved?.(PI_POOL_KEY);
215
+ // ctx.stream / onEvent / signal 直通——与 SAR 接线前的传参完全一致(双通道互斥设计
216
+ // 保持在 service/session-runner 内,引擎层不二次包装)
217
+ const wfResult = await service.executeAndAwait(opts, ctx.signal, ctx.onEvent, ctx.stream);
218
+ return {
219
+ handle: this.buildHandle(wfResult, ctx),
220
+ outcome: workflowResultToOutcome(wfResult, ctx),
221
+ };
222
+ }
223
+
224
+ /**
225
+ * D1 可选面:chatMode 交互控制面直通现有实现(message=deliverMessage /
226
+ * close=closeSubagent / cancel=cancel)。message 的 interrupt 语义(steer 抢占 vs
227
+ * followUp 排队)中立 action 未携带——P1 恒 false(followUp,与 tool 面默认一致);
228
+ * 后续 wave 如需抢占语义再扩展 InteractAction(设计 §3.3.5 的载荷形状)。
229
+ */
230
+ async interact(handle: EngineHandle, action: InteractAction): Promise<InteractResult> {
231
+ const service = this.requireService();
232
+ try {
233
+ if (action.kind === "cancel") {
234
+ const recordId = this.resolveRecordId(service, handle);
235
+ if (!recordId) return notResumable(handle);
236
+ return service.cancel(recordId)
237
+ ? { ok: true, delivered: true }
238
+ : { ok: false, code: "engine_interact_failed", message: `cancel returned false for ${recordId} (record already finalized or unknown)` };
239
+ }
240
+ const record = this.resolveRecord(service, handle);
241
+ if (!record) return notResumable(handle);
242
+ if (action.kind === "message") {
243
+ await service.deliverMessage(record, action.payload, false);
244
+ return { ok: true, delivered: true };
245
+ }
246
+ await service.closeSubagent(record, action.payload?.force === true);
247
+ return { ok: true, delivered: true };
248
+ } catch (err) {
249
+ // 现有交互面以 throw 表达业务拒绝(not ready / EPIPE 兜底耗尽等,文案自带行动语言)
250
+ // ——转结构化失败,调用方拿 code+message 而非异常
251
+ return {
252
+ ok: false,
253
+ code: "engine_interact_failed",
254
+ message: err instanceof Error ? err.message : String(err),
255
+ };
256
+ }
257
+ }
258
+
259
+ /**
260
+ * D6 read 三级降级:①pi session JSONL 原生读取(readPiSessionView——共享 reader
261
+ * 单一实现,sessionRead: 'full' 的依据)→ ②宿主 event journal 重放(P4 接线:
262
+ * common/journal-replay 复用 live reducer,重放等价性见 §3.3.6)→ ③outcome-only。
263
+ */
264
+ async read(handle: EngineHandle): Promise<SessionView> {
265
+ const sessionFile = refString(handle.data.sessionRef, "sessionFile");
266
+ const native = sessionFile !== undefined ? await readPiSessionView(sessionFile) : undefined;
267
+ if (!native) {
268
+ // ②级:journal 重放(journalPath 缺省 / 文件不存在 / 无事件 → undefined 落③级)
269
+ const journaled = replayJournalToSessionView(handle, PI_ENGINE_ID);
270
+ if (journaled !== undefined) return journaled;
271
+ return { engineId: PI_ENGINE_ID, turns: [], source: "outcome-only" };
272
+ }
273
+ return native;
274
+ }
275
+
276
+ // ── 内部 ──
277
+
278
+ private requireService(): PiEngineService {
279
+ const service = this.deps.getService();
280
+ if (!service) {
281
+ throw new Error(
282
+ `[pi-engine] SubagentService unavailable (initSession not called or extension disposed). ` +
283
+ `Recovery: ensure the subagent-workflow extension is initialized in this session before dispatching.`,
284
+ );
285
+ }
286
+ return service;
287
+ }
288
+
289
+ /** run 出口构造可持久化 handle(自描述:engineId + sessionRef + poolKey + 版本)。 */
290
+ private buildHandle(wfResult: WorkflowAgentResult, ctx: RunContext): EngineHandle {
291
+ const sessionFile = wfResult.sessionFile;
292
+ return {
293
+ data: {
294
+ v: 1,
295
+ engineId: PI_ENGINE_ID,
296
+ // pi 定位符:sessionFile(read 第①级);recordId 由宿主 record 层补记
297
+ //(executeAndAwait 不外露 record id,交互定位走 resolveRecord 的扫描兜底)
298
+ sessionRef: sessionFile !== undefined ? { sessionFile } : {},
299
+ poolKey: ctx.poolKey !== "" ? ctx.poolKey : PI_POOL_KEY,
300
+ adapterVersion: PI_ADAPTER_VERSION,
301
+ },
302
+ };
303
+ }
304
+
305
+ /** interact 定位 record:sessionRef.recordId 直查;缺省时按 sessionFile 扫描兜底。 */
306
+ private resolveRecord(service: PiEngineService, handle: EngineHandle): ExecutionRecord | undefined {
307
+ const recordId = this.resolveRecordId(service, handle);
308
+ if (recordId === undefined) return undefined;
309
+ try {
310
+ return service.getRecordForAction(recordId);
311
+ } catch {
312
+ return undefined;
313
+ }
314
+ }
315
+
316
+ private resolveRecordId(service: PiEngineService, handle: EngineHandle): string | undefined {
317
+ if (handle.data.engineId !== PI_ENGINE_ID) return undefined;
318
+ const recordId = refString(handle.data.sessionRef, "recordId");
319
+ if (recordId !== undefined) return recordId;
320
+ const sessionFile = refString(handle.data.sessionRef, "sessionFile");
321
+ if (sessionFile === undefined) return undefined;
322
+ // sessionFile 兜底:扫描 record 清单匹配定位符(interact 频率低,扫描成本可接受)
323
+ const found = service
324
+ .collectRecords(INTERACT_SCAN_LIMIT, "all")
325
+ .find((r) => r.sessionFile === sessionFile);
326
+ return found?.id;
327
+ }
328
+ }
329
+
330
+ // ── 模块级纯函数(可单测)──
331
+
332
+ /** sessionRef 取 string 值的运行时 guard(Record 索引读经 typeof 收窄,非裸取)。 */
333
+ function refString(ref: Record<string, string>, key: string): string | undefined {
334
+ const v = ref[key];
335
+ return typeof v === "string" ? v : undefined;
336
+ }
337
+
338
+ /** 默认版本探测:spawn `<command> --version`(probe 超时按探针失败处理)。 */
339
+ async function defaultProbeVersion(invocation: PiInvocation): Promise<string | undefined> {
340
+ try {
341
+ return await new Promise<string | undefined>((resolve, reject) => {
342
+ execFile(
343
+ invocation.command,
344
+ invocation.args,
345
+ { encoding: "utf8", timeout: PROBE_VERSION_TIMEOUT_MS },
346
+ (err: Error | null, stdout: string) => {
347
+ if (err) reject(err);
348
+ else resolve(stdout.trim().split("\n")[0]?.trim() || undefined);
349
+ },
350
+ );
351
+ });
352
+ } catch (err) {
353
+ // 版本探测是 best-effort(失败经 checks 反映进 ProbeReport,不影响执行主流程),
354
+ // debug 级留诊断线索即可,不刷 info/warn
355
+ logger.debug(
356
+ `[pi-engine] probe version check failed (best-effort continue): ${
357
+ err instanceof Error ? err.message : String(err)
358
+ }`,
359
+ );
360
+ return undefined;
361
+ }
362
+ }
363
+
364
+ /**
365
+ * invocation 是否可解析:非 PATH 依赖形态(node+script / standalone binary)直接认
366
+ * (command 是已存在的绝对路径);PATH 形态(command === "pi")扫 PATH 目录核实。
367
+ */
368
+ function isInvocationResolvable(invocation: PiInvocation): boolean {
369
+ if (invocation.command !== "pi") return true;
370
+ const pathEnv = process.env.PATH ?? "";
371
+ for (const dir of pathEnv.split(path.delimiter)) {
372
+ if (dir === "") continue;
373
+ try {
374
+ if (fs.existsSync(path.join(dir, "pi"))) return true;
375
+ } catch (err) {
376
+ // 单目录探测失败(权限等)继续扫下一个——debug 级留线索即可
377
+ logger.debug(
378
+ `[pi-engine] PATH dir probe failed (continue scanning): ${dir}: ${
379
+ err instanceof Error ? err.message : String(err)
380
+ }`,
381
+ );
382
+ }
383
+ }
384
+ return false;
385
+ }
386
+
387
+ /** orchestration AgentResult → AgentOutcome(补 engineId;exitCode 无来源缺省)。 */
388
+ function workflowResultToOutcome(wf: WorkflowAgentResult, ctx?: RunContext): AgentOutcome {
389
+ return {
390
+ content: wf.content,
391
+ parsedOutput: wf.parsedOutput,
392
+ usage: wf.usage,
393
+ durationMs: wf.durationMs,
394
+ error: wf.error,
395
+ sessionId: wf.sessionId,
396
+ sessionFile: wf.sessionFile,
397
+ worktreePath: wf.worktreePath,
398
+ toolCalls: wf.toolCalls,
399
+ engineId: PI_ENGINE_ID,
400
+ // D9① fallback 留痕(路由层经 RunContext 透传;无 fallback 缺省)
401
+ ...(ctx?.engineFallback !== undefined ? { engineFallback: ctx.engineFallback } : {}),
402
+ };
403
+ }
404
+
405
+ /** 死 handle 的统一拒绝(D1 推论:engine_session_not_resumable 指向 cold resume 路径)。 */
406
+ function notResumable(handle: EngineHandle): InteractResult {
407
+ return {
408
+ ok: false,
409
+ code: "engine_session_not_resumable",
410
+ message:
411
+ `the pi session behind this handle is not resumable via interact (record not found for ` +
412
+ `sessionRef ${JSON.stringify(handle.data.sessionRef)}). Idle-process reuse does not survive a main-session ` +
413
+ `reload. Recovery: use a cold resume path (pi --session / --resume with the session file), or start a new subagent.`,
414
+ };
415
+ }
@@ -0,0 +1,48 @@
1
+ // src/execution/engine/engines/pi/reader.ts
2
+ //
3
+ // PiEngine reader(P5):pi subagent session JSONL → SessionView(设计 D6 read
4
+ // 降级链第①级的 pi 侧实现)。
5
+ //
6
+ // 双端复用约束(设计 §3.3.7,与 zcode/reader.ts 同款):本模块必须保持无状态
7
+ // 纯函数、无 spawn/进程依赖、不 import 同包 launcher/preparer/parser。因此
8
+ // PI_ENGINE_ID 在此本地锚定(pi-engine.ts 的同名常量会连带运行时件 import,
9
+ // 破坏「共享 reader 是唯一允许被 xyz-agent runtime import 的引擎模块」约束)。
10
+ //
11
+ // 下沉说明(A1 守护):turns 重建逻辑本体 = session-reconstructor.reconstructFromFile
12
+ // (pi JSONL → ReconstructedRecord,extension 侧唯一 source of truth)。本模块只做
13
+ // ReconstructedRecord → SessionView 的投影(投影原语 toReplayedTurn / aggregateUsage
14
+ // 收敛在 common/session-view-projection.ts 单一实现),不改 reconstructor 本体——
15
+ // pi 现有直读行为零变化。
16
+ //
17
+ // xyz-agent runtime 侧的 pi 历史读取链(getHistoryFromFilePath)是独立实现(shared
18
+ // Message 投影),不消费本模块——两链各自保持现状(P5 只对非 pi 引擎引入共享
19
+ // reader;pi 的 runtime 链路回归由现有测试守护)。
20
+
21
+ import { reconstructFromFile } from "../../../session-reconstructor.ts";
22
+ import type { SessionView } from "../../types.ts";
23
+ import { aggregateUsage, toReplayedTurn } from "../../common/session-view-projection.ts";
24
+
25
+ /** pi 引擎的 registry key(与 engines/pi/pi-engine.ts 的 PI_ENGINE_ID 同值锚定)。 */
26
+ export const PI_ENGINE_ID = "pi";
27
+
28
+ /**
29
+ * 读取 pi subagent session 的引擎中立视图(read 第①级)。
30
+ *
31
+ * @param sessionFile pi session JSONL 绝对路径
32
+ * @returns undefined = 文件缺失/损坏/缺 identity entry/无 assistant message
33
+ * (reconstructFromFile 的降级语义,不 throw——降级链由宿主编排)。
34
+ */
35
+ export async function readPiSessionView(sessionFile: string): Promise<SessionView | undefined> {
36
+ const recon = reconstructFromFile(sessionFile);
37
+ if (recon === undefined) return undefined;
38
+ const usage = aggregateUsage(recon.turns);
39
+ return {
40
+ engineId: PI_ENGINE_ID,
41
+ // recon.id 是 subagent record id——pi 链路的 sessionId 约定即 header id 兜底
42
+ // record.id(collectResult 同源,与 pi-engine read 的既有约定一致)
43
+ sessionId: recon.id,
44
+ turns: recon.turns.map(toReplayedTurn),
45
+ ...(usage !== undefined ? { usage } : {}),
46
+ source: "native",
47
+ };
48
+ }
@@ -0,0 +1,35 @@
1
+ // src/execution/engine/engines/pi/registration.ts
2
+ //
3
+ // pi 引擎注册入口(P1 接线)。设计权威源:§3.3.1(注册表 id → factory)+ D9(缺省
4
+ // 引擎 = 'pi')。组合根(src/index.ts 扩展初始化)调用 registerPiEngine(),把进程级
5
+ // SubagentService 单例登记为 registry 里的 'pi' 引擎——此后引擎获取统一经
6
+ // getEngine('pi') / getEngine(DEFAULT_ENGINE_ID),上层不再硬编码「spawn pi」这一选择。
7
+ //
8
+ // 为什么独立文件:pi-engine.ts 保持零注册副作用(被 SAR 等 DI 场景 import 时只拿类),
9
+ // 注册是组合根的职责——依赖方向 registry ← registration ← pi-engine 单向,无循环。
10
+
11
+ import { getSubagentService } from "../../../subagent-service.ts";
12
+ import { registerEngine } from "../../registry.ts";
13
+ import { PI_ENGINE_ID, PiEngine } from "./pi-engine.ts";
14
+ import type { PiEngineService } from "./pi-engine.ts";
15
+
16
+ export { PI_ADAPTER_VERSION, PI_ENGINE_ID, PI_POOL_KEY, PiEngine } from "./pi-engine.ts";
17
+ export type { PiEngineDeps, PiEngineService } from "./pi-engine.ts";
18
+
19
+ /**
20
+ * 构造绑定到指定服务定位器的 PiEngine(DI 工厂)。
21
+ * SAR(per-session DI 构造,注入 mock 的测试场景)用本工厂绑定自身持有的服务引用;
22
+ * registry 全局单例绑定的是进程级 getSubagentService()——生产环境两者是同一对象。
23
+ */
24
+ export function createPiEngine(getService: () => PiEngineService | null): PiEngine {
25
+ return new PiEngine({ getService });
26
+ }
27
+
28
+ /**
29
+ * 把 'pi' 引擎登记进 registry(幂等——组合根可能多次执行,如每次 session_start)。
30
+ * 工厂绑进程单例 getSubagentService:惰性求值,session_start 注入前调用 getEngine
31
+ * 只会拿到引擎实例,真正 run 时才解析服务。
32
+ */
33
+ export function registerPiEngine(): void {
34
+ registerEngine(PI_ENGINE_ID, () => createPiEngine(getSubagentService));
35
+ }