@zhushanwen/subagent-core 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (278) hide show
  1. package/README.md +1 -1
  2. package/dist/chunk-KXVBIVOU.js +16 -0
  3. package/dist/chunk-T2SYBW3I.js +24 -0
  4. package/dist/chunk-VURUAGMM.js +2305 -0
  5. package/dist/{types-C3fE3R9x.d.cts → engine-discovery-scan-ocpM8FmI.d.cts} +770 -668
  6. package/dist/{types-C3fE3R9x.d.ts → engine-discovery-scan-ocpM8FmI.d.ts} +770 -668
  7. package/dist/execution/engine/engine-discovery-scan.cjs +2210 -0
  8. package/dist/execution/engine/engine-discovery-scan.d.cts +4 -0
  9. package/dist/execution/engine/engine-discovery-scan.d.ts +4 -0
  10. package/dist/execution/engine/engine-discovery-scan.js +20 -0
  11. package/dist/execution/engine/paths.cjs +6 -23
  12. package/dist/execution/engine/paths.d.cts +1 -8
  13. package/dist/execution/engine/paths.d.ts +1 -8
  14. package/dist/execution/engine/paths.js +1 -1
  15. package/dist/execution/relay-env.cjs +12 -28
  16. package/dist/execution/relay-env.d.cts +1 -33
  17. package/dist/execution/relay-env.d.ts +1 -33
  18. package/dist/execution/relay-env.js +5 -3
  19. package/dist/index.cjs +14045 -16059
  20. package/dist/index.d.cts +1035 -1579
  21. package/dist/index.d.ts +1035 -1579
  22. package/dist/index.js +9838 -13800
  23. package/dist.bundle/index.cjs +28875 -30323
  24. package/package.json +20 -26
  25. package/src/__tests__/record-store-last-line.test.ts +16 -7
  26. package/src/core/error-message.ts +7 -6
  27. package/src/core/host-services.ts +8 -2
  28. package/src/core/notify-ports.ts +17 -2
  29. package/src/execution/__tests__/chat-engine-routing.test.ts +62 -28
  30. package/src/execution/__tests__/chat-round-first-round-watchdog.test.ts +255 -0
  31. package/src/execution/__tests__/collect-coordinator-service.test.ts +36 -47
  32. package/src/execution/__tests__/collect-mixed-dispatch.test.ts +20 -53
  33. package/src/execution/__tests__/delivery-methods.test.ts +179 -237
  34. package/src/execution/__tests__/execute-and-await-worktree.test.ts +0 -2
  35. package/src/execution/__tests__/execute-nesting.test.ts +20 -13
  36. package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +20 -33
  37. package/src/execution/__tests__/gc-timer.test.ts +7 -22
  38. package/src/execution/__tests__/get-record-for-action-restart.test.ts +20 -10
  39. package/src/execution/__tests__/helpers/fake-engine-port.ts +187 -0
  40. package/src/execution/__tests__/helpers/spawn-mock.ts +36 -9
  41. package/src/execution/__tests__/helpers/subagent-service-mocks.ts +1 -1
  42. package/src/execution/__tests__/idle-gc.test.ts +200 -0
  43. package/src/execution/__tests__/lifecycle-predicates.test.ts +26 -21
  44. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +5 -2
  45. package/src/execution/__tests__/reconcile-sweep.test.ts +200 -0
  46. package/src/execution/__tests__/record-store.test.ts +60 -6
  47. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +20 -13
  48. package/src/execution/__tests__/round-supervisor.test.ts +293 -0
  49. package/src/execution/__tests__/session-pending.test.ts +119 -17
  50. package/src/execution/__tests__/stream-sink-retirement.test.ts +10 -10
  51. package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +1 -11
  52. package/src/execution/__tests__/subagent-service-notify-gate.test.ts +33 -30
  53. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +2 -9
  54. package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +59 -129
  55. package/src/execution/__tests__/subagent-service.test.ts +2 -2
  56. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +109 -7
  57. package/src/execution/__tests__/subprocess-agent-runner.test.ts +108 -36
  58. package/src/execution/__tests__/sync-collect-recovery.test.ts +21 -36
  59. package/src/execution/__tests__/workflow-state-root.test.ts +232 -0
  60. package/src/execution/agent-registry.ts +6 -2
  61. package/src/execution/dialog-queue.ts +6 -73
  62. package/src/execution/engine/__tests__/common/capability-gate.test.ts +115 -15
  63. package/src/execution/engine/__tests__/common/errors.test.ts +11 -18
  64. package/src/execution/engine/__tests__/common/kill-chain.test.ts +7 -0
  65. package/src/execution/engine/__tests__/common/nesting-guard.test.ts +9 -3
  66. package/src/execution/engine/__tests__/common/session-view-service.test.ts +90 -3
  67. package/src/execution/engine/__tests__/conformance/H9-test-disposition.md +116 -0
  68. package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/fake-engine-protocol.mjs +377 -0
  69. package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/recorded.fixture.json +331 -0
  70. package/src/execution/engine/__tests__/conformance/__fixtures__/engine-protocol/smoke-run.fixture.json +213 -0
  71. package/src/execution/engine/__tests__/conformance/chat-round-protocol.test.ts +230 -0
  72. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +69 -42
  73. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +4 -81
  74. package/src/execution/engine/__tests__/conformance/contract.probe.test.ts +67 -44
  75. package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +81 -54
  76. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +133 -70
  77. package/src/execution/engine/__tests__/conformance/engine-crash.test.ts +143 -0
  78. package/src/execution/engine/__tests__/conformance/fake-engine-capabilities.ts +19 -0
  79. package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +7 -2
  80. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +6 -5
  81. package/src/execution/engine/__tests__/conformance/protocol-blackbox.test.ts +398 -0
  82. package/src/execution/engine/__tests__/conformance/registry-fork-filter.test.ts +221 -0
  83. package/src/execution/engine/__tests__/conformance/round-liveness-supervisor.test.ts +302 -0
  84. package/src/execution/engine/__tests__/conformance/static-spawn-guard.test.ts +58 -0
  85. package/src/execution/engine/__tests__/engine-discovery-scan.test.ts +558 -0
  86. package/src/execution/engine/__tests__/host-task-spec.test.ts +45 -0
  87. package/src/execution/engine/__tests__/model-ref-split.test.ts +74 -0
  88. package/src/execution/engine/__tests__/registry.test.ts +206 -1
  89. package/src/execution/engine/__tests__/routing.test.ts +97 -0
  90. package/src/execution/engine/__tests__/run-failure-worktree-cleanup.test.ts +97 -0
  91. package/src/execution/engine/client/__tests__/__fixtures__/fake-engine.mjs +294 -0
  92. package/src/execution/engine/client/__tests__/engine-client.test.ts +516 -0
  93. package/src/execution/engine/client/__tests__/mirror.test.ts +67 -0
  94. package/src/execution/engine/client/__tests__/pid-file.test.ts +257 -0
  95. package/src/execution/engine/client/__tests__/protocol-closure.test.ts +95 -0
  96. package/src/execution/engine/client/__tests__/remote-engine.test.ts +457 -0
  97. package/src/execution/engine/client/client-options.ts +103 -0
  98. package/src/execution/engine/client/engine-client.ts +687 -0
  99. package/src/execution/engine/client/mirror.ts +167 -0
  100. package/src/execution/engine/client/pid-file.ts +392 -0
  101. package/src/execution/engine/client/reaper.ts +95 -0
  102. package/src/execution/engine/client/remote-engine.ts +441 -0
  103. package/src/execution/engine/client/reverse-router.ts +255 -0
  104. package/src/execution/engine/common/capability-gate.ts +109 -22
  105. package/src/execution/engine/common/errors.ts +9 -25
  106. package/src/execution/engine/common/event-journal.ts +4 -50
  107. package/src/execution/engine/common/journal-replay.ts +3 -1
  108. package/src/execution/engine/common/kill-chain.ts +18 -251
  109. package/src/execution/engine/common/nesting-guard.ts +18 -114
  110. package/src/execution/engine/common/pool-manager.ts +4 -0
  111. package/src/execution/engine/common/session-view-service.ts +56 -84
  112. package/src/execution/engine/config.ts +151 -0
  113. package/src/execution/engine/d8-compat.ts +346 -0
  114. package/src/execution/engine/engine-discovery-roots.ts +107 -0
  115. package/src/execution/engine/engine-discovery-scan.ts +269 -0
  116. package/src/execution/engine/engine-discovery.ts +30 -7
  117. package/src/execution/engine/engine-inspect-package.ts +282 -0
  118. package/src/execution/engine/engine-manifest.ts +244 -0
  119. package/src/execution/engine/host/__tests__/host-askuser-endpoint.test.ts +123 -0
  120. package/src/execution/engine/host/__tests__/host-bridge.test.ts +135 -0
  121. package/src/execution/engine/host/__tests__/spawned-children.test.ts +73 -0
  122. package/src/execution/engine/host/host-bridge.ts +150 -0
  123. package/src/execution/engine/host/host-ui-endpoint.ts +47 -0
  124. package/src/execution/engine/host/pi-host-binding.ts +91 -0
  125. package/src/execution/engine/host/spawned-children.ts +139 -0
  126. package/src/execution/engine/host-task-spec.ts +29 -2
  127. package/src/execution/engine/model-validation.ts +55 -2
  128. package/src/execution/engine/paths.ts +16 -41
  129. package/src/execution/engine/port.ts +48 -2
  130. package/src/execution/engine/registry.ts +153 -34
  131. package/src/execution/engine/routing.ts +225 -52
  132. package/src/execution/engine/types.ts +25 -200
  133. package/src/execution/finalize-record.ts +25 -0
  134. package/src/execution/idle-gc.ts +89 -14
  135. package/src/execution/lifecycle-manager.ts +10 -7
  136. package/src/execution/lifecycle-predicates.ts +10 -8
  137. package/src/execution/record-store.ts +29 -2
  138. package/src/execution/relay-env.ts +19 -36
  139. package/src/execution/round-supervisor/domain.ts +89 -0
  140. package/src/execution/round-supervisor/index.ts +33 -0
  141. package/src/execution/round-supervisor/notify-accounting.ts +87 -0
  142. package/src/execution/round-supervisor/reconcile-sweep.ts +235 -0
  143. package/src/execution/round-supervisor/service-binding.test.ts +661 -0
  144. package/src/execution/round-supervisor/service-binding.ts +273 -0
  145. package/src/execution/round-supervisor/supervisor.ts +426 -0
  146. package/src/execution/session-pending.ts +68 -74
  147. package/src/execution/settled-watchdog.ts +39 -0
  148. package/src/execution/subagent-service.ts +746 -400
  149. package/src/execution/subprocess-agent-runner.ts +16 -8
  150. package/src/execution/types.ts +41 -117
  151. package/src/execution/ui-channels.ts +13 -214
  152. package/src/execution/ui-request-handler-factory.ts +7 -4
  153. package/src/execution/ui-request-observability.ts +2 -2
  154. package/src/execution/workflow-state-root.ts +67 -0
  155. package/src/execution/worktree-git-ops.ts +5 -0
  156. package/src/execution/worktree-manager.ts +11 -1
  157. package/src/index.ts +55 -26
  158. package/src/orchestration/__tests__/execute-agent-call.test.ts +1 -1
  159. package/src/orchestration/execute-agent-call.ts +1 -1
  160. package/src/orchestration/file-run-store.ts +144 -59
  161. package/src/orchestration/models/types.ts +43 -42
  162. package/src/shared/zcode-model-ref.ts +31 -0
  163. package/dist/chunk-43ONBFZX.js +0 -240
  164. package/dist/chunk-A4IVZWVX.js +0 -31
  165. package/dist/chunk-APZY4IME.js +0 -27
  166. package/dist/chunk-V3VHZ2VX.js +0 -59
  167. package/dist/execution/engine/engines/zcode/constants.cjs +0 -107
  168. package/dist/execution/engine/engines/zcode/constants.d.cts +0 -135
  169. package/dist/execution/engine/engines/zcode/constants.d.ts +0 -135
  170. package/dist/execution/engine/engines/zcode/constants.js +0 -54
  171. package/dist/execution/engine/engines/zcode/reader.cjs +0 -276
  172. package/dist/execution/engine/engines/zcode/reader.d.cts +0 -21
  173. package/dist/execution/engine/engines/zcode/reader.d.ts +0 -21
  174. package/dist/execution/engine/engines/zcode/reader.js +0 -9
  175. package/src/__tests__/append-system-prompt-assembly.test.ts +0 -243
  176. package/src/__tests__/fr4-get-state-handshake.test.ts +0 -125
  177. package/src/__tests__/session-runner.test.ts +0 -69
  178. package/src/execution/__tests__/ask-user-transit-e2e.test.ts +0 -490
  179. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +0 -191
  180. package/src/execution/__tests__/descendant-sweep-guards.test.ts +0 -220
  181. package/src/execution/__tests__/descendant-sweep.test.ts +0 -269
  182. package/src/execution/__tests__/engine-model-validation.test.ts +0 -741
  183. package/src/execution/__tests__/epipe-fallback.test.ts +0 -248
  184. package/src/execution/__tests__/execution-runtime-face.test.ts +0 -272
  185. package/src/execution/__tests__/get-state-handshake.test.ts +0 -127
  186. package/src/execution/__tests__/helpers/session-runner-mocks.ts +0 -113
  187. package/src/execution/__tests__/keep-alive-no-progress.test.ts +0 -280
  188. package/src/execution/__tests__/kill-all-escalation.test.ts +0 -196
  189. package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +0 -113
  190. package/src/execution/__tests__/output-collector.test.ts +0 -469
  191. package/src/execution/__tests__/pi-invocation.test.ts +0 -166
  192. package/src/execution/__tests__/recursive-visibility-env.test.ts +0 -149
  193. package/src/execution/__tests__/rpc-mode.test.ts +0 -89
  194. package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +0 -387
  195. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +0 -277
  196. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +0 -254
  197. package/src/execution/__tests__/run-spawn-edges.test.ts +0 -1079
  198. package/src/execution/__tests__/run-spawn-integration.test.ts +0 -933
  199. package/src/execution/__tests__/run-spawn-resume.test.ts +0 -322
  200. package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +0 -196
  201. package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +0 -207
  202. package/src/execution/__tests__/service-kill-escalation.test.ts +0 -91
  203. package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +0 -110
  204. package/src/execution/__tests__/session-runner-close-prune.test.ts +0 -181
  205. package/src/execution/__tests__/session-runner-dispatch.test.ts +0 -489
  206. package/src/execution/__tests__/session-runner-epipe.test.ts +0 -179
  207. package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +0 -227
  208. package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +0 -290
  209. package/src/execution/__tests__/session-runner-schema-env.test.ts +0 -352
  210. package/src/execution/__tests__/settled-watchdog.test.ts +0 -524
  211. package/src/execution/__tests__/spawn-args.test.ts +0 -446
  212. package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +0 -192
  213. package/src/execution/__tests__/spawn-event-adapter.test.ts +0 -167
  214. package/src/execution/__tests__/spawn-worktree-guidance.test.ts +0 -208
  215. package/src/execution/__tests__/spawned-children.test.ts +0 -236
  216. package/src/execution/__tests__/start-sync-model-guard.test.ts +0 -150
  217. package/src/execution/__tests__/stdin-writer.test.ts +0 -464
  218. package/src/execution/__tests__/subagent-service-message-close.test.ts +0 -693
  219. package/src/execution/__tests__/temp-prompt.test.ts +0 -53
  220. package/src/execution/__tests__/timeout-integration.test.ts +0 -616
  221. package/src/execution/__tests__/turn-limiter-semantics.test.ts +0 -194
  222. package/src/execution/__tests__/turn-limiter.test.ts +0 -65
  223. package/src/execution/__tests__/ui-request-handler.test.ts +0 -205
  224. package/src/execution/__tests__/ui-request-queue.test.ts +0 -298
  225. package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +0 -230
  226. package/src/execution/engine/__tests__/common/schema-emulation.test.ts +0 -128
  227. package/src/execution/engine/__tests__/common/session-view-service-zcode-dbpath.test.ts +0 -149
  228. package/src/execution/engine/__tests__/conformance/__fixtures__/pi-golden-events.json +0 -28
  229. package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +0 -128
  230. package/src/execution/engine/__tests__/paths.test.ts +0 -39
  231. package/src/execution/engine/common/schema-emulation.ts +0 -189
  232. package/src/execution/engine/common/session-view-projection.ts +0 -51
  233. package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +0 -615
  234. package/src/execution/engine/engines/pi/__tests__/reader.test.ts +0 -155
  235. package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +0 -257
  236. package/src/execution/engine/engines/pi/argv-mirror.ts +0 -135
  237. package/src/execution/engine/engines/pi/get-state-handshake.ts +0 -179
  238. package/src/execution/engine/engines/pi/output-collector.ts +0 -317
  239. package/src/execution/engine/engines/pi/pi-engine.ts +0 -811
  240. package/src/execution/engine/engines/pi/pi-invocation.ts +0 -120
  241. package/src/execution/engine/engines/pi/reader.ts +0 -59
  242. package/src/execution/engine/engines/pi/registration.ts +0 -37
  243. package/src/execution/engine/engines/pi/session-runner.ts +0 -2840
  244. package/src/execution/engine/engines/pi/spawn-event-adapter.ts +0 -404
  245. package/src/execution/engine/engines/pi/stdin-writer.ts +0 -199
  246. package/src/execution/engine/engines/pi/temp-prompt.ts +0 -62
  247. package/src/execution/engine/engines/pi/turn-limiter.ts +0 -102
  248. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +0 -301
  249. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-appserver.json +0 -36
  250. package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +0 -321
  251. package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +0 -473
  252. package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +0 -43
  253. package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +0 -124
  254. package/src/execution/engine/engines/zcode/__tests__/reader.test.ts +0 -210
  255. package/src/execution/engine/engines/zcode/__tests__/registration.test.ts +0 -71
  256. package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +0 -247
  257. package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +0 -451
  258. package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +0 -792
  259. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +0 -782
  260. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +0 -168
  261. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +0 -210
  262. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +0 -353
  263. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +0 -282
  264. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +0 -372
  265. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +0 -145
  266. package/src/execution/engine/engines/zcode/appserver-launcher.ts +0 -167
  267. package/src/execution/engine/engines/zcode/connection.ts +0 -598
  268. package/src/execution/engine/engines/zcode/constants.ts +0 -176
  269. package/src/execution/engine/engines/zcode/golden-sample.ts +0 -47
  270. package/src/execution/engine/engines/zcode/parser.ts +0 -102
  271. package/src/execution/engine/engines/zcode/preparer.ts +0 -235
  272. package/src/execution/engine/engines/zcode/reader.ts +0 -381
  273. package/src/execution/engine/engines/zcode/registration.ts +0 -37
  274. package/src/execution/engine/engines/zcode/session-channel.ts +0 -968
  275. package/src/execution/engine/engines/zcode/zcode-engine.ts +0 -1431
  276. package/src/execution/round-settlement.ts +0 -93
  277. package/src/execution/ui-request-queue.ts +0 -215
  278. package/src/shared/schema-env.ts +0 -44
