@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
@@ -3,19 +3,31 @@
3
3
  // registry 专属测试(P1 验收 4):注册/获取/listEngines/hasEngine/
4
4
  // 未注册 id 报 engine_not_found(错误文案含已注册清单——错误规格表第 1 行契约)。
5
5
  // [R1 D6] 追加:重注册先 dispose 旧单例(D6②)+ disposeEngines 收割遍历(D6③)。
6
+ // [W3] 追加:EngineDescriptor 双模(inproc 快捷 / cli portFactory 代理透明——
7
+ // cli 形态 EnginePort 实例 = W2 RemoteEngine)+ D4 displayName 稳定序 +
8
+ // 全不可用 engine_not_found 文案(「未发现任何引擎包」+ 安装指引)。
6
9
 
7
- import { beforeEach, describe, expect, it, vi } from "vitest";
10
+ import { tmpdir } from "node:os";
11
+ import { join } from "node:path";
8
12
 
13
+ import { describe, expect, it, vi, beforeEach } from "vitest";
14
+
15
+ import { EngineClient } from "../client/engine-client.ts";
16
+ import { RemoteEngine } from "../client/remote-engine.ts";
9
17
  import type { EnginePort, RunContext } from "../port.ts";
10
18
  import {
11
19
  clearEngines,
12
20
  DEFAULT_ENGINE_ID,
13
21
  disposeEngines,
14
22
  EngineNotFoundError,
23
+ firstAvailableEngineId,
15
24
  getEngine,
16
25
  hasEngine,
17
26
  listEngines,
27
+ listEnginesByDisplayName,
18
28
  registerEngine,
29
+ registerEngineDescriptor,
30
+ type EngineManifestSnapshot,
19
31
  } from "../registry.ts";
20
32
  import type { SessionView } from "../types.ts";
21
33
  import type { AgentCallOpts } from "../../../orchestration/models/types.ts";
@@ -209,3 +221,196 @@ describe("engine registry", () => {
209
221
  });
210
222
  });
211
223
  });
