@zhushanwen/subagent-core 0.6.0 → 0.7.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 (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
@@ -0,0 +1,151 @@
1
+ // src/execution/engine/config.ts
2
+ //
3
+ // [W4] subagents/config.json 的 engines{} 段(三级发现的 L3 显式配置,设计 §3.4)。
4
+ //
5
+ // 契约(impl-plan §2.4):
6
+ // `engines: {"<id>": {command, args, config, cwd, enabled}}`
7
+ // - 无 `env` 键(v6 已删 extras 层——引擎 env 放行面归 manifest envPrefixes,L3 不重开);
8
+ // - `config` 经 initialize.engineConfig 透传(引擎不实现消费即死键,不校验内容);
9
+ // - `enabled: false` → 该引擎不装载(A12⑤:不进清单 + 派发报错含恢复指引)。
10
+ //
11
+ // 与 execution/config.ts 的关系:那边是全局配置的 sanitize 权威(maxConcurrent /
12
+ // defaultEngine / engineRouting / collectSync),其 SubagentsGlobalConfig 类型不含
13
+ // engines 键(sanitize 未知键忽略)。本模块只取 engines 段并逐条目校验——独立解析
14
+ // 避免 execution/config.ts 类型面为发现器扩键(发现器消费 raw 形态,字段级容错在本
15
+ // 模块内做,坏条目 warn 跳过、不影响其他条目)。
16
+
17
+ import * as fs from "node:fs";
18
+ import * as path from "node:path";
19
+
20
+ import { getLogger } from "../../core/logger.ts";
21
+
22
+ const logger = getLogger("subagents");
23
+
24
+ /** L3 显式引擎配置条目(config.json engines.<id>,字段级校验后形态)。 */
25
+ export interface ExplicitEngineEntry {
26
+ /** 引擎 CLI 命令(相对 PATH 的命令名或绝对/相对路径;装载期解析为可执行路径)。 */
27
+ command: string;
28
+ /** 引擎 CLI 参数(不含 command 本身;缺省 [])。 */
29
+ args: string[];
30
+ /** initialize.engineConfig 透传(Record<string,string>,缺省无)。 */
31
+ config?: Record<string, string>;
32
+ /** 引擎进程 cwd(缺省继承宿主进程)。 */
33
+ cwd?: string;
34
+ }
35
+
36
+ /**
37
+ * 读 config.json 的 engines 段(已启用条目,键 = 引擎 id)。
38
+ *
39
+ * 容错契约(与 config.json 用户手编文件定位一致,execution/config.ts 同判):
40
+ * 文件缺失 / 坏 JSON / engines 段非对象 → 静默空表(L3 缺席是常态,不 warn——绝大多数
41
+ * 安装无显式配置);单条目形态坏 → warn 跳过该条目(不影响其他条目,A12②「其他引擎
42
+ * 正常」的 L3 版)。
43
+ */
44
+ export function readExplicitEngines(agentDir: string): Record<string, ExplicitEngineEntry> {
45
+ const configPath = path.join(agentDir, "subagents", "config.json");
46
+ let parsed: unknown;
47
+ try {
48
+ parsed = JSON.parse(fs.readFileSync(configPath, "utf8"));
49
+ } catch {
50
+ return {};
51
+ }
52
+ if (typeof parsed !== "object" || parsed === null) return {};
53
+ const engines = (parsed as Record<string, unknown>)["engines"];
54
+ if (typeof engines !== "object" || engines === null || Array.isArray(engines)) {
55
+ if (engines !== undefined) {
56
+ logger.warn(
57
+ `[engine-discovery] config.json engines section must be an object keyed by engine id, got ${jsonKindOf(engines)} — ignoring L3 explicit engines`,
58
+ );
59
+ }
60
+ return {};
61
+ }
62
+ const out: Record<string, ExplicitEngineEntry> = {};
63
+ for (const [id, raw] of Object.entries(engines as Record<string, unknown>)) {
64
+ const entry = sanitizeExplicitEntry(id, raw);
65
+ if (entry !== undefined) out[id] = entry;
66
+ }
67
+ return out;
68
+ }
69
+
70
+ /** 单条目字段级校验:command 必需非空字符串;args/config/cwd 形态坏 → 丢弃该字段 + warn。 */
71
+ function sanitizeExplicitEntry(id: string, raw: unknown): ExplicitEngineEntry | undefined {
72
+ const v = requireEntryObject(id, raw);
73
+ if (v === undefined) return undefined;
74
+ if (v["enabled"] === false) return undefined; // A12⑤:显式禁用 = 不装载不进清单(非错误,不 warn)
75
+ const command = requireCommand(id, v["command"]);
76
+ if (command === undefined) return undefined;
77
+ return {
78
+ command,
79
+ ...normalizeArgs(id, v["args"]),
80
+ ...normalizeConfig(id, v["config"]),
81
+ ...normalizeCwd(id, v["cwd"]),
82
+ };
83
+ }
84
+
85
+ /** 条目形态守卫:非对象(含数组/null)→ warn 跳过整条。 */
86
+ function requireEntryObject(id: string, raw: unknown): Record<string, unknown> | undefined {
87
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
88
+ logger.warn(
89
+ `[engine-discovery] config.json engines.${id} must be an object, got ${jsonKindOf(raw)} — skipping entry`,
90
+ );
91
+ return undefined;
92
+ }
93
+ return raw as Record<string, unknown>;
94
+ }
95
+
96
+ /** command 校验:必需非空字符串,形态坏 → warn 跳过整条。 */
97
+ function requireCommand(id: string, command: unknown): string | undefined {
98
+ if (typeof command !== "string" || command.trim() === "") {
99
+ logger.warn(
100
+ `[engine-discovery] config.json engines.${id}.command is required (non-empty string) — skipping entry`,
101
+ );
102
+ return undefined;
103
+ }
104
+ return command;
105
+ }
106
+
107
+ /** args 字段:形态坏 warn 丢弃该字段;合法(或缺省)→ 条件 spread 形态。 */
108
+ function normalizeArgs(id: string, args: unknown): { args: string[] } {
109
+ if (args !== undefined && !isStringArray(args)) {
110
+ logger.warn(
111
+ `[engine-discovery] config.json engines.${id}.args must be a string array — ignoring args`,
112
+ );
113
+ }
114
+ return { args: isStringArray(args) ? args : [] };
115
+ }
116
+
117
+ /** config 字段:形态坏 warn 丢弃该字段;合法 → 条件 spread 形态。 */
118
+ function normalizeConfig(id: string, config: unknown): { config?: Record<string, string> } {
119
+ if (config !== undefined && !isStringRecord(config)) {
120
+ logger.warn(
121
+ `[engine-discovery] config.json engines.${id}.config must be a Record<string, string> — ignoring config`,
122
+ );
123
+ }
124
+ return isStringRecord(config) ? { config } : {};
125
+ }
126
+
127
+ /** cwd 字段:形态坏 warn 丢弃该字段;合法 → 条件 spread 形态。 */
128
+ function normalizeCwd(id: string, cwd: unknown): { cwd?: string } {
129
+ if (cwd !== undefined && typeof cwd !== "string") {
130
+ logger.warn(
131
+ `[engine-discovery] config.json engines.${id}.cwd must be a string — ignoring cwd`,
132
+ );
133
+ }
134
+ return typeof cwd === "string" ? { cwd } : {};
135
+ }
136
+
137
+ function isStringArray(value: unknown): value is string[] {
138
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
139
+ }
140
+
141
+ function isStringRecord(value: unknown): value is Record<string, string> {
142
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
143
+ return Object.values(value as Record<string, unknown>).every((item) => typeof item === "string");
144
+ }
145
+
146
+ /** unknown 值的 JSON 类型名(warn 文案用;不用 JSON.stringify——循环引用会炸)。 */
147
+ function jsonKindOf(value: unknown): string {
148
+ if (value === null) return "null";
149
+ if (Array.isArray(value)) return "array";
150
+ return typeof value;
151
+ }
@@ -0,0 +1,346 @@
1
+ // src/execution/engine/d8-compat.ts
2
+ //
3
+ // [W8] D8 兼容公共面薄壳(设计 §3.6 D8 表 + impl-plan §2.8):宿主零改动的机械保证。
4
+ // 三个符号的兼容形态:
5
+ // - registerZcodeEngine(engineDataDir) 薄壳:确保 id 'zcode' 的 cli descriptor 已
6
+ // 注册,engineDataDir 经合成 env(XYZ_AGENT_DATA_DIR)记入 descriptor portFactory;
7
+ // - createZcodeEngine(deps) → RemoteEngine('zcode'):deps → 协议客户端映射
8
+ // (engineDataDir → dataDir / cliPath → command / processEnv → baseEnv /
9
+ // sources 不跨进程——不变量 5「引擎不落盘宿主数据、自行解析凭据」);
10
+ // - killAllSpawnedChildren 收敛在 engine/host/spawned-children 公共面([W3][F-7
11
+ // 注释纠偏] 仅镜像记账——core 侧 spawnedChildren 镜像整体置死,不发进程信号;
12
+ // 引擎进程回收 = stdin-EOF 自灭链(正常路径)+ disposeEngines() 显式触发
13
+ // cli 引擎 dispose(宿主 shutdown 链预留 API,现无生产接线),非「进程组收割」)。
14
+ //
15
+ // vendored 定位(设计 §3.6 二选一通道①):core 相对自身定位
16
+ // `<coreDir>/../<engine>-subagent-cli`(与 zsw core-ref.js:15 同款相对解析,零配置);
17
+ // 通道②(宿主注入 XYZ_AGENT_ENGINE_ROOTS)由发现器承载,薄壳在注册表已有 descriptor
18
+ // 时直接保留(发现器快照权威)。定位失败(打包态 staging 前 / 引擎包未安装):
19
+ // registerZcodeEngine 回退 inproc 注册(过渡期可用性优先,W11 删 inproc 后该分支
20
+ // 一并删除);createZcodeEngine 返回 command = 期望路径的 RemoteEngine——首次协议
21
+ // 调用 engine_crashed 显式报错(不变量 6「失败不静默」,构造同步不 throw,§3.5.3)。
22
+ //
23
+ // hostKind:registerZcodeEngine 缺省 'pi'(现调用方 = xyz-agent 扩展宿主组合根);
24
+ // createZcodeEngine 恒 'zsw'(消费方 = zsw CLI vendored 形态)——pidfile 实例维度
25
+ // 命名段(engine.<hostKind>.<hostPid>.pid),不同宿主进程 pid 不同不互误杀。
26
+
27
+ import * as fs from "node:fs";
28
+ import * as path from "node:path";
29
+ import { fileURLToPath } from "node:url";
30
+
31
+ import { EngineClient } from "./client/engine-client.ts";
32
+ import { RemoteEngine, type RemoteEngineManifestSnapshot } from "./client/remote-engine.ts";
33
+ import {
34
+ readRelayForwardEnv,
35
+ } from "../relay-env.ts";
36
+ import { getEngineDataDir } from "./common/data-dir.ts";
37
+ import {
38
+ CONSERVATIVE_CAPABILITIES,
39
+ parseCapabilities,
40
+ parseEnvPrefixes,
41
+ parseModelCatalog,
42
+ resolveManifestBin,
43
+ } from "./engine-manifest.ts";
44
+ import { inspectEnginePackage } from "./engine-discovery-scan.ts";
45
+ import { hasEngine, registerEngineDescriptor, type CliEngineDescriptor } from "./registry.ts";
46
+ import type { EngineCapabilities } from "./types.ts";
47
+ import type { EnginePort } from "./port.ts";
48
+ import type { ModelCatalogEntry } from "@zhushanwen/subagent-engine-sdk";
49
+ import { getLogger } from "../../core/logger.ts";
50
+
51
+ const logger = getLogger("subagents");
52
+
53
+ /** D8 薄壳服务的引擎 id(设计 §3.6 D8 表硬编码面;不用 engines/zcode/constants 的
54
+ * ZCODE_ENGINEID——薄壳生命周期跨 W11(inproc 目录删除后薄壳仍在),不绑内部模块)。 */
55
+ const D8_ZCODE_ENGINE_ID = "zcode";
56
+
57
+ /** createZcodeEngine 消费宿主(zsw CLI vendored 形态)——pidfile hostKind 命名段。 */
58
+ const D8_ZSW_HOST_KIND = "zsw";
59
+
60
+ /** registerZcodeEngine 缺省宿主(xyz-agent 扩展宿主组合根是现调用方)。 */
61
+ const D8_PI_HOST_KIND = "pi";
62
+
63
+ /** deps.sources 忽略的 warn-once(跨进程不可达面,重复注册不刷屏)。 */
64
+ let warnedSourcesIgnored = false;
65
+
66
+ /**
67
+ * D8 兼容 deps 形状(zsw runner-core.js:163 实测调用面 {engineDataDir, cliPath?})。
68
+ * 与 engines/zcode ZcodeEngineDeps 结构兼容:zsw 传入对象可直接赋值;sources /
69
+ * probeVersion 为兼容残留字段——协议形态下不跨进程(忽略 + warn)。
70
+ */
71
+ export interface D8CompatZcodeEngineDeps {
72
+ /** 引擎数据根(→ 引擎子进程 env XYZ_AGENT_DATA_DIR,见设计 §3.6 注入矩阵)。 */
73
+ engineDataDir: () => string;
74
+ /** 引擎 CLI 入口覆盖(L3 等价;缺省 = vendored 相对定位)。 */
75
+ cliPath?: string;
76
+ /** 兼容残留:模型来源/凭据路径——不跨进程(不变量 5),忽略。 */
77
+ sources?: unknown;
78
+ /** 兼容残留:版本探测执行器——协议 probe 由引擎实现承担,忽略。 */
79
+ probeVersion?: unknown;
80
+ /** env 基底合并(buildEngineChildEnv baseEnv;缺省 {...process.env})。 */
81
+ processEnv?: NodeJS.ProcessEnv;
82
+ }
83
+
84
+ /**
85
+ * [W11 收口] 旧 ZcodeEngineDeps 的兼容别名:barrel 原类型导出(zsw 调用面的 deps
86
+ * 形状契约)与 D8CompatZcodeEngineDeps 合并——结构同源(engineDataDir/cliPath/
87
+ * sources/probeVersion/processEnv),别名保留免 zsw 侧机械改名。
88
+ */
89
+ export type ZcodeEngineDeps = D8CompatZcodeEngineDeps;
90
+
91
+ /** vendored 引擎包定位结果。 */
92
+ interface LocatedEnginePkg {
93
+ pkgDir: string;
94
+ /** manifest bin 解析后的引擎 CLI 入口绝对路径(已过可执行校验)。 */
95
+ binPath: string;
96
+ }
97
+
98
+ /** 引擎包 manifest 的薄壳消费面(capabilities/envPrefixes/modelCatalog 快照)。 */
99
+ interface ManifestInfo {
100
+ capabilities: EngineCapabilities;
101
+ envPrefixes: string[];
102
+ modelCatalog?: { dynamic: boolean; models: ModelCatalogEntry[] } | null;
103
+ }
104
+
105
+ // ============================================================
106
+ // vendored 定位(通道①)
107
+ // ============================================================
108
+
109
+ /**
110
+ * core 包根目录:本模块文件目录上 3 级(src 与 dist 同构——
111
+ * <pkg>/src/execution/engine/d8-compat.ts 与 <pkg>/dist/execution/engine/d8-compat.js
112
+ * 的 dirname 上 3 级都是包根)。不可得(bundle 进宿主无文件位置语义)→ undefined,
113
+ * 调用方走通道②(宿主注入发现根)或显式 cliPath。
114
+ */
115
+ function corePackageDir(): string | undefined {
116
+ let moduleDir: string | undefined;
117
+ try {
118
+ moduleDir = path.dirname(fileURLToPath(import.meta.url));
119
+ } catch {
120
+ moduleDir = undefined;
121
+ }
122
+ if (moduleDir === undefined && typeof __dirname !== "undefined") {
123
+ moduleDir = __dirname;
124
+ }
125
+ if (moduleDir === undefined) return undefined;
126
+ return path.resolve(moduleDir, "..", "..", "..");
127
+ }
128
+
129
+ /** 引擎包 npm 名(D8 vendored 布局约定 <engineId>-subagent-cli)。 */
130
+ function enginePkgName(engineId: string): string {
131
+ return `@zhushanwen/${engineId}-subagent-cli`;
132
+ }
133
+
134
+ /** 期望的 vendored bin 路径(定位失败时 createZcodeEngine 的 command 占位——spawn 时
135
+ * engine_crashed 显式报错附恢复指引,不留静默)。 */
136
+ function expectedVendoredBinPath(engineId: string): string {
137
+ const pkgDir = attemptVendoredPkgDir(engineId);
138
+ return path.join(pkgDir, "bin", `${engineId}-subagent-cli.mjs`);
139
+ }
140
+
141
+ function attemptVendoredPkgDir(engineId: string): string {
142
+ const coreDir = corePackageDir();
143
+ const packagesDir = coreDir !== undefined ? path.dirname(coreDir) : path.resolve("packages");
144
+ return path.join(packagesDir, `${engineId}-subagent-cli`);
145
+ }
146
+
147
+ /**
148
+ * vendored package.json 的 manifest 段原始解析(单一来源,locateVendoredEnginePkg 与
149
+ * readEngineManifest 共用):读取 pkgDir/package.json 并 duck-typed 三段下降
150
+ * pkg["xyz-agent"]["subagentEngine"]。读文件/JSON 解析失败或 manifest 段缺失/非对象
151
+ * (含 null 与数组)→ undefined。数组拒绝上收到共用守卫对两个调用点均结局等价:
152
+ * JSON.parse 产物为数组时不可能携带字符串键(JSON 语法限制),bin 提取路径对数组取
153
+ * ["bin"] 恒 undefined——与拒绝分支同收敛为定位失败。pkg 一并返回供调用方做 name
154
+ * 精确匹配与 bin 相对路径解析(单次解析,不重复读盘)。
155
+ */
156
+ function readSubagentEngineManifestRaw(
157
+ pkgDir: string,
158
+ ): { pkg: Record<string, unknown>; manifest: Record<string, unknown> } | undefined {
159
+ try {
160
+ const pkg = JSON.parse(fs.readFileSync(path.join(pkgDir, "package.json"), "utf8")) as Record<string, unknown>;
161
+ const agentNs = pkg["xyz-agent"];
162
+ const raw =
163
+ typeof agentNs === "object" && agentNs !== null
164
+ ? (agentNs as Record<string, unknown>)["subagentEngine"]
165
+ : undefined;
166
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return undefined;
167
+ return { pkg, manifest: raw as Record<string, unknown> };
168
+ } catch {
169
+ return undefined;
170
+ }
171
+ }
172
+
173
+ /**
174
+ * vendored 相对定位:`<coreDir>/../<engineId>-subagent-cli`。三重校验防同名目录冒充:
175
+ * package.json 可读 + name 精确匹配 + manifest bin 可执行。任一不成立 → undefined
176
+ * (调用方按通道②/显式 cliPath/期望路径降级)。
177
+ */
178
+ function locateVendoredEnginePkg(engineId: string): LocatedEnginePkg | undefined {
179
+ const pkgDir = attemptVendoredPkgDir(engineId);
180
+ const parsed = readSubagentEngineManifestRaw(pkgDir);
181
+ if (parsed === undefined) return undefined;
182
+ const { pkg, manifest } = parsed;
183
+ if (pkg["name"] !== enginePkgName(engineId)) return undefined;
184
+ const manifestBin = manifest["bin"];
185
+ if (typeof manifestBin !== "string" || manifestBin.trim() === "") return undefined;
186
+ const binPath = resolveManifestBin(pkgDir, pkg, manifestBin);
187
+ if (binPath === undefined || !canExecute(binPath)) return undefined;
188
+ return { pkgDir, binPath };
189
+ }
190
+
191
+ /** manifest 薄壳消费面解析(复用 W4 engine-manifest 字段级解析器——单源,不复制规则)。 */
192
+ function readEngineManifest(pkgDir: string): ManifestInfo | undefined {
193
+ const parsed = readSubagentEngineManifestRaw(pkgDir);
194
+ if (parsed === undefined) return undefined;
195
+ const manifest = parsed.manifest;
196
+ const id = typeof manifest["id"] === "string" ? manifest["id"] : D8_ZCODE_ENGINE_ID;
197
+ const modelCatalog = parseModelCatalog(id, manifest["modelCatalog"]);
198
+ return {
199
+ capabilities: parseCapabilities(id, manifest["capabilities"]),
200
+ envPrefixes: parseEnvPrefixes(id, manifest["envPrefixes"]),
201
+ ...(modelCatalog !== undefined ? { modelCatalog } : {}),
202
+ };
203
+ }
204
+
205
+ function canExecute(p: string): boolean {
206
+ try {
207
+ fs.accessSync(p, fs.constants.X_OK);
208
+ return true;
209
+ } catch {
210
+ return false;
211
+ }
212
+ }
213
+
214
+ // ============================================================
215
+ // registerZcodeEngine 薄壳
216
+ // ============================================================
217
+
218
+ /**
219
+ * D8 薄壳:确保 id 'zcode' 的引擎 descriptor 已注册,engineDataDir 记入 descriptor。
220
+ *
221
+ * 注册表已有 descriptor(W4 发现器先装载 / 重复调用)→ 直接保留——发现器 cli
222
+ * descriptor 携带完整 manifest 快照,权威高于薄壳构造。未注册 → vendored 定位构造
223
+ * cli descriptor(经 W4 inspectEnginePackage 全量 manifest 解析;engineDataDir 经
224
+ * 合成 env XYZ_AGENT_DATA_DIR 被 portFactory 捕获——「以显式值为准」)。[W11/DoD#5]
225
+ * 定位/解析失败保持未注册(inproc 回退已删,不变量 6「失败不静默」——派发期
226
+ * engine_not_found 显式报错 + 恢复指引;XYZ_ZCODE_CLI 覆盖通道由 createZcodeEngine
227
+ * 的 deps.cliPath 等价承载)。
228
+ */
229
+ export function registerZcodeEngine(engineDataDir: () => string = getEngineDataDir): void {
230
+ const id = D8_ZCODE_ENGINE_ID;
231
+ if (hasEngine(id)) return;
232
+ // [W11/DoD#5] inproc 回退分支已删(不变量 6「失败不静默」):定位/解析失败保持
233
+ // 未注册——派发期 engine_not_found 显式报错(含「安装/发现引擎包」恢复指引),
234
+ // XYZ_ZCODE_CLI 覆盖通道由 createZcodeEngine 的 deps.cliPath 等价承载。
235
+ const located = locateVendoredEnginePkg(id);
236
+ if (located === undefined) {
237
+ logger.warn(
238
+ `[d8-compat] engine '${id}' vendored package not located (enginePkg ${enginePkgName(id)}); ` +
239
+ "left unregistered — dispatch will fail with engine_not_found + recovery guidance",
240
+ );
241
+ return;
242
+ }
243
+ // 合成 env:显式 engineDataDir 写进 XYZ_AGENT_DATA_DIR,portFactory 闭包经
244
+ // getEngineDataDir(env) 解析即取显式值(W4 inspectEnginePackage 原样复用,零改动)。
245
+ const syntheticEnv: NodeJS.ProcessEnv = { ...process.env, XYZ_AGENT_DATA_DIR: engineDataDir() };
246
+ const inspection = inspectEnginePackage(located.pkgDir, "d8-compat", D8_PI_HOST_KIND, syntheticEnv);
247
+ if (inspection.status !== "ok") {
248
+ logger.warn(
249
+ `[d8-compat] engine '${id}' vendored package inspection failed (${inspection.reason}); ` +
250
+ "left unregistered — dispatch will fail with engine_not_found + recovery guidance",
251
+ );
252
+ return;
253
+ }
254
+ registerEngineDescriptor(id, inspection.entry.descriptor as CliEngineDescriptor);
255
+ }
256
+
257
+ // ============================================================
258
+ // createZcodeEngine 薄壳
259
+ // ============================================================
260
+
261
+ /** deps.sources 忽略的 warn-once(跨进程不可达面,重复注册不刷屏)。 */
262
+ function warnSourcesIgnoredOnce(deps: D8CompatZcodeEngineDeps): void {
263
+ if (deps.sources === undefined || warnedSourcesIgnored) return;
264
+ warnedSourcesIgnored = true;
265
+ logger.warn(
266
+ "[d8-compat] createZcodeEngine deps.sources is ignored (model/credential sources do not " +
267
+ "cross the process boundary — the engine resolves its own credentials, protocol invariant 5)",
268
+ );
269
+ }
270
+
271
+ /**
272
+ * manifest 薄壳消费面解析 + 回退合成:vendored 包未定位/manifest 不可读 → capabilities
273
+ * 全保守 + envPrefixes 空(gate 同步拦生成,失败显式发生在首次协议调用)。
274
+ */
275
+ function resolveManifestInfo(located: LocatedEnginePkg | undefined): ManifestInfo {
276
+ const info = located !== undefined ? readEngineManifest(located.pkgDir) : undefined;
277
+ return {
278
+ capabilities: info?.capabilities ?? { ...CONSERVATIVE_CAPABILITIES },
279
+ envPrefixes: info?.envPrefixes ?? [],
280
+ ...(info?.modelCatalog !== undefined && info.modelCatalog !== null ? { modelCatalog: info.modelCatalog } : {}),
281
+ };
282
+ }
283
+
284
+ /** RemoteEngine manifest 快照:capabilities 恒有;modelCatalog 三态语义(无枚举面不注入)。 */
285
+ function buildManifestSnapshot(manifestInfo: ManifestInfo): RemoteEngineManifestSnapshot {
286
+ return {
287
+ capabilities: manifestInfo.capabilities,
288
+ ...(manifestInfo.modelCatalog !== undefined && manifestInfo.modelCatalog !== null
289
+ ? { modelCatalog: manifestInfo.modelCatalog }
290
+ : {}),
291
+ };
292
+ }
293
+
294
+ /** 协议客户端装配(H12 relay 透传 + manifestDiagnostics 诊断快照)。 */
295
+ function buildZcodeEngineClient(
296
+ id: string,
297
+ command: string,
298
+ dataDir: string,
299
+ baseEnv: NodeJS.ProcessEnv,
300
+ manifestInfo: ManifestInfo,
301
+ ): EngineClient {
302
+ return new EngineClient({
303
+ engineId: id,
304
+ command,
305
+ args: [],
306
+ hostKind: D8_ZSW_HOST_KIND,
307
+ dataDir,
308
+ baseEnv,
309
+ envPrefixes: manifestInfo.envPrefixes,
310
+ // H12 relay 透传(zsw 宿主链路):宿主 env 的 relay 连接三键原样提取走 L0 注入;
311
+ // 身份键 SESSION_ID/RECORD_ID 由 buildEngineChildEnv L1 deny 剥除(引擎按
312
+ // run.params.ctx 重写,不靠 env 继承)。relay 未激活 → undefined → 不注入。
313
+ ...(readRelayForwardEnv(baseEnv) !== undefined ? { relay: readRelayForwardEnv(baseEnv) } : {}),
314
+ manifestDiagnostics: {
315
+ capabilities: manifestInfo.capabilities,
316
+ models:
317
+ manifestInfo.modelCatalog === undefined || manifestInfo.modelCatalog === null
318
+ ? null
319
+ : manifestInfo.modelCatalog.models,
320
+ },
321
+ });
322
+ }
323
+
324
+ /**
325
+ * D8 薄壳:deps → 协议客户端映射,返回 RemoteEngine('zcode')(implements EnginePort,
326
+ * 对 zsw 透传结构兼容)。构造同步、不 throw(§3.5.3 代理形态)——引擎包缺失时
327
+ * command = 期望路径,失败显式发生在首次协议调用(engine_crashed + 恢复指引)。
328
+ */
329
+ export function createZcodeEngine(deps: D8CompatZcodeEngineDeps): EnginePort {
330
+ const id = D8_ZCODE_ENGINE_ID;
331
+ warnSourcesIgnoredOnce(deps);
332
+ const dataDir = deps.engineDataDir();
333
+ const baseEnv: NodeJS.ProcessEnv = { ...process.env, ...(deps.processEnv ?? {}) };
334
+ const located = deps.cliPath !== undefined ? undefined : locateVendoredEnginePkg(id);
335
+ const command = deps.cliPath ?? located?.binPath ?? expectedVendoredBinPath(id);
336
+ const manifestInfo = resolveManifestInfo(located);
337
+ const manifestSnapshot = buildManifestSnapshot(manifestInfo);
338
+ const client = buildZcodeEngineClient(id, command, dataDir, baseEnv, manifestInfo);
339
+ return new RemoteEngine({
340
+ engineId: id,
341
+ client,
342
+ manifest: manifestSnapshot,
343
+ dataDir,
344
+ hostKind: D8_ZSW_HOST_KIND,
345
+ });
346
+ }
@@ -0,0 +1,107 @@
1
+ // src/execution/engine/engine-discovery-roots.ts
2
+ //
3
+ // [W4] 三级发现的搜索路径域(L1 env 根解析 / L2 node 解析域推导 / 发现根扫描)。
4
+ // 自 engine-discovery-scan.ts 拆出(max-lines 纪律)——路径收集与目录遍历是发现
5
+ // 主链的无状态前置面。设计权威源:§3.4 搜索路径表 + impl-plan §2.4。
6
+
7
+ import * as fs from "node:fs";
8
+ import * as path from "node:path";
9
+
10
+ import { getLogger } from "../../core/logger.ts";
11
+
12
+ const logger = getLogger("subagents");
13
+
14
+ /** L1 引擎发现根 env(设计 §3.4;值形态 = path.delimiter 分隔的绝对路径列表)。 */
15
+ export const ENGINE_ROOTS_ENV = "XYZ_AGENT_ENGINE_ROOTS";
16
+
17
+ /** L1 env 根解析:path.delimiter 分隔;空段跳过;非绝对路径丢弃 + warn;去重(大小写敏感)。 */
18
+ export function parseEngineRootsEnv(env: NodeJS.ProcessEnv): string[] {
19
+ const raw = env[ENGINE_ROOTS_ENV];
20
+ if (raw === undefined || raw.trim() === "") return [];
21
+ const out: string[] = [];
22
+ const seen = new Set<string>();
23
+ for (const part of raw.split(path.delimiter)) {
24
+ const dir = part.trim();
25
+ if (dir === "") continue;
26
+ if (!path.isAbsolute(dir)) {
27
+ logger.warn(
28
+ `[engine-discovery] ${ENGINE_ROOTS_ENV} entry '${dir}' is not an absolute path — dropping entry`,
29
+ );
30
+ continue;
31
+ }
32
+ if (seen.has(dir)) continue;
33
+ seen.add(dir);
34
+ out.push(dir);
35
+ }
36
+ return out;
37
+ }
38
+
39
+ /**
40
+ * L2 根推导:宿主进程入口(process.argv[1])所在目录逐级上溯的 node_modules——与
41
+ * createRequire(<宿主入口>) 的 require.resolve 候选目录链同构(「宿主 node_modules
42
+ * require.resolve」的扫描化:零枚举发现要求扫目录而非 resolve 具体包名)。打包态
43
+ * (staged 扩展 / Bun standalone 无 node_modules 结构)与 zsw vendor 态自然为空——
44
+ * 规格明确两态 L2 无效。argv[1] 不可得(嵌入式 / worker)→ L2 缺席。
45
+ */
46
+ export function deriveNodeModuleRoots(argvEntry: string | undefined = process.argv[1]): string[] {
47
+ if (!argvEntry || argvEntry.trim() === "") return [];
48
+ const roots: string[] = [];
49
+ let dir = path.dirname(path.resolve(argvEntry));
50
+ while (true) {
51
+ const nm = path.join(dir, "node_modules");
52
+ if (isDirectory(nm)) roots.push(nm);
53
+ const parent = path.dirname(dir);
54
+ if (parent === dir) break;
55
+ dir = parent;
56
+ }
57
+ return roots;
58
+ }
59
+
60
+ /**
61
+ * 扫描单个发现根:一级子目录直接含 package.json 即候选包;无 package.json 的目录
62
+ * 再下钻一层(org 分组布局:npm @scope/name、pi npm 安装位的 <org>/<pkg>)。二层封顶。
63
+ * 根不存在/不可读 → 静默(L2 上溯链多数层级无 node_modules,warn 会刷屏)。
64
+ */
65
+ export function scanEngineRoot(root: string, visit: (pkgDir: string) => void): void {
66
+ let entries: fs.Dirent[];
67
+ try {
68
+ entries = fs.readdirSync(root, { withFileTypes: true });
69
+ } catch {
70
+ return;
71
+ }
72
+ for (const item of entries) {
73
+ if (!item.isDirectory()) continue;
74
+ const dir = path.join(root, item.name);
75
+ if (isFile(path.join(dir, "package.json"))) {
76
+ visit(dir);
77
+ continue;
78
+ }
79
+ let sub: fs.Dirent[];
80
+ try {
81
+ sub = fs.readdirSync(dir, { withFileTypes: true });
82
+ } catch {
83
+ continue;
84
+ }
85
+ for (const child of sub) {
86
+ if (!child.isDirectory()) continue;
87
+ const subDir = path.join(dir, child.name);
88
+ if (isFile(path.join(subDir, "package.json"))) visit(subDir);
89
+ }
90
+ }
91
+ }
92
+
93
+ function isDirectory(p: string): boolean {
94
+ try {
95
+ return fs.statSync(p).isDirectory();
96
+ } catch {
97
+ return false; // ENOENT 等 → 非目录
98
+ }
99
+ }
100
+
101
+ function isFile(p: string): boolean {
102
+ try {
103
+ return fs.statSync(p).isFile();
104
+ } catch {
105
+ return false; // ENOENT 等 → 非文件
106
+ }
107
+ }