@zhushanwen/subagent-core 0.4.0 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (272) hide show
  1. package/dist/{chunk-4KN4TTG7.js → chunk-43ONBFZX.js} +1 -1
  2. package/dist/{chunk-X7SZ5HLQ.js → chunk-V3VHZ2VX.js} +20 -27
  3. package/dist/execution/engine/engines/zcode/constants.cjs +29 -41
  4. package/dist/execution/engine/engines/zcode/constants.d.cts +65 -53
  5. package/dist/execution/engine/engines/zcode/constants.d.ts +65 -53
  6. package/dist/execution/engine/engines/zcode/constants.js +17 -27
  7. package/dist/execution/engine/engines/zcode/reader.d.cts +2 -1
  8. package/dist/execution/engine/engines/zcode/reader.d.ts +2 -1
  9. package/dist/execution/engine/engines/zcode/reader.js +2 -2
  10. package/dist/index.cjs +16418 -14070
  11. package/dist/index.d.cts +5136 -4016
  12. package/dist/index.d.ts +5136 -4016
  13. package/dist/index.js +16343 -14048
  14. package/dist/{types-DpUO16pj.d.cts → types-Dv4QhSJ_.d.cts} +215 -112
  15. package/dist/{types-DpUO16pj.d.ts → types-Dv4QhSJ_.d.ts} +215 -112
  16. package/package.json +3 -3
  17. package/src/__tests__/agent-opts-resolver-schema-prompt.test.ts +0 -1
  18. package/src/__tests__/append-system-prompt-assembly.test.ts +8 -7
  19. package/src/__tests__/fr4-get-state-handshake.test.ts +2 -2
  20. package/src/__tests__/m2-append-content-probe.test.ts +0 -1
  21. package/src/__tests__/manifest-store.test.ts +10 -9
  22. package/src/__tests__/record-store-cache.test.ts +0 -2
  23. package/src/__tests__/record-store-index.test.ts +1 -2
  24. package/src/__tests__/review-fix-loop-script.test.ts +4 -2
  25. package/src/__tests__/review-fix-loop-utils.test.ts +10 -10
  26. package/src/__tests__/robustness-low-batch1.test.ts +6 -5
  27. package/src/__tests__/robustness-medium-batch1.test.ts +2 -2
  28. package/src/__tests__/robustness-medium-batch2.test.ts +17 -6
  29. package/src/__tests__/robustness-medium-batch4.test.ts +2 -2
  30. package/src/__tests__/session-runner.test.ts +1 -1
  31. package/src/core/__tests__/host-services.test.ts +2 -2
  32. package/src/core/__tests__/logger.test.ts +1 -1
  33. package/src/core/error-message.ts +9 -0
  34. package/src/core/host-services.ts +21 -5
  35. package/src/core/notify-ports.ts +18 -5
  36. package/src/execution/__tests__/agent-profile.test.ts +4 -4
  37. package/src/execution/__tests__/agent-registry.test.ts +370 -0
  38. package/src/execution/__tests__/agent-result-mapper.test.ts +18 -2
  39. package/src/execution/__tests__/agents-assembly.test.ts +2 -2
  40. package/src/execution/__tests__/ask-user-transit-e2e.test.ts +6 -6
  41. package/src/execution/__tests__/chat-engine-routing.test.ts +68 -33
  42. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +4 -4
  43. package/src/execution/__tests__/cold-resurrect.test.ts +283 -0
  44. package/src/execution/__tests__/delivery-methods.test.ts +75 -62
  45. package/src/execution/__tests__/descendant-sweep-guards.test.ts +220 -0
  46. package/src/execution/__tests__/descendant-sweep.test.ts +6 -6
  47. package/src/execution/__tests__/dialog-queue.test.ts +2 -2
  48. package/src/execution/__tests__/engine-model-validation.test.ts +741 -0
  49. package/src/execution/__tests__/epipe-fallback.test.ts +29 -24
  50. package/src/execution/__tests__/execute-and-await-worktree.test.ts +21 -91
  51. package/src/execution/__tests__/execute-nesting.test.ts +34 -188
  52. package/src/execution/__tests__/execution-record.test.ts +217 -60
  53. package/src/execution/__tests__/execution-runtime-face.test.ts +5 -5
  54. package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +3 -3
  55. package/src/execution/__tests__/finalize-record.test.ts +173 -17
  56. package/src/execution/__tests__/gc-timer.test.ts +55 -2
  57. package/src/execution/__tests__/get-record-for-action-restart.test.ts +12 -12
  58. package/src/execution/__tests__/get-state-handshake.test.ts +1 -1
  59. package/src/execution/__tests__/helpers/mock-extension-api.ts +1 -1
  60. package/src/execution/__tests__/helpers/session-runner-mocks.ts +113 -0
  61. package/src/execution/__tests__/helpers/spawn-mock.ts +150 -3
  62. package/src/execution/__tests__/helpers/subagent-service-mocks.ts +137 -0
  63. package/src/execution/__tests__/keep-alive-no-progress.test.ts +50 -148
  64. package/src/execution/__tests__/kill-all-escalation.test.ts +2 -2
  65. package/src/execution/__tests__/lifecycle-predicates.test.ts +1 -2
  66. package/src/execution/__tests__/manifest-parentid.test.ts +4 -4
  67. package/src/execution/__tests__/max-turns-to-watchdog-ms.test.ts +1 -1
  68. package/src/execution/__tests__/model-resolver.test.ts +2 -2
  69. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +23 -121
  70. package/src/execution/__tests__/nested-visibility.test.ts +2 -0
  71. package/src/execution/__tests__/notify-ledger.test.ts +973 -18
  72. package/src/execution/__tests__/output-collector.test.ts +117 -6
  73. package/src/execution/__tests__/pi-invocation.test.ts +1 -1
  74. package/src/execution/__tests__/record-store.test.ts +10 -9
  75. package/src/execution/__tests__/records-cwd-isolation.test.ts +2 -2
  76. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +29 -132
  77. package/src/execution/__tests__/recursive-visibility-env.test.ts +55 -179
  78. package/src/execution/__tests__/rpc-mode.test.ts +2 -2
  79. package/src/execution/__tests__/run-and-finalize-anchoring.test.ts +387 -0
  80. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +15 -9
  81. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +4 -4
  82. package/src/execution/__tests__/run-spawn-edges.test.ts +17 -15
  83. package/src/execution/__tests__/run-spawn-integration.test.ts +2 -2
  84. package/src/execution/__tests__/run-spawn-resume.test.ts +2 -2
  85. package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +2 -2
  86. package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +4 -4
  87. package/src/execution/__tests__/service-kill-escalation.test.ts +7 -5
  88. package/src/execution/__tests__/session-context-resolver.test.ts +0 -2
  89. package/src/execution/__tests__/session-file-gc.test.ts +35 -0
  90. package/src/execution/__tests__/session-pending.test.ts +257 -14
  91. package/src/execution/__tests__/session-reconstructor.test.ts +366 -15
  92. package/src/execution/__tests__/session-runner-branch-cache-lru.test.ts +1 -1
  93. package/src/execution/__tests__/session-runner-close-prune.test.ts +2 -2
  94. package/src/execution/__tests__/session-runner-dispatch.test.ts +489 -0
  95. package/src/execution/__tests__/session-runner-epipe.test.ts +3 -3
  96. package/src/execution/__tests__/session-runner-heartbeat-idle-fallback.test.ts +5 -4
  97. package/src/execution/__tests__/session-runner-lifecycle-helpers.test.ts +290 -0
  98. package/src/execution/__tests__/session-runner-schema-env.test.ts +7 -5
  99. package/src/execution/__tests__/settled-watchdog.test.ts +282 -47
  100. package/src/execution/__tests__/spawn-args.test.ts +11 -10
  101. package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +26 -23
  102. package/src/execution/__tests__/spawn-event-adapter.test.ts +1 -1
  103. package/src/execution/__tests__/spawn-worktree-guidance.test.ts +5 -4
  104. package/src/execution/__tests__/spawned-children.test.ts +2 -1
  105. package/src/execution/__tests__/start-sync-model-guard.test.ts +4 -4
  106. package/src/execution/__tests__/stdin-writer.test.ts +6 -4
  107. package/src/execution/__tests__/stream-sink-retirement.test.ts +6 -6
  108. package/src/execution/__tests__/subagent-actions-core.test.ts +42 -22
  109. package/src/execution/__tests__/subagent-service-message-close.test.ts +29 -26
  110. package/src/execution/__tests__/subagent-service-multiproc-guard.test.ts +8 -8
  111. package/src/execution/__tests__/subagent-service-notify-gate.test.ts +27 -22
  112. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +30 -29
  113. package/src/execution/__tests__/subagent-service-recovery-bounds.test.ts +45 -20
  114. package/src/execution/__tests__/subagent-service.test.ts +37 -30
  115. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +88 -12
  116. package/src/execution/__tests__/subprocess-agent-runner-timeout.test.ts +75 -0
  117. package/src/execution/__tests__/subprocess-agent-runner.test.ts +22 -20
  118. package/src/execution/__tests__/temp-prompt.test.ts +1 -1
  119. package/src/execution/__tests__/timeout-integration.test.ts +6 -5
  120. package/src/execution/__tests__/turn-limiter-semantics.test.ts +1 -1
  121. package/src/execution/__tests__/turn-limiter.test.ts +1 -1
  122. package/src/execution/__tests__/ui-request-handler-factory.test.ts +12 -6
  123. package/src/execution/__tests__/ui-request-handler.test.ts +4 -3
  124. package/src/execution/__tests__/ui-request-queue.test.ts +142 -2
  125. package/src/execution/__tests__/worktree-manager.test.ts +14 -5
  126. package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +4 -3
  127. package/src/execution/agent-registry.ts +74 -30
  128. package/src/execution/agent-result-mapper.ts +3 -0
  129. package/src/execution/cold-resurrect.ts +199 -0
  130. package/src/execution/engine/__tests__/common/capability-gate.test.ts +91 -0
  131. package/src/execution/engine/__tests__/common/data-dir.test.ts +1 -1
  132. package/src/execution/engine/__tests__/common/journal-wiring.test.ts +84 -0
  133. package/src/execution/engine/__tests__/common/kill-chain.test.ts +34 -7
  134. package/src/execution/engine/__tests__/common/persona-router.test.ts +4 -5
  135. package/src/execution/engine/__tests__/common/pool-manager.test.ts +211 -51
  136. package/src/execution/engine/__tests__/common/session-view-service-zcode-dbpath.test.ts +149 -0
  137. package/src/execution/engine/__tests__/common/session-view-service.test.ts +499 -0
  138. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +22 -112
  139. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +15 -28
  140. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +12 -12
  141. package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +1 -1
  142. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +21 -82
  143. package/src/execution/engine/__tests__/conformance/zcode-appserver-harness.ts +4 -7
  144. package/src/execution/engine/__tests__/engine-discovery.test.ts +15 -3
  145. package/src/execution/engine/__tests__/model-prompt.test.ts +15 -3
  146. package/src/execution/engine/__tests__/registry.test.ts +4 -2
  147. package/src/execution/engine/__tests__/routing.test.ts +115 -6
  148. package/src/execution/engine/common/capability-gate.ts +92 -0
  149. package/src/execution/engine/common/journal-wiring.ts +97 -0
  150. package/src/execution/engine/common/kill-chain.ts +42 -10
  151. package/src/execution/engine/common/nesting-guard.ts +66 -0
  152. package/src/execution/engine/common/persona-router.ts +18 -8
  153. package/src/execution/engine/common/pool-manager.ts +332 -78
  154. package/src/execution/engine/common/session-view-service.ts +506 -0
  155. package/src/execution/engine/common/session-view-types.ts +125 -0
  156. package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +174 -28
  157. package/src/execution/engine/engines/pi/__tests__/spawn-opts-direct.test.ts +257 -0
  158. package/src/execution/{argv-mirror.ts → engine/engines/pi/argv-mirror.ts} +43 -21
  159. package/src/execution/{get-state-handshake.ts → engine/engines/pi/get-state-handshake.ts} +1 -1
  160. package/src/execution/{output-collector.ts → engine/engines/pi/output-collector.ts} +108 -19
  161. package/src/execution/engine/engines/pi/pi-engine.ts +434 -38
  162. package/src/execution/{pi-invocation.ts → engine/engines/pi/pi-invocation.ts} +2 -2
  163. package/src/execution/engine/engines/pi/reader.ts +11 -0
  164. package/src/execution/engine/engines/pi/registration.ts +4 -2
  165. package/src/execution/{session-runner.ts → engine/engines/pi/session-runner.ts} +617 -412
  166. package/src/execution/{spawn-event-adapter.ts → engine/engines/pi/spawn-event-adapter.ts} +75 -34
  167. package/src/execution/{stdin-writer.ts → engine/engines/pi/stdin-writer.ts} +10 -9
  168. package/src/execution/{temp-prompt.ts → engine/engines/pi/temp-prompt.ts} +2 -2
  169. package/src/execution/{turn-limiter.ts → engine/engines/pi/turn-limiter.ts} +1 -1
  170. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/fake-appserver.mjs +54 -27
  171. package/src/execution/engine/engines/zcode/__tests__/appserver-launcher.test.ts +321 -0
  172. package/src/execution/engine/engines/zcode/__tests__/connection.test.ts +13 -12
  173. package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +7 -210
  174. package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +6 -110
  175. package/src/execution/engine/engines/zcode/__tests__/session-channel-dispose-harvest.test.ts +247 -0
  176. package/src/execution/engine/engines/zcode/__tests__/session-channel-turn-timers.test.ts +451 -0
  177. package/src/execution/engine/engines/zcode/__tests__/session-channel.test.ts +20 -8
  178. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-appserver.test.ts +77 -110
  179. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-degrade.test.ts +25 -319
  180. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-dispose.test.ts +210 -0
  181. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-retry.test.ts +353 -0
  182. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-status.test.ts +282 -0
  183. package/src/execution/engine/engines/zcode/__tests__/zcode-engine-timeout.test.ts +372 -0
  184. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +48 -148
  185. package/src/execution/engine/engines/zcode/appserver-launcher.ts +167 -0
  186. package/src/execution/engine/engines/zcode/connection.ts +24 -11
  187. package/src/execution/engine/engines/zcode/constants.ts +75 -71
  188. package/src/execution/engine/engines/zcode/golden-sample.ts +8 -46
  189. package/src/execution/engine/engines/zcode/parser.ts +12 -346
  190. package/src/execution/engine/engines/zcode/preparer.ts +12 -143
  191. package/src/execution/engine/engines/zcode/session-channel.ts +351 -39
  192. package/src/execution/engine/engines/zcode/zcode-engine.ts +665 -760
  193. package/src/execution/engine/host-task-spec.ts +32 -36
  194. package/src/execution/engine/model-validation.ts +177 -0
  195. package/src/execution/engine/port.ts +24 -6
  196. package/src/execution/engine/routing.ts +75 -1
  197. package/src/execution/engine/types.ts +30 -103
  198. package/src/execution/execution-record.ts +222 -144
  199. package/src/execution/finalize-record.ts +131 -93
  200. package/src/execution/idle-gc.ts +28 -0
  201. package/src/execution/lifecycle-predicates.ts +1 -1
  202. package/src/execution/notify-host.ts +212 -0
  203. package/src/execution/record-store.ts +119 -73
  204. package/src/execution/round-settlement.ts +93 -0
  205. package/src/execution/session-file-gc.ts +96 -62
  206. package/src/execution/session-reconstructor.ts +256 -143
  207. package/src/execution/sessions-index.ts +117 -47
  208. package/src/execution/settled-watchdog.ts +264 -52
  209. package/src/execution/subagent-actions-core.ts +22 -22
  210. package/src/execution/subagent-service.ts +815 -963
  211. package/src/execution/subprocess-agent-runner.ts +144 -97
  212. package/src/execution/types.ts +9 -0
  213. package/src/execution/ui-request-handler-factory.ts +102 -74
  214. package/src/execution/ui-request-queue.ts +50 -19
  215. package/src/index.ts +442 -316
  216. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +1 -1
  217. package/src/orchestration/__tests__/agent-call-stream.test.ts +2 -2
  218. package/src/orchestration/__tests__/args-validator.test.ts +14 -0
  219. package/src/orchestration/__tests__/error-recovery-rebuild-failure.test.ts +1 -1
  220. package/src/orchestration/__tests__/error-recovery-terminal-hardening.test.ts +11 -9
  221. package/src/orchestration/__tests__/execute-agent-call.test.ts +151 -11
  222. package/src/orchestration/__tests__/file-run-store.test.ts +7 -6
  223. package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +2 -2
  224. package/src/orchestration/__tests__/lifecycle-abort-broadcast-signal.test.ts +1 -1
  225. package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +3 -3
  226. package/src/orchestration/__tests__/lifecycle.test.ts +7 -5
  227. package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +1 -1
  228. package/src/orchestration/__tests__/run-snapshot.test.ts +8 -5
  229. package/src/orchestration/__tests__/script-lint.test.ts +17 -0
  230. package/src/orchestration/__tests__/test-mocks.ts +2 -0
  231. package/src/orchestration/__tests__/worker-exit-without-result.test.ts +7 -6
  232. package/src/orchestration/__tests__/worker-message-pump-finalize-run.test.ts +229 -0
  233. package/src/orchestration/__tests__/{error-recovery-handlers.test.ts → worker-message-pump-handlers.test.ts} +27 -19
  234. package/src/orchestration/__tests__/{error-recovery-postmessage-defense.test.ts → worker-message-pump-postmessage-defense.test.ts} +8 -8
  235. package/src/orchestration/__tests__/{error-recovery-serialize-failed-result.test.ts → worker-message-pump-serialize-failed-result.test.ts} +2 -2
  236. package/src/orchestration/__tests__/{error-recovery-workflow-call.test.ts → worker-message-pump-workflow-call.test.ts} +1 -1
  237. package/src/orchestration/__tests__/worker-pending-timeout-abort.test.ts +1 -1
  238. package/src/orchestration/__tests__/worker-script-builder.test.ts +1 -1
  239. package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +1 -1
  240. package/src/orchestration/agent-opts-resolver.ts +1 -1
  241. package/src/orchestration/args-validator.ts +53 -31
  242. package/src/orchestration/execute-agent-call.ts +19 -73
  243. package/src/orchestration/launcher.ts +128 -81
  244. package/src/orchestration/lifecycle.ts +43 -45
  245. package/src/orchestration/models/ports.ts +4 -4
  246. package/src/orchestration/models/run-runtime.ts +2 -2
  247. package/src/orchestration/models/trace.ts +1 -1
  248. package/src/orchestration/models/types.ts +111 -22
  249. package/src/orchestration/models/workflow-run.ts +1 -1
  250. package/src/orchestration/script-lint.ts +91 -58
  251. package/src/orchestration/{error-recovery.ts → worker-message-pump.ts} +157 -150
  252. package/src/shared/__tests__/meta-parser.test.ts +151 -0
  253. package/src/shared/__tests__/resource-discovery.test.ts +258 -215
  254. package/src/shared/atomic-write.ts +12 -9
  255. package/src/shared/meta-parser.ts +158 -84
  256. package/src/shared/model-ref.ts +15 -2
  257. package/src/shared/resource-discovery.ts +9 -183
  258. package/src/shared/schema-jsonify.ts +1 -1
  259. package/workflows/review-fix-loop-utils.cjs +151 -89
  260. package/workflows/review-fix-loop-utils.d.cts +287 -0
  261. package/src/execution/__tests__/execute-options-mapper.test.ts +0 -200
  262. package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +0 -164
  263. package/src/execution/engine/engines/pi/task-spec-mapper.ts +0 -100
  264. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +0 -39
  265. package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +0 -150
  266. package/src/execution/engine/engines/zcode/__tests__/preparer-appserver.test.ts +0 -387
  267. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +0 -710
  268. package/src/execution/engine/engines/zcode/appserver-home.ts +0 -442
  269. package/src/execution/engine/engines/zcode/appserver-probe.ts +0 -141
  270. package/src/execution/engine/engines/zcode/launcher.ts +0 -161
  271. package/src/execution/execute-options-mapper.ts +0 -115
  272. package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +0 -288