224
+
225
+ // ============================================================
226
+ // [W3] EngineDescriptor 双模 + manifest 快照 + D4(impl-plan §2.3)
227
+ // ============================================================
228
+
229
+ /** cli 形态 manifest 快照样本(与 RemoteEngineManifestSnapshot 结构闭包的运行时互证载体)。 */
230
+ function makeManifestSnapshot(displayName?: string): EngineManifestSnapshot {
231
+ return {
232
+ capabilities: {
233
+ schemaEnforcement: "emulated",
234
+ steer: "unsupported",
235
+ conversation: "unsupported",
236
+ personaInjection: "prompt",
237
+ eventGranularity: "stream",
238
+ sandbox: "emulated",
239
+ sessionRead: "full",
240
+ resume: "cold",
241
+ interrupt: "kill-only",
242
+ permissionMode: "fixed",
243
+ maxTurns: false,
244
+ },
245
+ modelCatalog: { dynamic: true, models: [{ id: "glm-4.6", canonicalRef: "zai/glm-4.6" }] },
246
+ ...(displayName !== undefined ? { displayName } : {}),
247
+ };
248
+ }
249
+
250
+ /** W2 真实 cli 形态 port 装配(构造同步、不 spawn——ensureConnected 才 spawn)。 */
251
+ function makeRemoteEngine(id: string, manifest: EngineManifestSnapshot): RemoteEngine {
252
+ const client = new EngineClient({
253
+ engineId: id,
254
+ command: process.execPath,
255
+ args: ["-e", ""],
256
+ hostKind: "test",
257
+ dataDir: join(tmpdir(), `registry-w3-${id}`),
258
+ manifestDiagnostics: { capabilities: manifest.capabilities, models: manifest.modelCatalog?.models ?? null },
259
+ });
260
+ return new RemoteEngine({
261
+ engineId: id,
262
+ client,
263
+ manifest: { capabilities: manifest.capabilities, modelCatalog: manifest.modelCatalog },
264
+ dataDir: join(tmpdir(), `registry-w3-${id}`),
265
+ hostKind: "test",
266
+ });
267
+ }
268
+
269
+ describe("EngineDescriptor 双模(W3 D1)", () => {
270
+ beforeEach(() => {
271
+ clearEngines();
272
+ });
273
+
274
+ it("registerEngine = inproc 快捷:descriptor kind=inproc,getEngine 透明(既有工厂语义不变)", () => {
275
+ registerEngine("fake", () => makeFakeEngine("fake"));
276
+ const engine = getEngine("fake");
277
+ expect(engine.id).toBe("fake");
278
+ expect(hasEngine("fake")).toBe(true);
279
+ expect(listEngines()).toEqual(["fake"]);
280
+ });
281
+
282
+ it("cli descriptor:getEngine 返回 portFactory 产物,两形态透明(cli 形态实例 = W2 RemoteEngine)", () => {
283
+ const manifest = makeManifestSnapshot();
284
+ const port = makeRemoteEngine("zcode-cli", manifest);
285
+ const portFactory = vi.fn(() => port as EnginePort);
286
+ registerEngineDescriptor("zcode-cli", {
287
+ kind: "cli",
288
+ command: process.execPath,
289
+ args: ["-e", ""],
290
+ capabilities: manifest.capabilities,
291
+ portFactory,
292
+ manifest: { modelCatalog: manifest.modelCatalog, displayName: manifest.displayName },
293
+ });
294
+ const engine = getEngine("zcode-cli");
295
+ // 两形态透明:上层拿到的是同一个 EnginePort 面;cli 实例 = W2 RemoteEngine
296
+ expect(portFactory).toHaveBeenCalledTimes(1);
297
+ expect(engine).toBe(port);
298
+ expect(engine).toBeInstanceOf(RemoteEngine);
299
+ expect(engine.id).toBe("zcode-cli");
300
+ // 同步成员直读 manifest 快照(W2 形态映射经 registry descriptor 快照成立)
301
+ expect(engine.capabilities()).toBe(manifest.capabilities);
302
+ });
303
+
304
+ it("cli descriptor 惰性单例:portFactory 首次取用才执行,重复 getEngine 同实例", () => {
305
+ const manifest = makeManifestSnapshot();
306
+ const portFactory = vi.fn(() => makeRemoteEngine("lazy", manifest) as EnginePort);
307
+ registerEngineDescriptor("lazy", {
308
+ kind: "cli",
309
+ command: process.execPath,
310
+ args: [],
311
+ capabilities: manifest.capabilities,
312
+ portFactory,
313
+ });
314
+ // 注册本身不触发 portFactory(descriptor 首次使用才解析——§3.5.3 代理形态)
315
+ expect(portFactory).not.toHaveBeenCalled();
316
+ expect(hasEngine("lazy")).toBe(true);
317
+ const a = getEngine("lazy");
318
+ const b = getEngine("lazy");
319
+ expect(a).toBe(b);
320
+ expect(portFactory).toHaveBeenCalledTimes(1);
321
+ });
322
+
323
+ it("cli descriptor 覆盖同 id:旧单例 dispose 触发 + 新 portFactory 重建(与 inproc 覆盖同语义)", () => {
324
+ const manifest = makeManifestSnapshot();
325
+ const dispose = vi.fn(() => Promise.resolve());
326
+ const oldPort = makeRemoteEngine("overwrite", manifest);
327
+ (oldPort as unknown as { dispose: typeof dispose }).dispose = dispose;
328
+ registerEngineDescriptor("overwrite", {
329
+ kind: "cli",
330
+ command: process.execPath,
331
+ args: [],
332
+ capabilities: manifest.capabilities,
333
+ portFactory: () => oldPort as EnginePort,
334
+ });
335
+ getEngine("overwrite");
336
+ const newPort = makeRemoteEngine("overwrite", manifest);
337
+ registerEngineDescriptor("overwrite", {
338
+ kind: "cli",
339
+ command: process.execPath,
340
+ args: [],
341
+ capabilities: manifest.capabilities,
342
+ portFactory: () => newPort as EnginePort,
343
+ });
344
+ expect(dispose).toHaveBeenCalledTimes(1);
345
+ expect(getEngine("overwrite")).toBe(newPort);
346
+ });
347
+ });
348
+
349
+ describe("D4:displayName 稳定序与首个可用引擎(W3)", () => {
350
+ beforeEach(() => {
351
+ clearEngines();
352
+ });
353
+
354
+ it("listEnginesByDisplayName:displayName 排序(码点序),缺省 = id,displayName 相同按 id 决胜", () => {
355
+ // 注册序故意与 displayName 序不同——排序键是 manifest displayName 而非注册序
356
+ registerEngineDescriptor("beta", {
357
+ kind: "cli",
358
+ command: process.execPath,
359
+ args: [],
360
+ capabilities: makeManifestSnapshot("Zulu").capabilities,
361
+ portFactory: () => makeFakeEngine("beta"),
362
+ manifest: { displayName: "Zulu" },
363
+ });
364
+ registerEngine("alpha", () => makeFakeEngine("alpha")); // inproc 无 manifest → displayName = id
365
+ registerEngineDescriptor("gamma", {
366
+ kind: "cli",
367
+ command: process.execPath,
368
+ args: [],
369
+ capabilities: makeManifestSnapshot().capabilities,
370
+ portFactory: () => makeFakeEngine("gamma"),
371
+ // 无 displayName → 缺省 = id
372
+ });
373
+ registerEngineDescriptor("delta", {
374
+ kind: "cli",
375
+ command: process.execPath,
376
+ args: [],
377
+ capabilities: makeManifestSnapshot("Zulu").capabilities,
378
+ portFactory: () => makeFakeEngine("delta"),
379
+ manifest: { displayName: "Zulu" },
380
+ });
381
+ // 码点序:'Z'(0x5A) < 'a'(0x61) —— displayName "Zulu" 组(beta/delta,组内按 id
382
+ // 决胜 beta < delta)排在 "alpha"/"gamma" 之前
383
+ expect(listEnginesByDisplayName()).toEqual(["beta", "delta", "alpha", "gamma"]);
384
+ });
385
+
386
+ it("firstAvailableEngineId:清单第一项;空注册表 undefined(调用方转 engine_not_found)", () => {
387
+ expect(firstAvailableEngineId()).toBeUndefined();
388
+ registerEngineDescriptor("zcode", {
389
+ kind: "cli",
390
+ command: process.execPath,
391
+ args: [],
392
+ capabilities: makeManifestSnapshot("ZCode").capabilities,
393
+ portFactory: () => makeFakeEngine("zcode"),
394
+ manifest: { displayName: "ZCode" },
395
+ });
396
+ registerEngine("pi", () => makeFakeEngine("pi"));
397
+ // 码点序:"ZCode"(Z=0x5A) < "pi"(p=0x70)——displayName 序与 id 序不同向正是本用例点
398
+ expect(firstAvailableEngineId()).toBe("zcode");
399
+ });
400
+ });
401
+
402
+ describe("engine_not_found 空清单文案(W3 D4:未发现任何引擎包)", () => {
403
+ it("registered 为空:文案含「No engine packages were discovered」+ 安装指引", () => {
404
+ const err = new EngineNotFoundError("pi", []);
405
+ expect(err.code).toBe("engine_not_found");
406
+ expect(err.message).toContain("No engine packages were discovered");
407
+ expect(err.message).toContain("XYZ_AGENT_ENGINE_ROOTS");
408
+ expect(err.message).toContain("(none)");
409
+ });
410
+
411
+ it("registered 非空:维持既有恢复指引(frontmatter / 默认引擎设置修 typo)", () => {
412
+ const err = new EngineNotFoundError("ghost", ["pi", "zcode"]);
413
+ expect(err.message).toContain("frontmatter");
414
+ expect(err.message).not.toContain("No engine packages were discovered");
415
+ });
416
+ });
@@ -54,6 +54,8 @@ function makeRoute(overrides?: {
54
54
  taskModel?: string;
55
55
  /** 额外注册第三引擎(probe ok)——fallback 目标为「非 pi 的其他全局默认」用例。 */
56
56
  globalEngine?: string;
57
+ /** D4 可用清单覆盖(缺省 = 注册表插入序 pi 优先;displayName 序由注册方负责)。 */
58
+ available?: string[];
57
59
  }) {
58
60
  const engines = new Map<string, EnginePort>();
59
61
  engines.set(DEFAULT_ENGINE_ID, makeFakeEngine("pi", true));
@@ -77,6 +79,7 @@ function makeRoute(overrides?: {
77
79
  },
78
80
  hasEngineFn: (id) => engines.has(id),
79
81
  listEnginesFn: () => [...engines.keys()],
82
+ listAvailableEnginesFn: () => overrides?.available ?? [...engines.keys()],
80
83
  };
81
84
  return { opts, probeCalls, engines };
82
85
  }