@@ -29,6 +29,13 @@ import type { AgentCallOpts, AgentResult } from "../../orchestration/models/type
29
29
  import { ModelConfigService, setModelConfigService } from "../model-config-service.ts";
30
30
  import type { ModelInfo, ModelRegistryLike } from "../model-resolver.ts";
31
31
  import { replayJournal } from "../engine/common/event-journal.ts";
32
+ import { clearEngines, registerEngine } from "../engine/registry.ts";
33
+ import type { EnginePort, RunContext } from "../engine/port.ts";
34
+ import type {
35
+ AgentOutcome,
36
+ EngineCapabilities,
37
+ ProbeReport,
38
+ } from "../engine/types.ts";
32
39
  import type { SubprocessAgentRunnerDeps } from "../subprocess-agent-runner.ts";
33
40
  import type { SubagentService } from "../subagent-service.ts";
34
41
  import { SubprocessAgentRunner } from "../subprocess-agent-runner.ts";
@@ -48,9 +55,41 @@ function makeMockResult(overrides: Partial<AgentResult> = {}): AgentResult {
48
55
  };
49
56
  }
50
57
 
58
+ /** 协议替身的引擎内「任务声明 → 执行选项」映射(原 core PiEngine
59
+ * agentCallToExecuteOptions 的最小同构还原——[W3] 该映射本体已随 inproc pi 引擎目录 迁入
60
+ * pi-subagent-cli 引擎包,此处仅为保留 executeAndAwait 委托断言面)。 */
61
+ function fakeAgentCallToExecuteOptions(task: AgentCallOpts, ctxModel?: ModelInfo): Record<string, unknown> {
62
+ const rawSlug = task.description ?? task.agent ?? "workflow-agent";
63
+ return {
64
+ task: task.prompt,
65
+ slug: rawSlug.length > 35 ? rawSlug.slice(0, 35) : rawSlug,
66
+ agent: task.agent,
67
+ model: task.model,
68
+ thinkingLevel: task.thinkingLevel,
69
+ skillPath: task.skillPath,
70
+ appendSystemPrompt: task.appendSystemPrompt,
71
+ schema: task.schema,
72
+ schemaEnv: task.schemaEnv,
73
+ maxTurns: task.maxTurns,
74
+ graceTurns: task.graceTurns,
75
+ ctxModel,
76
+ fork: task.fork,
77
+ worktree: task.worktree,
78
+ cwd: task.cwd,
79
+ conversation: task.conversation,
80
+ idleTimeoutMs: task.idleTimeoutMs,
81
+ };
82
+ }
83
+
84
+ /** 引擎 run 捕获面(T3.19 直传保真断言用:SAR → engine.run 的 task 原样性)。 */
85
+ const engineRunSpy = vi.fn();
86
+
51
87
  /** 创建 mock SubagentService(只实现 executeAndAwait)。
52
- * [D4 聚合连带] SAR 构造器经 asEngineService 显式视图取 PiEngineService——fake 的
53
- * face 即自身,getter 直接返回 self。 */
88
+ * [D4 聚合连带] SAR 构造器经 asEngineService 显式视图取引擎服务面——fake 的 face
89
+ * 即自身,getter 直接返回 self。
90
+ * [W3 改写] 同步注册「委托式」替身 pi 引擎:run() 还原引擎内 task→ExecuteOptions
91
+ * 映射后委托 mock 的 executeAndAwait(原 inproc PiEngine 的 workflow 分支形态)——
92
+ * resolveHostPiEnginePort 对已注册 port 原样返回,SAR 路由命中替身。 */
54
93
  function createMockService(impl?: typeof vi.fn): SubagentService {
55
94
  const executeAndAwait = impl ?? vi.fn().mockResolvedValue(makeMockResult());
56
95
  // partial mock(仅 executeAndAwait + asEngineService self-引用)——SAR 测试路径
@@ -58,6 +97,50 @@ function createMockService(impl?: typeof vi.fn): SubagentService {
58
97
  const partial: { executeAndAwait: typeof executeAndAwait; asEngineService?: unknown } = { executeAndAwait };
59
98
  const service = partial as unknown as SubagentService;
60
99
  partial.asEngineService = service;
100
+
101
+ const fakePort: EnginePort = {
102
+ id: "pi",
103
+ capabilities: (): EngineCapabilities => ({
104
+ schemaEnforcement: "native",
105
+ steer: "unsupported",
106
+ conversation: "native",
107
+ personaInjection: "flag",
108
+ eventGranularity: "stream",
109
+ sandbox: "emulated",
110
+ sessionRead: "full",
111
+ resume: "native",
112
+ interrupt: "kill-only",
113
+ permissionMode: "native",
114
+ maxTurns: true,
115
+ }),
116
+ probe: async (): Promise<ProbeReport> => ({ ok: true, engineVersion: "fake", checks: [] }),
117
+ run: async (task: AgentCallOpts, ctx: RunContext) => {
118
+ engineRunSpy(task, ctx);
119
+ const wfResult = await executeAndAwait(
120
+ fakeAgentCallToExecuteOptions(task, ctx.ctxModel),
121
+ ctx.signal,
122
+ ctx.onEvent,
123
+ ctx.stream,
124
+ ) as { content: string; parsedOutput?: unknown; durationMs?: number; error?: string };
125
+ const outcome: AgentOutcome = {
126
+ content: wfResult.content,
127
+ parsedOutput: wfResult.parsedOutput,
128
+ durationMs: wfResult.durationMs,
129
+ error: wfResult.error,
130
+ engineId: "pi",
131
+ };
132
+ return {
133
+ handle: {
134
+ data: { v: 1, engineId: "pi", sessionRef: {}, poolKey: "shared", adapterVersion: "fake-sar" },
135
+ },
136
+ outcome,
137
+ };
138
+ },
139
+ interact: async () => ({ ok: false, code: "engine_interact_failed", message: "not supported in this test" }),
140
+ read: async () => ({ engineId: "pi", turns: [], source: "outcome-only" }),
141
+ };
142
+ clearEngines();
143
+ registerEngine("pi", () => fakePort);
61
144
  return service;
62
145
  }
63
146
 
@@ -83,6 +166,9 @@ function makeBaseOpts(): AgentCallOpts {
83
166
  describe("SubprocessAgentRunner (wave-4 delegate)", () => {
84
167
  beforeEach(() => {
85
168
  configureCore({ dataRoot: () => "/fake-sar-data-root", log: () => {} });
169
+ // [U-2 一致性修复] pi 未注册时 resolveHostPiEnginePort 返回 engine_not_found stub
170
+ // ——替身 pi 引擎在 createMockService 内按测试服务注册([W3] 委托式协议替身)。
171
+ engineRunSpy.mockClear();
86
172
  });
87
173
 
88
174
  afterEach(() => {
@@ -441,7 +527,7 @@ describe("SubprocessAgentRunner (wave-4 delegate)", () => {
441
527
  const workflowOnEvent = vi.fn();
442
528
  await sar.run(makeBaseOpts(), new AbortController().signal, workflowOnEvent);
443
529
 
444
- // journal 落在 <dataDir>/engines/pi/shared/journal-<taskId>.jsonl
530
+ // journal 落在 <dataDir>/inproc pi 引擎目录/shared/journal-<taskId>.jsonl
445
531
  const journalFile = join(dataDir, "engines", "pi", "shared");
446
532
  const files = readdirSync(journalFile).filter((f) => f.startsWith("journal-") && f.endsWith(".jsonl"));
447
533
  expect(files.length).toBe(1);
@@ -491,17 +577,14 @@ describe("SubprocessAgentRunner (wave-4 delegate)", () => {
491
577
  });
492
578
 
493
579
  // ────────────────────────────────────────────────
494
- // T3.19: AgentCallOptsExecuteOptions 映射保真
580
+ // T3.19: SARengine.run 任务声明直传保真
581
+ // [W3 改写] 「AgentCallOpts → ExecuteOptions 映射点在 PiEngine 边界」随 inproc pi 引擎目录
582
+ // 删除迁入 pi-subagent-cli(引擎包 spawn-args 测试守护);core 侧存留契约 =
583
+ // SAR 对 AgentCallOpts 直传零映射(engine.run 的 task 原样性)。
495
584
  // ────────────────────────────────────────────────
496
- describe("T3.19 直出保真(D6:映射点在 PiEngine.agentCallToExecuteOptions,SAR 直传零映射)", () => {
497
- it("prompt → task, agent → agent, schemaEnv 透传(经 pi 边界直出)", async () => {
498
- let capturedOpts: Record<string, unknown> | undefined;
499
- const mockService = createMockService(
500
- vi.fn().mockImplementation((opts: Record<string, unknown>) => {
501
- capturedOpts = opts;
502
- return Promise.resolve(makeMockResult());
503
- }),
504
- );
585
+ describe("T3.19 直传保真(D6:SAR 直传零映射,任务声明原样到达 engine.run)", () => {
586
+ it("prompt/agent/skillPath/schemaEnv 原样到达 engine.run", async () => {
587
+ const mockService = createMockService();
505
588
  const deps: SubprocessAgentRunnerDeps = { subagentService: mockService };
506
589
  const sar = new SubprocessAgentRunner(deps);
507
590
 
@@ -514,35 +597,24 @@ describe("SubprocessAgentRunner (wave-4 delegate)", () => {
514
597
  };
515
598
  await sar.run(opts, new AbortController().signal);
516
599
 
517
- expect(capturedOpts).toBeDefined();
518
- expect(capturedOpts!.task).toBe("do the thing");
519
- expect(capturedOpts!.agent).toBe("code-reviewer");
520
- expect(capturedOpts!.skillPath).toBe("/skills/code-review.md");
521
- // schemaEnv 应通过 ExecuteOptions 透传
522
- expect((capturedOpts as Record<string, unknown>).schemaEnv).toBe('{"type":"object"}');
600
+ expect(engineRunSpy).toHaveBeenCalledTimes(1);
601
+ const [task] = engineRunSpy.mock.calls[0] as [AgentCallOpts, unknown];
602
+ expect(task.prompt).toBe("do the thing");
603
+ expect(task.agent).toBe("code-reviewer");
604
+ expect(task.skillPath).toBe("/skills/code-review.md");
605
+ expect(task.schemaEnv).toBe('{"type":"object"}');
523
606
  });
524
607
 
525
- it("schema 透传为原始对象", async () => {
526
- let capturedOpts: Record<string, unknown> | undefined;
527
- const mockService = createMockService(
528
- vi.fn().mockImplementation((opts: Record<string, unknown>) => {
529
- capturedOpts = opts;
530
- return Promise.resolve(makeMockResult());
531
- }),
532
- );
608
+ it("schema 原样透传为原始对象", async () => {
609
+ const mockService = createMockService();
533
610
  const deps: SubprocessAgentRunnerDeps = { subagentService: mockService };
534
611
  const sar = new SubprocessAgentRunner(deps);
535
612
 
536
- const opts = {
537
- ...makeBaseOpts(),
538
- schema: { type: "object", properties: { name: { type: "string" } } },
539
- };
540
- await sar.run(opts, new AbortController().signal);
613
+ const schema = { type: "object", properties: { name: { type: "string" } } };
614
+ await sar.run({ ...makeBaseOpts(), schema }, new AbortController().signal);
541
615
 
542
- expect(capturedOpts!.schema).toEqual({
543
- type: "object",
544
- properties: { name: { type: "string" } },
545
- });
616
+ const [task] = engineRunSpy.mock.calls[0] as [AgentCallOpts, unknown];
617
+ expect(task.schema).toEqual(schema);
546
618
  });
547
619
  });
548
620
 
@@ -62,35 +62,18 @@ import * as path from "node:path";
62
62
 
63
63
  import { afterEach, beforeEach, describe, expect, it, vi, type Mock } from "vitest";
64
64
 
65
- const { loggerMock, runSpawnMock } = vi.hoisted(() => ({
65
+ const { loggerMock } = vi.hoisted(() => ({
66
66
  loggerMock: { debug: vi.fn(), warn: vi.fn(), error: vi.fn(), info: vi.fn() },
67
- runSpawnMock: vi.fn(async () => ({
68
- text: "ok",
69
- turns: 1,
70
- durationMs: 10,
71
- success: true,
72
- sessionId: "spawned",
73
- toolCalls: [],
74
- })),
75
67
  }));
76
68
  // mock logger:路径从 __tests__ 出发是 ../../core/(src/core/logger.ts——subagent-service
77
69
  // 经 ../core/logger.ts 引用的同一模块)。曾写 ../core/logger.ts 指向不存在的
78
70
  // src/execution/core/,vi.mock 静默失效(D4 上限用例首次断言日志时暴露)。
79
71
  vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
80
72
 
81
- // mock session-runnerexecute 链经 kickOffBackground runAndFinalize runSpawn。
82
- // 路径必须命中生产 import 的真实模块——merge session-runner 落位
83
- // src/execution/engine/engines/pi/session-runner.ts(subagent-service.ts 的 import 源),
84
- // 旧路径 "../session-runner.ts" 模块已不存在,拦截静默失效(U2 教训的变体:
85
- // 相对路径「层级」与「落位」都可能漂移,见 collect-coordinator-service.test.ts)。
86
- vi.mock("../engine/engines/pi/session-runner.ts", () => ({
87
- runSpawn: runSpawnMock,
88
- killAllSpawnedChildren: vi.fn(),
89
- killRecordChildWithEscalation: vi.fn(),
90
- getChildByRecord: vi.fn(() => undefined),
91
- registerSpawnedChildForRecord: vi.fn(),
92
- spawnedChildren: new Map(),
93
- }));
73
+ // [W3 改写] mock session-runnerexecute runSpawn 替身)随 inproc pi 引擎目录 删除消亡——
74
+ // execute 链改为协议 seam(registerFakePiEngine 替身 + 显式 settle 应答)。
75
+ import { registerFakePiEngine, type FakePiEnginePort } from "./helpers/fake-engine-port.ts";
76
+ import { clearEngines } from "../engine/registry.ts";
94
77
 
95
78
  import { SUBAGENT_RECORD_CUSTOM_TYPE } from "../record-entry.ts";
96
79
  import { ManifestStore } from "../manifest-store.ts";
@@ -275,6 +258,12 @@ describe("sync collect recovery (U5 E1/E9) — 真实文件通路", () => {
275
258
 
276
259
  /** 恢复侧 service:断言 pi(覆写不落盘,断言内存面)/ 写文件 pi(覆写真实落盘,
277
260
  * E1 读覆写后末条——kill -9 真实链路同构)两用。 */
261
+ /** [W3] 协议替身注册(per-test 调用;afterEach clearEngines 统一清理)。 */
262
+ function makeRecoveryFake(): FakePiEnginePort {
263
+ clearEngines();
264
+ return registerFakePiEngine();
265
+ }
266
+
278
267
  function makeRecoveryService(pi: AssertPi | WritingPi): SubagentService {
279
268
  const modelService = new ModelConfigService({ agentDir, cwd: agentDir });
280
269
  const modelRegistry: ModelRegistryLike = {
@@ -564,15 +553,13 @@ describe("sync collect recovery (U5 E1/E9) — 真实文件通路", () => {
564
553
 
565
554
  it("E9 dispose:缓冲终态成员逐条转 async notify + 落标;在跑成员走现有退出路径(不通知)", async () => {
566
555
  // 复用 execute 真链:成员 1 受控终态(入缓冲后批因成员 2 在跑不闭合),成员 2 悬置 running
567
- let resolve1!: (v: { text: string; turns: number; durationMs: number; success: boolean; sessionId: string; toolCalls: [] }) => void;
568
- runSpawnMock.mockImplementationOnce(() => new Promise((res) => { resolve1 = res; }));
569
- runSpawnMock.mockImplementationOnce(() => new Promise(() => { /* 悬置到 dispose */ }));
556
+ const fake = makeRecoveryFake();
570
557
  const pi = makeAssertPi();
571
558
  const service = makeRecoveryService(pi);
572
559
  const spy = spyNotifier(service);
573
560
  const h1 = await service.execute({ task: "terminal one", slug: "one", collect: "sync" });
574
561
  await service.execute({ task: "running two", slug: "two", collect: "sync" });
575
- await until(() => runSpawnMock.mock.calls.length >= 2);
562
+ await until(() => fake.runs.length >= 2);
576
563
 
577
564
  // 成员 1 子文件就位(E9 落标 getFullRecord 冷路径数据源)
578
565
  const sessionsDir = getSubagentSessionDir(agentDir, agentDir);
@@ -599,9 +586,10 @@ describe("sync collect recovery (U5 E1/E9) — 真实文件通路", () => {
599
586
  );
600
587
 
601
588
  // 成员 1 终态:入缓冲,成员 2 在跑 → 批不闭合(零批投递)
602
- resolve1({ text: "ok-terminal", turns: 1, durationMs: 10, success: true, sessionId: "spawned", toolCalls: [] });
603
- await until(() => spy.notify.mock.calls.length + spy.notifyBatch.mock.calls.length > 0 || runSpawnMock.mock.calls.length >= 2);
604
- await new Promise((resolve) => setTimeout(resolve, 50)); // microtask 链排空
589
+ fake.runs[0]!.settle({ content: "ok-terminal" });
590
+ // settle 收尾链排空(finalize→route 入缓冲;成员 2 悬置 零投递)
591
+ await new Promise((resolve) => setTimeout(resolve, 100));
592
+ expect(spy.notify.mock.calls.length + spy.notifyBatch.mock.calls.length).toBe(0);
605
593
  expect(spy.notifyBatch).not.toHaveBeenCalled();
606
594
 
607
595
  // dispose(E9):成员 1 逐条转 async 写账 + 落标;成员 2 不通知(现有退出路径)
@@ -1071,23 +1059,20 @@ describe("sync collect recovery (U5 E1/E9) — 真实文件通路", () => {
1071
1059
  // 成员 1:失败终态(archive 出内存)且不写子文件 → getFullRecord 双 miss
1072
1060
  //(内存已出 + 子文件缺失——子文件被 GC/删除的窗口形态);
1073
1061
  // 成员 2:SP-5 成功回退(留内存)→ getFullRecord 内存命中(正常落标对照)。
1074
- let resolve1!: (v: { text: string; turns: number; durationMs: number; success: boolean; sessionId: string; toolCalls: [] }) => void;
1075
- let resolve2!: (v: { text: string; turns: number; durationMs: number; success: boolean; sessionId: string; toolCalls: [] }) => void;
1076
- runSpawnMock.mockImplementationOnce(() => new Promise((res) => { resolve1 = res; }));
1077
- runSpawnMock.mockImplementationOnce(() => new Promise((res) => { resolve2 = res; }));
1062
+ const fake = makeRecoveryFake();
1078
1063
  // 写文件 pi:标记 entry 真实落盘主文件,E1 扫描通路(rebuildEntryRecord)可重解析
1079
1064
  const pi = makeWritingPi(mainFile);
1080
1065
  const service = makeRecoveryService(pi);
1081
1066
  const spy = spyNotifier(service);
1082
1067
  const h1 = await service.execute({ task: "miss one", slug: "one", collect: "sync" });
1083
1068
  const h2 = await service.execute({ task: "full two", slug: "two", collect: "sync" });
1084
- await until(() => runSpawnMock.mock.calls.length >= 2);
1069
+ await until(() => fake.runs.length >= 2);
1085
1070
 
1086
1071
  // 成员 1 子文件故意缺失(getFullRecord miss 前提);成员 2 内存命中不依赖子文件。
1087
1072
  // 注:两成员的 settle 链竞态可能拆两批([U8] 合批窗口形态)——S11 断言与批切分
1088
1073
  // 无关,只锁「批成员并集覆盖」与「miss 成员兜底落标」。
1089
- resolve1({ text: "boom-miss", turns: 1, durationMs: 10, success: false, sessionId: "spawned", toolCalls: [] });
1090
- resolve2({ text: "ok-full", turns: 1, durationMs: 10, success: true, sessionId: "spawned", toolCalls: [] });
1074
+ fake.runs[0]!.settle({ content: "boom-miss", error: "boom-miss" });
1075
+ fake.runs[1]!.settle({ content: "ok-full" });
1091
1076
  await until(() => spy.notifyBatch.mock.calls.length > 0);
1092
1077
  await new Promise((resolve) => setTimeout(resolve, 50)); // flushBatch 落标同步链排空
1093
1078
 
@@ -0,0 +1,232 @@
1
+ // src/execution/__tests__/workflow-state-root.test.ts
2
+ //
3
+ // [F-1 修复] pi 宿主 WorkflowRun state 读侧装配同源布局测试。
4
+ //
5
+ // 背景(装配错位事故面):round-supervisor sweep 与 idle-gc 曾用 FileRunStore 缺省根
6
+ // `<dataRoot>/workflow-state`(zcode 宿主布局)读 workflow run state,而 pi 宿主真实
7
+ // 落盘 = JsonlRunStore 的 `<sessionDir>/workflow-state/<runId>.jsonl`——两目录生产不
8
+ // 相交 → findStateByIdSync 恒 missing → sweep 按终态补注销**活跃 run**;WorkflowRun
9
+ // GC 恒空转。修复后装配点传 resolvePiWorkflowStateDir()(execution/workflow-state-root.ts,
10
+ // 与 extensions/universal/subagent-workflow/src/session-lifecycle.ts resolveSessionDir
11
+ // 同规则重写)。
12
+ //
13
+ // 本套件用 mkdtemp 真实目录布局(非 mock fs)证明四件事:
14
+ // ① resolvePiWorkflowStateDir 探测语义两分支(sessionScopedDir 存在/不存在);
15
+ // ② FileRunStore({stateDir}) findStateByIdSync 在真实布局命中 running/终态/missing;
16
+ // ③ sweep 装配链(runPendingReconcileSweepForService,env 指向 tmp agentDir)端到端:
17
+ // running run 不补注销(修复前被误注销的事故方向)、终态 run 补注销;
18
+ // ④ idle-gc 同布局:30 天 running run 文件被终态化写回(findStateByIdSync 变 done)。
19
+ //
20
+ // 写删目标全部 mkdtempSync 自建自删(禁触真实数据目录纪律)。
21
+
22
+ import * as fs from "node:fs";
23
+ import * as os from "node:os";
24
+ import * as path from "node:path";
25
+
26
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
27
+
28
+ import { FileRunStore } from "../../orchestration/file-run-store.ts";
29
+ import { Budget } from "../../orchestration/models/budget.ts";
30
+ import { Trace } from "../../orchestration/models/trace.ts";
31
+ import { WorkflowRun } from "../../orchestration/models/workflow-run.ts";
32
+ import { startIdleGc } from "../idle-gc.ts";
33
+ import { RecordStore } from "../record-store.ts";
34
+ import { runPendingReconcileSweepForService } from "../round-supervisor/service-binding.ts";
35
+ import type { RoundSupervisorBinding } from "../round-supervisor/service-binding.ts";
36
+ import { resolvePiWorkflowStateDir } from "../workflow-state-root.ts";
37
+
38
+ const DAY_MS = 24 * 60 * 60 * 1000;
39
+ const GC_INTERVAL_MS = 60 * 60 * 1000;
40
+
41
+ let tmpDir: string;
42
+ let stopGc: (() => void) | undefined;
43
+
44
+ beforeEach(() => {
45
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "wf-state-root-"));
46
+ });
47
+
48
+ afterEach(() => {
49
+ stopGc?.();
50
+ stopGc = undefined;
51
+ vi.unstubAllEnvs();
52
+ vi.useRealTimers();
53
+ fs.rmSync(tmpDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
54
+ });
55
+
56
+ /** 与 session-lifecycle.resolveSessionDir 同规则的 cwd-slug(测试构造期望布局用)。 */
57
+ function slugOf(cwd: string): string {
58
+ return `--${cwd.replace(/^\//, "").replace(/\//g, "-")}--`;
59
+ }
60
+
61
+ /** 构造可持久化的 WorkflowRun(对齐 file-run-store.test.ts makeRun 模式)。 */
62
+ function makeRun(
63
+ runId: string,
64
+ opts: { status?: "running" | "done"; startedAt?: string } = {},
65
+ ): WorkflowRun {
66
+ const status = opts.status ?? "running";
67
+ return WorkflowRun.reconstruct(
68
+ runId,
69
+ {
70
+ scriptSource: "export function execute() { return 'ok'; }",
71
+ args: { topic: "demo" },
72
+ scriptName: "test-script",
73
+ scriptPath: "/fake/test.js",
74
+ parameters: { type: "object" },
75
+ budgetTokens: 1000,
76
+ },
77
+ {
78
+ status,
79
+ ...(status === "done" ? { reason: "completed" as const } : {}),
80
+ budget: new Budget({ maxTokens: 1000, usedTokens: 1, usedCost: 0, totalCallCount: 1 }),
81
+ calls: new Map(),
82
+ trace: new Trace(),
83
+ errorLogs: [],
84
+ scriptResult: undefined,
85
+ },
86
+ { startedAt: opts.startedAt ?? "2026-08-30T00:00:00.000Z" },
87
+ );
88
+ }
89
+
90
+ describe("resolvePiWorkflowStateDir 探测语义(session-lifecycle resolveSessionDir 同规则)", () => {
91
+ it("sessionScopedDir 存在 → 用 <agentDir>/sessions/<slug>/workflow-state(实例隔离布局)", () => {
92
+ const cwd = path.join(tmpDir, "proj"); // 形式 cwd(不要求真实存在)
93
+ const agentDir = path.join(tmpDir, "agent");
94
+ const sessionScopedDir = path.join(agentDir, "sessions", slugOf(cwd));
95
+ fs.mkdirSync(sessionScopedDir, { recursive: true });
96
+ expect(resolvePiWorkflowStateDir({ agentDir, cwd })).toBe(path.join(sessionScopedDir, "workflow-state"));
97
+ });
98
+
99
+ it("sessionScopedDir 不存在 → 回退 <agentDir>/workflow-state(JsonlRunStore 首写 mkdir 的根布局)", () => {
100
+ const agentDir = path.join(tmpDir, "agent");
101
+ fs.mkdirSync(agentDir, { recursive: true });
102
+ expect(resolvePiWorkflowStateDir({ agentDir, cwd: path.join(tmpDir, "fresh-proj") })).toBe(
103
+ path.join(agentDir, "workflow-state"),
104
+ );
105
+ });
106
+ });
107
+
108
+ describe("FileRunStore({stateDir}) × 真实 JsonlRunStore 布局(findStateByIdSync 读侧判据)", () => {
109
+ it("真实布局命中:running → {kind:running};done → {kind:terminal, reason};无文件 → {kind:missing}", async () => {
110
+ const stateDir = path.join(tmpDir, "sessions", slugOf("/x/y"), "workflow-state");
111
+ const store = new FileRunStore({ stateDir });
112
+ await store.save(makeRun("wf-live")); // running(首写不节流)
113
+ await store.save(makeRun("wf-done", { status: "done" }));
114
+
115
+ expect(store.findStateByIdSync("wf-live")).toEqual({ kind: "running" });
116
+ expect(store.findStateByIdSync("wf-done")).toEqual({ kind: "terminal", reason: "completed" });
117
+ expect(store.findStateByIdSync("wf-never")).toEqual({ kind: "missing" });
118
+ // 落盘路径形状与 pi 壳 JsonlRunStore 同构:<sessionDir>/workflow-state/<runId>.jsonl
119
+ expect(fs.existsSync(path.join(stateDir, "wf-live.jsonl"))).toBe(true);
120
+ });
121
+ });
122
+
123
+ describe("sweep 装配链端到端(runPendingReconcileSweepForService × 真实布局)", () => {
124
+ /**
125
+ * 装配 harness:env 指向 tmp agentDir(生产装配 resolvePiWorkflowStateDir() 无参走
126
+ * env + process.cwd()),run state 由 FileRunStore({stateDir}) 写入解析出的目录——
127
+ * 证明「生产装配点读的目录 = run state 真实落盘目录」(同源布局闭环)。
128
+ */
129
+ function setupSweep(): { agentDir: string; store: FileRunStore } {
130
+ const agentDir = path.join(tmpDir, "agent");
131
+ vi.stubEnv("PI_CODING_AGENT_DIR", agentDir);
132
+ const store = new FileRunStore({ stateDir: resolvePiWorkflowStateDir() }); // 与生产装配同参
133
+ return { agentDir, store };
134
+ }
135
+
136
+ function makeBinding(sessionFile: string, appended: Array<{ customType: string; data: unknown }>): RoundSupervisorBinding {
137
+ return {
138
+ getStore: () => new RecordStore(path.join(tmpDir, "records")),
139
+ getPi: () =>
140
+ ({
141
+ appendEntry: (type: string, data: unknown) => appended.push({ customType: type, data }),
142
+ events: { emit: vi.fn() },
143
+ sendMessage: vi.fn(),
144
+ }) as unknown as NonNullable<ReturnType<RoundSupervisorBinding["getPi"]>>,
145
+ getSessionRootId: () => "root",
146
+ getMainSessionFile: () => sessionFile,
147
+ finalizeClosed: vi.fn(),
148
+ };
149
+ }
150
+
151
+ function writeRegister(sessionFile: string, id: string): void {
152
+ fs.writeFileSync(
153
+ sessionFile,
154
+ JSON.stringify({ customType: "pending:register", data: { id, type: "workflow", name: id } }) + "\n",
155
+ "utf-8",
156
+ );
157
+ }
158
+
159
+ it("活跃 workflow run(running state 文件在盘)→ sweep 不补注销(修复前被误注销)", async () => {
160
+ const { agentDir, store } = setupSweep();
161
+ await store.save(makeRun("wf-live"));
162
+
163
+ const sessionFile = path.join(agentDir, "main-session.jsonl");
164
+ writeRegister(sessionFile, "wf-live");
165
+ const appended: Array<{ customType: string; data: unknown }> = [];
166
+ runPendingReconcileSweepForService(makeBinding(sessionFile, appended), false);
167
+ expect(appended).toHaveLength(0); // 活跃 run 不注销——事故方向的回归钉
168
+ });
169
+
170
+ it("终态 workflow run(done state 文件在盘)→ sweep 补注销(reason 取 run reason)", async () => {
171
+ const { agentDir, store } = setupSweep();
172
+ await store.save(makeRun("wf-done", { status: "done" }));
173
+
174
+ const sessionFile = path.join(agentDir, "main-session.jsonl");
175
+ writeRegister(sessionFile, "wf-done");
176
+ const appended: Array<{ customType: string; data: unknown }> = [];
177
+ runPendingReconcileSweepForService(makeBinding(sessionFile, appended), false);
178
+ expect(appended).toEqual([
179
+ { customType: "pending:unregister", data: { id: "wf-done", reason: "completed", status: "completed" } },
180
+ ]);
181
+ });
182
+ });
183
+
184
+ describe("idle-gc 同布局(WorkflowRun GC 读对根)", () => {
185
+ /**
186
+ * GC interval 用 fake timers 推进;但 gcWorkflowRuns 的 loadAll/save 是**真实 fs IO**
187
+ * (libuv 线程池回调不属被 fake 的 timer API)——推进后经 setImmediate(toFake 排除,
188
+ * 保持真实调度)反复让出事件循环排空在途 IO。
189
+ */
190
+ async function flushRealIo(): Promise<void> {
191
+ for (let i = 0; i < 50; i++) {
192
+ await new Promise<void>((resolve) => setImmediate(() => resolve()));
193
+ }
194
+ }
195
+
196
+ it("真实布局 30 天 running run → GC 终态化写回(findStateByIdSync 变 terminal/time_limited)", async () => {
197
+ vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout", "setInterval", "clearInterval"] });
198
+ const agentDir = path.join(tmpDir, "agent");
199
+ const cwd = path.join(tmpDir, "proj");
200
+ const sessionScopedDir = path.join(agentDir, "sessions", slugOf(cwd));
201
+ fs.mkdirSync(sessionScopedDir, { recursive: true });
202
+ const stateDir = resolvePiWorkflowStateDir({ agentDir, cwd });
203
+ const runStore = new FileRunStore({ stateDir });
204
+ await runStore.save(
205
+ makeRun("wf-stale", { startedAt: new Date(Date.now() - 31 * DAY_MS).toISOString() }),
206
+ );
207
+ expect(runStore.findStateByIdSync("wf-stale")).toEqual({ kind: "running" });
208
+
209
+ stopGc = startIdleGc(new RecordStore(path.join(tmpDir, "records")), runStore);
210
+ await vi.advanceTimersByTimeAsync(GC_INTERVAL_MS + 1);
211
+ await flushRealIo();
212
+
213
+ // 终态已落盘(同目录 append 终态快照行)——GC 读侧与落盘侧同根闭环
214
+ expect(runStore.findStateByIdSync("wf-stale")).toEqual({ kind: "terminal", reason: "time_limited" });
215
+ });
216
+
217
+ it("窗内 running run 不动(GC 判据在正确根上按 startedAt 生效)", async () => {
218
+ vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout", "setInterval", "clearInterval"] });
219
+ const agentDir = path.join(tmpDir, "agent");
220
+ const stateDir = resolvePiWorkflowStateDir({ agentDir, cwd: tmpDir });
221
+ const runStore = new FileRunStore({ stateDir });
222
+ await runStore.save(
223
+ makeRun("wf-fresh", { startedAt: new Date(Date.now() - 1 * DAY_MS).toISOString() }),
224
+ );
225
+
226
+ stopGc = startIdleGc(new RecordStore(path.join(tmpDir, "records")), runStore);
227
+ await vi.advanceTimersByTimeAsync(GC_INTERVAL_MS + 1);
228
+ await flushRealIo();
229
+
230
+ expect(runStore.findStateByIdSync("wf-fresh")).toEqual({ kind: "running" });
231
+ });
232
+ });
@@ -20,7 +20,10 @@ import { parseResourceMeta } from "../shared/meta-parser.ts";
20
20
  import { lintAgentMeta } from "../orchestration/script-lint.ts";
21
21
  import type { AgentMeta, RoutingExample } from "../shared/resource-meta.ts";
22
22
  import type { AgentConfig } from "./model-resolver.ts";
23
- import { hasEngine, listEngines, EngineNotFoundError } from "./engine/registry.ts";
23
+ import { EngineNotFoundError, listEngines } from "./engine/registry.ts";
24
+ // [W8] 存在性校验经补扫通道(快照未命中触发一次三级补扫,W4 ensureEngineDiscovered
25
+ // 语义——宿主未接线补扫参数时与裸 hasEngine 等价)。
26
+ import { hasEngineWithRescan } from "./engine/routing.ts";
24
27
 
25
28
  const logger = getLogger("subagents");
26
29
 
@@ -134,7 +137,8 @@ function resolveAgentEngine(
134
137
  filePath: string,
135
138
  ): string | undefined {
136
139
  const engine = agentMeta?.engine ?? extractYamlField(yamlBlock, "engine");
137
- if (engine !== undefined && !hasEngine(engine)) {
140
+ // [W8] 补扫通道:快照未命中先一次三级补扫再判(装了引擎包 下次解析即可用)。
141
+ if (engine !== undefined && !hasEngineWithRescan(engine)) {
138
142
  throw new EngineNotFoundError(engine, listEngines(), filePath);
139
143
  }
140
144
  return engine;
@@ -84,79 +84,12 @@ function dialogTimeoutLogMessage(req: UiRequest, waitedMs: number): string {
84
84
  );
85
85
  }
86
86
 
87
- // ── 类型定义(本模块是 UiRequest/UiResponse/UiRequestHandler 的规范来源) ──
88
- // session-runner.ts 和 ui-channels.ts 复用这些类型(session-runner 再导出供测试 import)。
89
-
90
- /** Pi extension_ui_request 的方法枚举(dialog + fire-and-forget 两类)。
91
- * dialog 类:select/confirm/input/editor(占输入焦点,等响应)。
92
- * fire-and-forget 类:notify/setStatus/setWidget/setTitle/set_editor_text(纯展示/写入)。
93
- * (string & {}) 兜底:Pi 未来新增 method 或未知 method 走字符串字面量类型。 */
94
- export type UiMethod =
95
- | "select"
96
- | "confirm"
97
- | "input"
98
- | "editor"
99
- | "notify"
100
- | "setStatus"
101
- | "setWidget"
102
- | "setTitle"
103
- | "set_editor_text"
104
- | (string & {});
105
-
106
- /** UI 请求(session-runner 构造后传给 handler)。
107
- *
108
- * method 是判别字段,决定排队策略(dialog 排队)和业务路由(channel 分发)。
109
- * method 特定字段按 method 可选出现(与 ExtensionUiRequest 1:1,由 session-runner 从
110
- * ExtensionUiRequest 平铺构造)。channel/channelPayload 由 parseChannel 填充。
111
- *
112
- * 契约来源:.fix-plans/00-master-summary.md §二 2.2。 */
113
- export interface UiRequest {
114
- /** Pi rpc-types.ts 的 method(select/confirm/input/editor 为 dialog 类)。 */
115
- method: UiMethod;
116
- /** 请求 id(从 extension_ui_request envelope 顶层提取,用于 response 关联)。 */
117
- id: string;
118
- // method 特定字段(按 method 可选,与 ExtensionUiRequest 1:1)
119
- title?: string;
120
- options?: string[];
121
- message?: string;
122
- placeholder?: string;
123
- prefill?: string;
124
- notifyType?: string;
125
- statusKey?: string;
126
- statusText?: string | undefined;
127
- widgetKey?: string;
128
- widgetLines?: string[] | undefined;
129
- widgetPlacement?: "aboveEditor" | "belowEditor";
130
- text?: string;
131
- timeout?: number;
132
- /** channel 名(从 method 对应字段的 NUL 前缀解析)。
133
- * select → 从 title 解析;setWidget → 从 widgetLines[0] 解析;其他 → undefined。
134
- * 已知值:"ask_user"(select)、"gui_widget"(setWidget)。handler 按 channel 分发。 */
135
- channel?: string;
136
- /** channel 解析后的结构化 payload(已 JSON.parse)。
137
- * ask_user: {questions, allowCancel};gui_widget: {component};无 channel: undefined。 */
138
- channelPayload?: unknown;
139
- /** 内部元数据字段:发起该 UI 请求的子进程 pid(由 session-runner.handleUiRequest 从
140
- * child.pid 填入)。L2 队列据此关联 rejectChildDialogs(child close 时批量 reject)。
141
- * 下划线前缀表示内部字段,非 Pi 协议字段,不参与 stdin 回写。 */
142
- _childPid?: number;
143
- }
144
-
145
- /** UI 响应(handler 返回,session-runner 按 shape 回写 stdin)。
146
- * - {value}: select/input/editor 的答案
147
- * - {confirmed}: confirm 的答案
148
- * - {cancelled}: 取消(child close / handler 抛错 / 用户取消)
149
- * - {ack}: fire-and-forget(当前不透传到 TUI,留作协议完整) */
150
- export type UiResponse =
151
- | { value: string }
152
- | { confirmed: boolean }
153
- | { cancelled: true }
154
- | { ack: true };
155
-
156
- /** UI 请求 handler 签名(单函数,按 req.method 内部路由)。
157
- * 实现方负责:channel 业务路由(ask_user → AskUserComponent)+ 默认转发(ctx.ui.*)。
158
- * 抛错由调用方(DialogGlobalQueue / session-runner)兜底为 {cancelled:true}。 */
159
- export type UiRequestHandler = (req: UiRequest) => Promise<UiResponse>;
87
+ // ── 类型再导出(W7 / R3 S-A + R4-S③:SSOT 迁 @zhushanwen/subagent-engine-sdk
88
+ // src/ui-types.ts,结构等价;队列实现本体留 core——壳侧 host/askUser 消费端与
89
+ // inproc 过渡链路同样消费该类型面。双向可赋值断言挂本包 typecheck 断言族
90
+ // (contract-closure),漂移编译期可抓。 ──
91
+ export type { UiMethod, UiRequest, UiRequestHandler, UiResponse } from "@zhushanwen/subagent-engine-sdk";
92
+ import type { UiRequest, UiRequestHandler, UiResponse } from "@zhushanwen/subagent-engine-sdk";
160
93
 
161
94
  // ── DialogGlobalQueue 实现 ──
162
95