@@ -71,7 +71,7 @@ vi.mock("../alive-store.ts", () => ({
71
71
  writeAliveMarker: vi.fn(),
72
72
  }));
73
73
 
74
- vi.mock("../temp-prompt.ts", () => ({
74
+ vi.mock("../engine/engines/pi/temp-prompt.ts", () => ({
75
75
  writePromptToTempFile: vi.fn(async (agent: string, content: string) => {
76
76
  captured.content = content;
77
77
  const safeName = agent.replace(/[^\w.-]+/g, "_");
@@ -83,7 +83,7 @@ vi.mock("../temp-prompt.ts", () => ({
83
83
  import { spawn } from "node:child_process";
84
84
 
85
85
  import { createRecord } from "../execution-record.ts";
86
- import { runSpawn, type RunOptions, type SessionRunnerContext } from "../session-runner.ts";
86
+ import { runSpawn, type RunOptions, type SessionRunnerContext } from "../engine/engines/pi/session-runner.ts";
87
87
 
88
88
  const mockSpawn = vi.mocked(spawn);
89
89
 
@@ -117,8 +117,9 @@ function makeRecord() {
117
117
  return createRecord("wt-guidance-1", {
118
118
  agent: "general-purpose",
119
119
  model: "test/model",
120
- mode: "sync",
120
+ mode: "background",
121
121
  task: "test task",
122
+ slug: "test",
122
123
  startedAt: Date.now(),
123
124
  rootSessionId: "s1",
124
125
  parentRecordId: undefined,
@@ -128,7 +129,7 @@ function makeRecord() {
128
129
 
129
130
  function makeRunOpts(overrides: Partial<RunOptions> = {}): RunOptions {
130
131
  return {
131
- resolved: { model: { provider: "test", id: "model" }, thinkingLevel: undefined },
132
+ resolved: { model: { provider: "test", id: "model", name: "Model", reasoning: false }, thinkingLevel: undefined },
132
133
  agentConfig: undefined,
133
134
  appendSystemPrompt: undefined,
134
135
  skillPath: undefined,
@@ -29,7 +29,7 @@ vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
29
29
 
30
30
  import type { EnginePort } from "../engine/port.ts";
31
31
  import { clearEngines, getEngine, registerEngine } from "../engine/registry.ts";
32
- import { getChildByRecord, killAllSpawnedChildren, spawnedChildren } from "../session-runner.ts";
32
+ import { getChildByRecord, killAllSpawnedChildren, spawnedChildren } from "../engine/engines/pi/session-runner.ts";
33
33
 
34
34
  /**
35
35
  * 假 child:只关心 killed 标记 + kill 调用(killAllSpawnedChildren 遍历用)。
@@ -63,6 +63,7 @@ function makeDisposingEngine(
63
63
  resume: "unsupported",
64
64
  interrupt: "kill-only",
65
65
  permissionMode: "fixed",
66
+ maxTurns: false,
66
67
  }),
67
68
  probe: () =>
68
69
  Promise.resolve({ ok: true, engineVersion: "0.0.0-test", checks: [{ name: "stub", ok: true }] }),
@@ -30,14 +30,14 @@ vi.mock("node:child_process", async () => {
30
30
  });
31
31
 
32
32
  // buildSpawnArgs 包装为 spy(保留真实实现)——「非全等拒单不触达 spawn 参数组装」的承重断言。
33
- vi.mock("../session-runner.ts", async (importOriginal) => {
34
- const actual = await importOriginal<typeof import("../session-runner.ts")>();
33
+ vi.mock("../engine/engines/pi/session-runner.ts", async (importOriginal) => {
34
+ const actual = await importOriginal<typeof import("../engine/engines/pi/session-runner.ts")>();
35
35
  return { ...actual, buildSpawnArgs: vi.fn(actual.buildSpawnArgs) };
36
36
  });
37
37
 
38
38
  import { ModelConfigService } from "../model-config-service.ts";
39
39
  import type { ModelInfo, ModelRegistryLike } from "../model-resolver.ts";
40
- import { buildSpawnArgs } from "../session-runner.ts";
40
+ import { buildSpawnArgs } from "../engine/engines/pi/session-runner.ts";
41
41
  import type { PiLike } from "../subagent-service.ts";
42
42
  import { SubagentService } from "../subagent-service.ts";
43
43
 
@@ -128,7 +128,7 @@ describe("start 同步期裁决(U1 验收 3:非全等 → isError 且无 spa
128
128
  });
129
129
 
130
130
  it("全等入参通过裁决(reject 不发生,放行链路触达 spawn 参数组装)——守卫零误伤对照", async () => {
131
- // 放行路径会进入 kickOffBackground(detached);execute resolve 即视为裁决放行。
131
+ // 放行路径会进入轮次 kick-off(detached);execute resolve 即视为裁决放行。
132
132
  // spawn 本身由 FakeChild 缺失抛错收口——本断言只关心「越过了同步裁决」:
133
133
  // execute 不 reject,且 buildSpawnArgs 已被调(或即将被调的 record 已创建)。
134
134
  const promise = service.execute({
@@ -33,7 +33,7 @@ vi.mock("../../core/logger.ts", () => ({
33
33
  getLogger: () => loggerMock,
34
34
  }));
35
35
 
36
- import { respond, sendGetStateCommand, sendPromptCommand } from "../stdin-writer.ts";
36
+ import { respond, sendGetStateCommand, sendPromptCommand } from "../engine/engines/pi/stdin-writer.ts";
37
37
 
38
38
  // ── helpers ──
39
39
 
@@ -362,8 +362,10 @@ describe("sendGetStateCommand", () => {
362
362
  describe("writeStdinLine 背压 — write 返回 false 时 warn 不 throw", () => {
363
363
  it("child.stdin.write 返回 false → warn 一次,函数正常返回(不 throw)", () => {
364
364
  // 构造 write 永远返回 false 的假 stream(模拟内核缓冲满/HWM 到达)
365
+ // 保留 vi.fn 引用(经 PassThrough 视图后 write 的 mock 面不可达)
366
+ const fakeWrite = vi.fn((_chunk: unknown) => false);
365
367
  const fakeStdin = {
366
- write: vi.fn(() => false),
368
+ write: fakeWrite,
367
369
  destroyed: false,
368
370
  } as unknown as PassThrough;
369
371
  const child = { stdin: fakeStdin } as unknown as ChildProcess;
@@ -371,8 +373,8 @@ describe("writeStdinLine 背压 — write 返回 false 时 warn 不 throw", () =
371
373
  expect(() => respond(child, "req-backpressure", { value: "x" })).not.toThrow();
372
374
 
373
375
  // write 被调一次(写入 command 行)
374
- expect(fakeStdin.write).toHaveBeenCalledTimes(1);
375
- const writtenArg = fakeStdin.write.mock.calls[0]?.[0] as string;
376
+ expect(fakeWrite).toHaveBeenCalledTimes(1);
377
+ const writtenArg = fakeWrite.mock.calls[0]![0] as string;
376
378
  expect(writtenArg).toContain("extension_ui_response");
377
379
  expect(writtenArg).toContain("req-backpressure");
378
380
  expect(writtenArg.endsWith("\n")).toBe(true);
@@ -3,7 +3,7 @@
3
3
  // widget 私货通道退役步骤 2(E 方案 §6.3)测试,两层:
4
4
  // 判定层:createBackgroundStream 真实函数(vi.importActual 绕过文件级 mock)——
5
5
  // gui+relay 激活不创建;tui / relay 未激活 / headless 原样创建;sink=null 恒降级。
6
- // 接线层:SubagentService.execute(background → kickOffBackground 同步段)透传
6
+ // 接线层:SubagentService.execute(background → kickOffChatRound 同步段)透传
7
7
  // mode/streamSink 给工厂——mode 传错 = 判定失效,故接线面必须锁定。
8
8
  //
9
9
  // spawn 层 mock(FakeChild),与 nested-visibility-env-propagation.test.ts 同模式。
@@ -103,7 +103,7 @@ vi.mock("../manifest-store.ts", () => {
103
103
  return { ManifestStore: vi.fn(function (_recordsDir: string) { return new FakeManifestStore(); }) };
104
104
  });
105
105
 
106
- vi.mock("../temp-prompt.ts", () => ({
106
+ vi.mock("../engine/engines/pi/temp-prompt.ts", () => ({
107
107
  writePromptToTempFile: vi.fn(async (agent: string) => {
108
108
  const safeName = agent.replace(/[^\w.-]+/g, "_");
109
109
  return { dir: `/tmp/fake-${safeName}`, filePath: `/tmp/fake-${safeName}/prompt-${safeName}.md` };
@@ -156,7 +156,7 @@ const ctxModel: ModelInfo = { id: "m", name: "M", provider: "p", reasoning: fals
156
156
 
157
157
  function setupService(mode: ExtensionMode | undefined, sessionId = "retire-it"): SubagentService {
158
158
  const agentDir = "/tmp/stream-retire-it";
159
- const modelService = new ModelConfigService({ agentDir });
159
+ const modelService = new ModelConfigService({ agentDir, cwd: agentDir });
160
160
  modelService.initModel({
161
161
  modelRegistry: makeEmptyRegistry(),
162
162
  sessionId,
@@ -176,7 +176,7 @@ function setupService(mode: ExtensionMode | undefined, sessionId = "retire-it"):
176
176
  return service;
177
177
  }
178
178
 
179
- /** 等 kickOffBackground 的 detached runAndFinalize 走到 spawn(stream 创建先于此)。 */
179
+ /** 等 kickOffChatRound 的 detached 轮次执行走到 spawn(stream 创建先于此)。 */
180
180
  async function waitForSpawnCall(): Promise<void> {
181
181
  const baseline = mockSpawn.mock.calls.length;
182
182
  const deadline = Date.now() + 2000;
@@ -187,7 +187,7 @@ async function waitForSpawnCall(): Promise<void> {
187
187
  }
188
188
 
189
189
  async function executeBackground(service: SubagentService): Promise<void> {
190
- await service.execute({ task: "retire test", ctxModel });
190
+ await service.execute({ task: "retire test", slug: "test", ctxModel });
191
191
  await waitForSpawnCall();
192
192
  // 推进 detached runSpawn 收尾(FakeChild close),防句柄悬挂干扰后续用例
193
193
  lastSpawnedChild().emit("close", 0);
@@ -234,7 +234,7 @@ describe("退役步骤 2 判定层:createBackgroundStream", () => {
234
234
 
235
235
  // ── 接线层:service → 工厂的参数透传(mode 传错 = 判定失效)──
236
236
 
237
- describe("退役步骤 2 接线层:kickOffBackground 透传 mode/sink 给工厂", () => {
237
+ describe("退役步骤 2 接线层:kickOffChatRound 透传 mode/sink 给工厂", () => {
238
238
  beforeEach(() => {
239
239
  vi.clearAllMocks();
240
240
  for (const key of RELAY_ENV_KEYS) delete process.env[key];
@@ -42,9 +42,9 @@ import type {
42
42
  ExecutionHandle,
43
43
  ExecutionRecord,
44
44
  SubagentRecord,
45
- SubagentService,
46
45
  SubagentToolDetails,
47
46
  } from "../types.ts";
47
+ import type { SubagentService } from "../subagent-service.ts";
48
48
 
49
49
  // ── 时钟固定(duration 快照确定性,见文件头)──
50
50
  const FROZEN_NOW = 1788189209000;
@@ -132,17 +132,35 @@ function makeRec(over: Partial<SubagentRecord> = {}): SubagentRecord {
132
132
  }
133
133
 
134
134
  function makeService(over: Record<string, unknown> = {}): SubagentService {
135
- return {
135
+ // 聚合面形态对齐 subagent-service.ts D4:queries(读模型)与 chatActions(对话
136
+ // action 面)分组挂载,mock 按同构 key 装配(over 仍传平铺 key,最小化快照用例改动)。
137
+ const m = {
136
138
  execute: vi.fn(),
137
- findRecord: vi.fn(() => undefined),
138
139
  cancel: vi.fn(() => false),
140
+ findRecord: vi.fn(() => undefined),
139
141
  collectRecords: vi.fn(() => [] as SubagentRecord[]),
140
142
  getFullRecord: vi.fn(() => undefined as SubagentRecord | undefined),
141
143
  lookupRecordAnyState: vi.fn(() => undefined as SubagentRecord | undefined),
142
144
  getRecordForAction: vi.fn(),
143
145
  closeSubagent: vi.fn(),
144
- deliverMessage: vi.fn(),
146
+ deliverChatMessage: vi.fn(),
145
147
  ...over,
148
+ };
149
+ return {
150
+ execute: m.execute,
151
+ cancel: m.cancel,
152
+ queries: {
153
+ findRecord: m.findRecord,
154
+ lookupRecordAnyState: m.lookupRecordAnyState,
155
+ collectRecords: m.collectRecords,
156
+ getFullRecord: m.getFullRecord,
157
+ onChange: vi.fn(() => () => {}),
158
+ },
159
+ chatActions: {
160
+ getRecordForAction: m.getRecordForAction,
161
+ closeSubagent: m.closeSubagent,
162
+ deliverChatMessage: m.deliverChatMessage,
163
+ },
146
164
  } as unknown as SubagentService;
147
165
  }
148
166
 
@@ -290,7 +308,7 @@ describe("⛔4 startHandler(校验 + 启动,快照 = pi-sw 实测)", () =>
290
308
  });
291
309
 
292
310
  it("正常启动 → 领域对象全字段 + execute 参数(task/slug trim、拍平透传、ctxModel/signal)", async () => {
293
- const execute = vi.fn(async (): Promise<ExecutionHandle> => ({
311
+ const execute = vi.fn(async (_opts: { task: string; slug?: string; agent?: string }): Promise<ExecutionHandle> => ({
294
312
  mode: "background",
295
313
  subagentId: "bg-9-abc",
296
314
  sessionFile: "sess-9.jsonl",
@@ -334,7 +352,7 @@ describe("⛔4 startHandler(校验 + 启动,快照 = pi-sw 实测)", () =>
334
352
  },
335
353
  });
336
354
  expect(execute).toHaveBeenCalledTimes(1);
337
- expect(execute.mock.calls[0][0]).toEqual({
355
+ expect(execute.mock.calls[0]![0]).toEqual({
338
356
  task: "long task", // trim 生效
339
357
  slug: "long-running",
340
358
  agent: "/x/agent.md",
@@ -489,6 +507,8 @@ describe("⛔4 cancelHandler(守卫 + 归属判定 + CAS 失败映射,快照
489
507
  expect(
490
508
  await errOf(() =>
491
509
  cancelHandler(
510
+ // "sync" 是历史已裁撤值(ExecutionMode 现仅 "background")——经 as never
511
+ // 注入非法值专测守卫分支,运行时守卫按 !== "background" 拦截
492
512
  makeService({ findRecord: vi.fn(() => makeRec({ id: "bg-1", mode: "sync" as never })) }),
493
513
  { subagentId: "bg-1" },
494
514
  ),
@@ -587,11 +607,11 @@ describe("⛔4 messageHandler(守卫 + upgrade + 投递,快照 = pi-sw 实
587
607
  });
588
608
  });
589
609
 
590
- it("chatMode record → deliverMessage(text trim + interrupt) + 领域对象", async () => {
591
- const deliverMessage = vi.fn(async () => {});
610
+ it("chatMode record → deliverChatMessage(text trim + interrupt) + 领域对象", async () => {
611
+ const deliverChatMessage = vi.fn(async () => {});
592
612
  const chatRecord = makeExecRecord({ id: "bg-1", chatMode: true, slug: "src-slug" });
593
613
  const r = await messageHandler(
594
- makeService({ getRecordForAction: vi.fn(() => chatRecord), deliverMessage }),
614
+ makeService({ getRecordForAction: vi.fn(() => chatRecord), deliverChatMessage }),
595
615
  { subagentId: "bg-1", text: " go on ", interrupt: true },
596
616
  );
597
617
  expect(r).toEqual({
@@ -600,18 +620,18 @@ describe("⛔4 messageHandler(守卫 + upgrade + 投递,快照 = pi-sw 实
600
620
  slug: "src-slug",
601
621
  response: { delivered: true },
602
622
  });
603
- expect(deliverMessage).toHaveBeenCalledWith(chatRecord, "go on", true);
623
+ expect(deliverChatMessage).toHaveBeenCalledWith(chatRecord, "go on", true);
604
624
  });
605
625
 
606
626
  it("one-shot upgrade:非 chatMode running record 收 message → 置位 chatMode 后投递", async () => {
607
- const deliverMessage = vi.fn(async () => {});
627
+ const deliverChatMessage = vi.fn(async () => {});
608
628
  const upgradeRec = makeExecRecord({ id: "bg-1", chatMode: false, status: "running" });
609
629
  await messageHandler(
610
- makeService({ getRecordForAction: vi.fn(() => upgradeRec), deliverMessage }),
630
+ makeService({ getRecordForAction: vi.fn(() => upgradeRec), deliverChatMessage }),
611
631
  { subagentId: "bg-1", text: "hi" },
612
632
  );
613
633
  expect(upgradeRec.chatMode).toBe(true);
614
- expect(deliverMessage).toHaveBeenCalledWith(upgradeRec, "hi", false);
634
+ expect(deliverChatMessage).toHaveBeenCalledWith(upgradeRec, "hi", false);
615
635
  });
616
636
 
617
637
  it("getRecordForAction 拒绝 + 无终态快照 → 原错误透传(文案最准原则)", async () => {
@@ -774,7 +794,7 @@ describe("⛔4 closeHandler(force 语义透传,快照 = pi-sw 实测)", ()
774
794
  // forkFromHandler(守卫链 1–6)
775
795
  // ============================================================
776
796
  describe("⛔4 forkFromHandler(守卫链 + slug 派生 + prompt 包装,快照 = pi-sw 实测)", () => {
777
- function makeForkService(source: SubagentRecord | undefined, execute = vi.fn(async (): Promise<ExecutionHandle> => ({
797
+ function makeForkService(source: SubagentRecord | undefined, execute = vi.fn(async (_opts: { task: string; slug?: string; agent?: string }): Promise<ExecutionHandle> => ({
778
798
  mode: "background",
779
799
  subagentId: "bg-new-1",
780
800
  sessionFile: "sess-new.jsonl",
@@ -899,7 +919,7 @@ describe("⛔4 forkFromHandler(守卫链 + slug 派生 + prompt 包装,快
899
919
  });
900
920
 
901
921
  it("正常:显式 prompt → wrapForkFromPrompt 包装 + slug 派生 'src-slug-resumed' + execute 参数", async () => {
902
- const execute = vi.fn(async (): Promise<ExecutionHandle> => ({
922
+ const execute = vi.fn(async (_opts: { task: string; slug?: string; agent?: string }): Promise<ExecutionHandle> => ({
903
923
  mode: "background",
904
924
  subagentId: "bg-new-1",
905
925
  sessionFile: "sess-new.jsonl",
@@ -928,7 +948,7 @@ describe("⛔4 forkFromHandler(守卫链 + slug 派生 + prompt 包装,快
928
948
  });
929
949
 
930
950
  it("无 prompt → FORK_FROM_DEFAULT_PROMPT 逐字", async () => {
931
- const execute = vi.fn(async (): Promise<ExecutionHandle> => ({
951
+ const execute = vi.fn(async (_opts: { task: string; slug?: string; agent?: string }): Promise<ExecutionHandle> => ({
932
952
  mode: "background",
933
953
  subagentId: "bg-new-2",
934
954
  sessionFile: "sess-new-2.jsonl",
@@ -955,7 +975,7 @@ describe("⛔4 forkFromHandler(守卫链 + slug 派生 + prompt 包装,快
955
975
  });
956
976
 
957
977
  it("slug 派生:34 字符源 slug 截 27 + '-resumed';fallback 链 slug||agent||'resumed'", async () => {
958
- const execute34 = vi.fn(async (): Promise<ExecutionHandle> => ({
978
+ const execute34 = vi.fn(async (_opts: { task: string; slug?: string; agent?: string }): Promise<ExecutionHandle> => ({
959
979
  mode: "background",
960
980
  subagentId: "bg-new-3",
961
981
  sessionFile: undefined,
@@ -965,9 +985,9 @@ describe("⛔4 forkFromHandler(守卫链 + slug 派生 + prompt 包装,快
965
985
  makeForkService(makeRec({ id: "bg-1", slug: "s".repeat(34), sessionFile: "sess-1.jsonl" }), execute34),
966
986
  { sourceSubagentId: "bg-1", prompt: "p" },
967
987
  );
968
- expect(execute34.mock.calls[0][0].slug).toBe(`${"s".repeat(27)}-resumed`);
988
+ expect(execute34.mock.calls[0]![0].slug).toBe(`${"s".repeat(27)}-resumed`);
969
989
 
970
- const executeAgent = vi.fn(async (): Promise<ExecutionHandle> => ({
990
+ const executeAgent = vi.fn(async (_opts: { task: string; slug?: string; agent?: string }): Promise<ExecutionHandle> => ({
971
991
  mode: "background",
972
992
  subagentId: "bg-new-4",
973
993
  sessionFile: undefined,
@@ -980,9 +1000,9 @@ describe("⛔4 forkFromHandler(守卫链 + slug 派生 + prompt 包装,快
980
1000
  ),
981
1001
  { sourceSubagentId: "bg-1", prompt: "p" },
982
1002
  );
983
- expect(executeAgent.mock.calls[0][0].slug).toBe("/home/u/agents/helper.md-resumed");
1003
+ expect(executeAgent.mock.calls[0]![0].slug).toBe("/home/u/agents/helper.md-resumed");
984
1004
 
985
- const executeResumed = vi.fn(async (): Promise<ExecutionHandle> => ({
1005
+ const executeResumed = vi.fn(async (_opts: { task: string; slug?: string; agent?: string }): Promise<ExecutionHandle> => ({
986
1006
  mode: "background",
987
1007
  subagentId: "bg-new-5",
988
1008
  sessionFile: undefined,
@@ -992,6 +1012,6 @@ describe("⛔4 forkFromHandler(守卫链 + slug 派生 + prompt 包装,快
992
1012
  makeForkService(makeRec({ id: "bg-1", slug: "", agent: "", sessionFile: "sess-1.jsonl" }), executeResumed),
993
1013
  { sourceSubagentId: "bg-1", prompt: "p" },
994
1014
  );
995
- expect(executeResumed.mock.calls[0][0].slug).toBe("resumed-resumed");
1015
+ expect(executeResumed.mock.calls[0]![0].slug).toBe("resumed-resumed");
996
1016
  });
997
1017
  });
@@ -20,7 +20,7 @@ const { loggerMock, killEscalationSpy } = vi.hoisted(() => ({
20
20
  vi.mock("../../core/logger.ts", () => ({ getLogger: () => loggerMock }));
21
21
 
22
22
  // mock session-runner(import 链需要 runSpawn/killAllSpawnedChildren/getChildByRecord 存在)
23
- vi.mock("../session-runner.ts", () => ({
23
+ vi.mock("../engine/engines/pi/session-runner.ts", () => ({
24
24
  runSpawn: vi.fn(),
25
25
  killAllSpawnedChildren: vi.fn(),
26
26
  getChildByRecord: vi.fn(() => undefined),
@@ -30,12 +30,13 @@ vi.mock("../session-runner.ts", () => ({
30
30
  }));
31
31
 
32
32
  import { createRecord, updateFromEvent } from "../execution-record.ts";
33
+ import { createNotifyHost } from "../notify-host.ts";
33
34
  import { ModelConfigService } from "../model-config-service.ts";
34
35
  import { RecordStore } from "../record-store.ts";
35
36
  import { SubagentService } from "../subagent-service.ts";
36
37
  import type { PiLike } from "../subagent-service.ts";
37
38
  import type { ExecutionRecord } from "../types.ts";
38
- import { getChildByRecord } from "../session-runner.ts";
39
+ import { getChildByRecord } from "../engine/engines/pi/session-runner.ts";
39
40
 
40
41
  // [T2④] kill 收敛入口 spy(工厂引用,断言服务侧三条路径收敛到它)
41
42
  const killRecordChildWithEscalationMock = killEscalationSpy;
@@ -95,7 +96,7 @@ function setup(): {
95
96
  pi: MockPi;
96
97
  } {
97
98
  const agentDir = makeTmpAgentDir();
98
- const modelService = new ModelConfigService({ agentDir });
99
+ const modelService = new ModelConfigService({ agentDir, cwd: agentDir });
99
100
  const service = new SubagentService({ cwd: agentDir, modelService });
100
101
  // [C2] pi 提为外层引用(对照 real-chain :139-140 形态)——close 现状语义断言需读
101
102
  // pi.sendMessage.mock.calls(末条通知 content 指针行),内联构造不保留引用。
@@ -121,23 +122,23 @@ describe("getRecordForAction 归属守卫(决策 3)", () => {
121
122
 
122
123
  afterEach(() => {
123
124
  service.dispose();
124
- fs.rmSync(agentDir, { recursive: true, force: true });
125
+ fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
125
126
  });
126
127
 
127
128
  it("正常(rootSessionId 匹配)→ 返回可变 record", () => {
128
129
  const record = makeRecord({ rootSessionId: sessionRootId });
129
130
  store.register(record);
130
- expect(service.getRecordForAction(record.id)).toBe(record);
131
+ expect(service.chatActions.getRecordForAction(record.id)).toBe(record);
131
132
  });
132
133
 
133
134
  it("不存在 → throw not found or not owned", () => {
134
- expect(() => service.getRecordForAction("sa-nonexistent")).toThrow(/not found or not owned/);
135
+ expect(() => service.chatActions.getRecordForAction("sa-nonexistent")).toThrow(/not found or not owned/);
135
136
  });
136
137
 
137
138
  it("rootSessionId 不匹配 → throw not found or not owned(不区分 not found vs not owned,防信息泄露)", () => {
138
139
  const record = makeRecord({ id: "sa-other", rootSessionId: "other-session" });
139
140
  store.register(record);
140
- expect(() => service.getRecordForAction("sa-other")).toThrow(/not found or not owned/);
141
+ expect(() => service.chatActions.getRecordForAction("sa-other")).toThrow(/not found or not owned/);
141
142
  });
142
143
  });
143
144
 
@@ -157,7 +158,7 @@ describe("closeSubagent 行为分流", () => {
157
158
 
158
159
  afterEach(() => {
159
160
  service.dispose();
160
- fs.rmSync(agentDir, { recursive: true, force: true });
161
+ fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
161
162
  });
162
163
 
163
164
  it("running + force:false + 有活进程 → 置 closeAfterRound=true(不立即终态,等轮完)", async () => {
@@ -167,7 +168,7 @@ describe("closeSubagent 行为分流", () => {
167
168
  const record = makeRecord({ status: "running" });
168
169
  store.register(record);
169
170
 
170
- await service.closeSubagent(record, false);
171
+ await service.chatActions.closeSubagent(record, false);
171
172
 
172
173
  expect(record.closeAfterRound).toBe(true);
173
174
  expect(record.status).toBe("running"); // 仍 running
@@ -181,7 +182,7 @@ describe("closeSubagent 行为分流", () => {
181
182
  record.sessionFile = path.join(agentDir, "test.jsonl");
182
183
  store.register(record);
183
184
 
184
- await service.closeSubagent(record, false);
185
+ await service.chatActions.closeSubagent(record, false);
185
186
 
186
187
  expect(record.status).toBe("closed");
187
188
  expect(store.getMutable(record.id)).toBeUndefined(); // archived
@@ -191,7 +192,7 @@ describe("closeSubagent 行为分流", () => {
191
192
  const record = makeRecord({ status: "running", sessionFile: path.join(agentDir, "run.jsonl") });
192
193
  store.register(record);
193
194
 
194
- await service.closeSubagent(record, true);
195
+ await service.chatActions.closeSubagent(record, true);
195
196
 
196
197
  // v4 B-1:cancelled 折入 closed(closedReason='cancelled' 区分用户取消语义)
197
198
  expect(record.status).toBe("closed");
@@ -203,7 +204,7 @@ describe("closeSubagent 行为分流", () => {
203
204
  const record = makeRecord({ status: "closed" });
204
205
  store.register(record);
205
206
 
206
- await service.closeSubagent(record, false);
207
+ await service.chatActions.closeSubagent(record, false);
207
208
 
208
209
  expect(record.status).toBe("closed"); // 不变
209
210
  });
@@ -223,7 +224,7 @@ describe("closeSubagent 行为分流", () => {
223
224
  expect(hasIdleTimer(record.id)).toBe(true);
224
225
 
225
226
  try {
226
- await service.closeSubagent(record, false);
227
+ await service.chatActions.closeSubagent(record, false);
227
228
  } finally {
228
229
  disarmIdleTimer(record.id); // 断言失败路径的兜底清理
229
230
  }
@@ -242,7 +243,7 @@ describe("closeSubagent 行为分流", () => {
242
243
  record.sessionFile = path.join(agentDir, "path-b.jsonl");
243
244
  store.register(record);
244
245
 
245
- await service.closeSubagent(record, false);
246
+ await service.chatActions.closeSubagent(record, false);
246
247
 
247
248
  expect(record.status).toBe("closed");
248
249
  expect(record.closedReason).toBe("user-close");
@@ -269,7 +270,7 @@ describe("closeSubagent 行为分流", () => {
269
270
  expect(hasIdleTimer(record.id)).toBe(true);
270
271
 
271
272
  try {
272
- await service.closeSubagent(record, true);
273
+ await service.chatActions.closeSubagent(record, true);
273
274
  } finally {
274
275
  disarmIdleTimer(record.id); // 断言失败路径的兜底清理
275
276
  }
@@ -299,7 +300,7 @@ describe("cancelBackground CAS 抢锁(A2-1)——cancel 迟到不覆写已
299
300
 
300
301
  afterEach(() => {
301
302
  service.dispose();
302
- fs.rmSync(agentDir, { recursive: true, force: true });
303
+ fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
303
304
  });
304
305
 
305
306
  it("record 已 closed 未 archive(doFinalizeRecord Step 0 await 窗口形态)→ cancel 返回 false,不覆写终态/不写 tombstone/不 notify/不 archive", () => {
@@ -354,7 +355,7 @@ describe("[M5] closeAfterRound 消费点挂 onRoundSettled(chatMode 轮完成
354
355
  afterEach(() => {
355
356
  service.dispose();
356
357
  _resetLifecycleState();
357
- fs.rmSync(agentDir, { recursive: true, force: true });
358
+ fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
358
359
  });
359
360
 
360
361
  it("热路径轮完成(onRoundSettled)+ closeAfterRound=true → 消费标志终态化(closed+user-close + archive)", async () => {
@@ -428,7 +429,7 @@ describe("[C2] close 现状语义 + sessionFile 条件透传", () => {
428
429
  afterEach(() => {
429
430
  service.dispose();
430
431
  _resetLifecycleState();
431
- fs.rmSync(agentDir, { recursive: true, force: true });
432
+ fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
432
433
  });
433
434
 
434
435
  it("close 语义:轮次通知(含指针行)发出后 closeAfterRoundSettled 终态化 + 终态通知,两条都送达", async () => {
@@ -473,8 +474,10 @@ describe("[C2] close 现状语义 + sessionFile 条件透传", () => {
473
474
 
474
475
  // running + 无活进程(getChildByRecord mock undefined)→ isResumable 放行 →
475
476
  // 非 chatMode 映射 closed 通知,立即 flush(无其他 running background)
476
- const svc = service as unknown as { notifyComplete(r: ExecutionRecord): void };
477
- svc.notifyComplete(record);
477
+ // [D4-①] notifyComplete 已随通知簇搬至 notify-host——按轴直接构造(同 pi/store
478
+ // 注入,行为与经 Service 的原路径等价;dedup 集为空与单次调用断言相容)
479
+ createNotifyHost({ getPi: () => pi, listRunning: () => store.listRunning(), getIsIdle: () => undefined })
480
+ .notifyComplete(record);
478
481
 
479
482
  expect(pi.sendMessage).toHaveBeenCalledTimes(1);
480
483
  const msg = pi.sendMessage.mock.calls[0]![0] as {
@@ -493,8 +496,8 @@ describe("[C2] close 现状语义 + sessionFile 条件透传", () => {
493
496
  record.sessionFile = path.join(agentDir, "c2-chat.jsonl");
494
497
  store.register(record);
495
498
 
496
- const svc = service as unknown as { notifyComplete(r: ExecutionRecord): void };
497
- svc.notifyComplete(record);
499
+ createNotifyHost({ getPi: () => pi, listRunning: () => store.listRunning(), getIsIdle: () => undefined })
500
+ .notifyComplete(record);
498
501
 
499
502
  expect(pi.sendMessage).toHaveBeenCalledTimes(1);
500
503
  const msg = pi.sendMessage.mock.calls[0]![0] as {
@@ -526,7 +529,7 @@ describe("[C-4] onRoundSettled 轮次增量 + [C-1] close 终态通知", () => {
526
529
  afterEach(() => {
527
530
  service.dispose();
528
531
  _resetLifecycleState();
529
- fs.rmSync(agentDir, { recursive: true, force: true });
532
+ fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
530
533
  });
531
534
 
532
535
  /** 真实回调链(与生产一致:agent_settled → onRoundSettled),turns 经 updateFromEvent 累积。 */
@@ -601,7 +604,7 @@ describe("[C-4] onRoundSettled 轮次增量 + [C-1] close 终态通知", () => {
601
604
  updateFromEvent(record, { type: "text_delta", delta: "LAST-ROUND-INCREMENT" });
602
605
  updateFromEvent(record, { type: "turn_end" });
603
606
  settleRound(record); // 第 1 条:轮次通知
604
- await service.closeSubagent(record, false); // idle close(D2 路径②)
607
+ await service.chatActions.closeSubagent(record, false); // idle close(D2 路径②)
605
608
  } finally {
606
609
  disarmIdleTimer(record.id);
607
610
  }
@@ -651,7 +654,7 @@ describe("[C-4] onRoundSettled 轮次增量 + [C-1] close 终态通知", () => {
651
654
  record.sessionFile = path.join(agentDir, "oneshot-close.jsonl");
652
655
  store.register(record);
653
656
 
654
- await service.closeSubagent(record, false); // resumable → closeChatIdle → notifyClosed 拒绝
657
+ await service.chatActions.closeSubagent(record, false); // resumable → closeChatIdle → notifyClosed 拒绝
655
658
 
656
659
  expect(record.status).toBe("closed");
657
660
  expect(pi.sendMessage).not.toHaveBeenCalled();
@@ -672,7 +675,7 @@ describe("[C-4] onRoundSettled 轮次增量 + [C-1] close 终态通知", () => {
672
675
  updateFromEvent(record, { type: "turn_end" });
673
676
  settleRound(record); // 轮终:onRoundSettled 写 record.result = "ok"
674
677
  expect(record.result).toBe("ok"); // 前提锚点:轮终真实值在位
675
- await service.closeSubagent(record, false); // idle close → completeRecord + archive
678
+ await service.chatActions.closeSubagent(record, false); // idle close → completeRecord + archive
676
679
  } finally {
677
680
  disarmIdleTimer(record.id);
678
681
  }
@@ -34,7 +34,7 @@ vi.mock("../alive-store.ts", () => ({
34
34
  findForeignLiveInstance: foreignLiveSpy,
35
35
  }));
36
36
 
37
- vi.mock("../session-runner.ts", () => ({
37
+ vi.mock("../engine/engines/pi/session-runner.ts", () => ({
38
38
  runSpawn: vi.fn(),
39
39
  killAllSpawnedChildren: vi.fn(),
40
40
  getChildByRecord: vi.fn(() => undefined),
@@ -63,7 +63,7 @@ function makePi(): PiLike {
63
63
 
64
64
  function setup(): { agentDir: string; service: SubagentService; store: RecordStore } {
65
65
  const agentDir = makeTmpAgentDir();
66
- const modelService = new ModelConfigService({ agentDir });
66
+ const modelService = new ModelConfigService({ agentDir, cwd: agentDir });
67
67
  const service = new SubagentService({ cwd: agentDir, modelService });
68
68
  service.initSession({ pi: makePi(), sessionId: "root-session" });
69
69
  const store = (service as unknown as { store: RecordStore }).store;
@@ -79,7 +79,7 @@ describe("T5① child process skips orphan recovery scan", () => {
79
79
  recover: ReturnType<typeof vi.spyOn>;
80
80
  entryOnly: ReturnType<typeof vi.spyOn>;
81
81
  } => {
82
- const modelService = new ModelConfigService({ agentDir });
82
+ const modelService = new ModelConfigService({ agentDir, cwd: agentDir });
83
83
  const svc = new SubagentService({ cwd: agentDir, modelService });
84
84
  const store = (svc as unknown as { store: RecordStore }).store;
85
85
  const recover = vi.spyOn(store, "recoverOrphanRecords").mockImplementation(() => {});
@@ -95,7 +95,7 @@ describe("T5① child process skips orphan recovery scan", () => {
95
95
  afterEach(() => {
96
96
  vi.unstubAllEnvs();
97
97
  vi.restoreAllMocks();
98
- fs.rmSync(agentDir, { recursive: true, force: true });
98
+ fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
99
99
  });
100
100
 
101
101
  it("root process (no self-record env) runs the orphan recovery scan", () => {
@@ -127,7 +127,7 @@ describe("T5③ cold-lookup running candidate foreign-instance guard", () => {
127
127
 
128
128
  afterEach(() => {
129
129
  vi.restoreAllMocks();
130
- fs.rmSync(agentDir, { recursive: true, force: true });
130
+ fs.rmSync(agentDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
131
131
  });
132
132
 
133
133
  const stubColdCandidate = (record: {
@@ -150,9 +150,9 @@ describe("T5③ cold-lookup running candidate foreign-instance guard", () => {
150
150
  });
151
151
  foreignLiveSpy.mockReturnValue({ pid: 4242, id: "sa-foreign", startedAt: Date.now() });
152
152
 
153
- expect(() => service.getRecordForAction("sa-foreign")).toThrow(ResurrectDeniedError);
153
+ expect(() => service["getRecordForAction"]("sa-foreign")).toThrow(ResurrectDeniedError);
154
154
  try {
155
- service.getRecordForAction("sa-foreign");
155
+ service["getRecordForAction"]("sa-foreign");
156
156
  } catch (err) {
157
157
  expect((err as Error).message).toContain("4242");
158
158
  expect((err as Error).message).toContain("double-write");
@@ -168,7 +168,7 @@ describe("T5③ cold-lookup running candidate foreign-instance guard", () => {
168
168
  sessionFile: "/tmp/fake-session-2.jsonl",
169
169
  rootSessionId: "root-session",
170
170
  });
171
- const found = service.getRecordForAction("sa-cold");
171
+ const found = service["getRecordForAction"]("sa-cold");
172
172
  expect(found.id).toBe("sa-cold");
173
173
  });
174
174
  });