@@ -338,3 +341,97 @@ describe("routeEngineForHost:宿主统一路由(D3-②)", () => {
338
341
  expect((err as EngineNotFoundError).registered).toContain("pi");
339
342
  });
340
343
  });
344
+
345
+ // ============================================================
346
+ // [W3] D4 缺省引擎回落链 + fallback 目标「首个可用引擎」(impl-plan §2.3)
347
+ // ============================================================
348
+
349
+ describe("D4 缺省引擎回落链(default 层宽容 / call/frontmatter 层显式报错)", () => {
350
+ it("defaultEngine 指向未注册 id(已卸载):warn + 回落首个可用 + engineFallback 留痕(A12④)", async () => {
351
+ const { opts } = makeRoute({
352
+ routing: { globalDefaultEngine: "ghost-engine" },
353
+ available: ["pi", "zcode"],
354
+ });
355
+ const result = await routeEngine(opts);
356
+ expect(result.engineId).toBe("pi"); // 首个可用(displayName 序第一)
357
+ expect(result.requestedEngineId).toBe("ghost-engine");
358
+ expect(result.engineFallback).toEqual({ from: "ghost-engine", reason: "engine_not_found" });
359
+ expect(result.source).toBe("default");
360
+ });
361
+
362
+ it("default 层回落不探(目标引擎不可用由 run 期显式失败,probe 已失败一次不重复)", async () => {
363
+ const { opts, probeCalls } = makeRoute({
364
+ routing: { globalDefaultEngine: "ghost-engine" },
365
+ available: ["pi", "zcode"],
366
+ });
367
+ await routeEngine(opts);
368
+ expect(probeCalls).toEqual([]);
369
+ });
370
+
371
+ it("全不可用(清单空):engine_not_found + 「未发现任何引擎包」+ 安装指引(D4)", async () => {
372
+ const { opts } = makeRoute({
373
+ routing: { globalDefaultEngine: "ghost-engine" },
374
+ available: [],
375
+ });
376
+ const err = await routeEngine(opts).catch((e: unknown) => e);
377
+ expect(err).toBeInstanceOf(EngineNotFoundError);
378
+ expect((err as EngineNotFoundError).message).toContain("No engine packages were discovered");
379
+ });
380
+
381
+ it("call 层未知 id:仍 engine_not_found 前置暴露(D4 宽容回落只作用 default 层)", async () => {
382
+ const { opts } = makeRoute({
383
+ routing: { callEngine: "ghost-engine" },
384
+ available: ["pi", "zcode"],
385
+ });
386
+ await expect(routeEngine(opts)).rejects.toThrowError(EngineNotFoundError);
387
+ });
388
+
389
+ it("frontmatter 层未知 id:同 call 层前置暴露(配置错误不静默换引擎)", async () => {
390
+ const { opts } = makeRoute({
391
+ routing: { agentEngine: "ghost-engine" },
392
+ available: ["pi", "zcode"],
393
+ });
394
+ await expect(routeEngine(opts)).rejects.toThrowError(EngineNotFoundError);
395
+ });
396
+ });
397
+
398
+ describe("fallbackTargetId「首个可用引擎」语义(W3:恒 'pi' 作废)", () => {
399
+ it("probe 失败兜底:全局默认不在可用清单 → 首个可用引擎(排除 from)", async () => {
400
+ // globalDefaultEngine='ghost'(未注册)→ 既有逻辑会 get('ghost') 炸;D4 后校验
401
+ // 可用性,落「首个可用引擎」
402
+ const { opts, engines } = makeRoute({
403
+ zcodeProbeOk: false,
404
+ globalEngine: "claude",
405
+ routing: { agentEngine: "zcode", globalDefaultEngine: "ghost" },
406
+ available: ["claude", "zcode"],
407
+ });
408
+ const result = await routeEngine(opts);
409
+ expect(result.engine).toBe(engines.get("claude")); // 可用清单内第一 ≠ from
410
+ expect(result.engineId).toBe("claude");
411
+ });
412
+
413
+ it("可用清单只含刚失败的引擎(from):不原地重试,直接报 engine_probe_failed(D4 无可用不 fallback)", async () => {
414
+ const { opts } = makeRoute({
415
+ zcodeProbeOk: false,
416
+ routing: { agentEngine: "zcode" },
417
+ available: ["zcode"],
418
+ });
419
+ const err = await routeEngine(opts).catch((e: unknown) => e);
420
+ expect(err).toBeInstanceOf(EngineError);
421
+ expect((err as EngineError).code).toBe("engine_probe_failed");
422
+ expect((err as EngineError).message).toContain("无其他可用引擎");
423
+ });
424
+
425
+ it("default 层 probe 失败(defaultEngine 配了坏引擎):首个可用引擎,不再恒回 'pi'", async () => {
426
+ const { opts, engines } = makeRoute({
427
+ zcodeProbeOk: false,
428
+ globalEngine: "alpha",
429
+ routing: { globalDefaultEngine: "zcode" },
430
+ available: ["alpha", "zcode"],
431
+ });
432
+ // available 无 pi:D4 语义下不再强行回 'pi'——首个可用(≠from)= alpha
433
+ const result = await routeEngine(opts);
434
+ expect(result.engine).toBe(engines.get("alpha"));
435
+ expect(result.engineFallback).toEqual({ from: "zcode", reason: "engine_probe_failed" });
436
+ });
437
+ });
@@ -0,0 +1,97 @@
1
+ // run-failure-worktree-cleanup.test.ts —— [W3 契约变更⑤] run 期失败清理前置副作用的
2
+ // 链路锚定单测。
3
+ //
4
+ // 设计权威源:docs/design/subagent-engine-protocolization.md §3.3 能力位段(manifest
5
+ // 多声明 → initialize 发现 → engine_capability_mismatch 该 run 失败 + record 标 failed
6
+ // + **清理 run 前已建的前置副作用**);impl-plan §2.3 契约变更⑤。
7
+ //
8
+ // 链路事实(2026-09-09 grep/read 实测,W3 以测试锚定防后续单元破坏):
9
+ // executeViaEngine(worktree 创建,record.worktreeHandle 绑定)
10
+ // → kickOffEngineRun → runEngineTask catch(engine.run prepare 期 reject,含协议
11
+ // 握手 engine_capability_mismatch 形态)
12
+ // → finalizeFailed(CAS tryTransition closed/gc 抢到锁)
13
+ // → finalizeRecord → doFinalizeRecord Step 3b cleanupWorktreeIfBound
14
+ // → worktreeManager.cleanup(record.worktreeHandle)。
15
+ // 前置副作用唯一实体 = worktree(并发池槽在 kickOffEngineRun finally 自回收;journal
16
+ // 是宿主②级数据源不清理)。本文件直接锚定执行体 doFinalizeRecord 的 worktree 清理
17
+ // 行为——subagent-service.finalizeFailed 的 JSDoc 为链路登记处。
18
+
19
+ import { describe, expect, it, vi } from "vitest";
20
+
21
+ import { doFinalizeRecord, type FinalizeDeps } from "../../finalize-record.ts";
22
+ import type { AgentResult, ExecutionRecord, WorktreeHandle } from "../../types.ts";
23
+
24
+ /** 最小 record(worktree 绑定可注入;status running 供终态迁移)。 */
25
+ function makeRecord(worktreeHandle?: WorktreeHandle): ExecutionRecord {
26
+ return {
27
+ id: "sa-w3-cleanup",
28
+ agent: "tester",
29
+ model: "prov/model",
30
+ task: "t",
31
+ status: "running",
32
+ mode: "background",
33
+ startedAt: Date.now(),
34
+ turnCount: 0,
35
+ chatMode: false,
36
+ ...(worktreeHandle !== undefined ? { worktreeHandle } : {}),
37
+ } as unknown as ExecutionRecord;
38
+ }
39
+
40
+ function makeDeps(cleanup: ReturnType<typeof vi.fn>): FinalizeDeps {
41
+ return {
42
+ manifestStore: {
43
+ writeManifest: vi.fn(async () => undefined),
44
+ } as unknown as FinalizeDeps["manifestStore"],
45
+ worktreeManager: { cleanup } as unknown as FinalizeDeps["worktreeManager"],
46
+ store: { archive: vi.fn() } as unknown as FinalizeDeps["store"],
47
+ modelService: { getAgentDir: () => "/tmp/w3-test-agentdir" } as unknown as FinalizeDeps["modelService"],
48
+ pi: null,
49
+ emitUnregister: vi.fn(),
50
+ };
51
+ }
52
+
53
+ describe("契约⑤:run 期失败路径清理前置副作用(worktree)", () => {
54
+ it("record.worktreeHandle 绑定:doFinalizeRecord(finalizeFailed 执行体)调用 worktreeManager.cleanup 且传入原 handle", async () => {
55
+ const handle: WorktreeHandle = Object.freeze({
56
+ path: "/tmp/w3-test-worktree",
57
+ branch: "pi-sub-sa-w3-cleanup",
58
+ baseCommit: "deadbeef",
59
+ mainCwd: "/tmp/w3-test-repo",
60
+ });
61
+ const cleanup = vi.fn(async () => undefined);
62
+ const record = makeRecord(handle);
63
+ await doFinalizeRecord(makeDeps(cleanup), record, { success: false } as AgentResult, "closed", "gc");
64
+ expect(cleanup).toHaveBeenCalledTimes(1);
65
+ expect(cleanup).toHaveBeenCalledWith(handle);
66
+ });
67
+
68
+ it("无 worktree 绑定:cleanup 不被调(幂等守卫——非 worktree 任务零清理副作用)", async () => {
69
+ const cleanup = vi.fn(async () => undefined);
70
+ const record = makeRecord(undefined);
71
+ await doFinalizeRecord(makeDeps(cleanup), record, { success: false } as AgentResult, "closed", "gc");
72
+ expect(cleanup).not.toHaveBeenCalled();
73
+ });
74
+
75
+ it("cleanup 抛错不阻断收尾链(best-effort:manifest/emitUnregister 仍执行)", async () => {
76
+ const handle: WorktreeHandle = Object.freeze({
77
+ path: "/tmp/w3-test-worktree",
78
+ branch: "pi-sub-sa-w3-cleanup",
79
+ baseCommit: "deadbeef",
80
+ mainCwd: "/tmp/w3-test-repo",
81
+ });
82
+ const emitUnregister = vi.fn();
83
+ const cleanup = vi.fn(async () => {
84
+ throw new Error("git worktree remove failed (simulated)");
85
+ });
86
+ const record = makeRecord(handle);
87
+ await doFinalizeRecord(
88
+ { ...makeDeps(cleanup), emitUnregister },
89
+ record,
90
+ { success: false } as AgentResult,
91
+ "closed",
92
+ "gc",
93
+ );
94
+ expect(cleanup).toHaveBeenCalledTimes(1);
95
+ expect(emitUnregister).toHaveBeenCalledWith("sa-w3-cleanup", "closed");
96
+ });
97
+ });