@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,457 @@
1
+ // RemoteEngine 测试(W2):cli 形态 EnginePort 门面(fake 引擎 CLI)。
2
+ //
3
+ // 覆盖(impl-plan §2.2 必写死条目):
4
+ // 同步成员形态映射(capabilities 直读 / listModels 三态 / validateModel 三态与
5
+ // 成员不实现)/ run 帧映射(task 子集收窄 + ctx 承载)/ RunContext 反向通道映射 /
6
+ // abort → cancel + 3s 收敛(收敛与超时杀链两路)/ read dataDir 必填 /
7
+ // 运行中失败合成 outcome vs prepare 期失败 reject 的分界。
8
+
9
+ import { mkdtempSync, rmSync } from "node:fs";
10
+ import { tmpdir } from "node:os";
11
+ import { join } from "node:path";
12
+ import { fileURLToPath } from "node:url";
13
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
14
+
15
+ import { EngineClient } from "../engine-client.ts";
16
+ import { RemoteEngine, type RemoteEngineManifestSnapshot } from "../remote-engine.ts";
17
+ import { SubagentStream } from "../../../stream-sink.ts";
18
+ import { isProcessAlive } from "../pid-file.ts";
19
+ import { getLogger, type UiRequest } from "@zhushanwen/subagent-engine-sdk";
20
+
21
+ const FAKE_ENGINE = fileURLToPath(new URL("./__fixtures__/fake-engine.mjs", import.meta.url));
22
+
23
+ /**
24
+ * 默认 manifest capabilities 基线(与 fake 引擎 initialize 应答逐位一致——gate 位
25
+ * 多声明判定的「一致放行」基线;多声明用例在此上单点改强)。
26
+ */
27
+ const FAKE_MATCHED_CAPS = {
28
+ schemaEnforcement: "emulated",
29
+ steer: "unsupported",
30
+ conversation: "unsupported",
31
+ personaInjection: "prompt",
32
+ eventGranularity: "stream",
33
+ sandbox: "emulated",
34
+ sessionRead: "full",
35
+ resume: "cold",
36
+ interrupt: "kill-only",
37
+ permissionMode: "fixed",
38
+ maxTurns: false,
39
+ } as const;
40
+
41
+ /** 轮询等待(真实 timers;引擎启动/信号传播是真实 IO,fake timers 会挂死自写轮询)。 */
42
+ async function waitForTrue(predicate: () => boolean, timeoutMs = 8_000, stepMs = 25): Promise<void> {
43
+ const deadline = Date.now() + timeoutMs;
44
+ while (Date.now() < deadline) {
45
+ if (predicate()) return;
46
+ await new Promise((resolve) => setTimeout(resolve, stepMs));
47
+ }
48
+ throw new Error("waitForTrue: condition not met within timeout");
49
+ }
50
+
51
+ function waitForReady(client: EngineClient): Promise<void> {
52
+ return waitForTrue(() => client.currentState === "ready");
53
+ }
54
+
55
+ let dataDir: string;
56
+
57
+ beforeEach(() => {
58
+ dataDir = mkdtempSync(join(tmpdir(), "w2-remote-engine-"));
59
+ });
60
+
61
+ afterEach(() => {
62
+ rmSync(dataDir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
63
+ vi.useRealTimers();
64
+ });
65
+
66
+ interface Fixture {
67
+ engine: RemoteEngine;
68
+ client: EngineClient;
69
+ cleanup: () => Promise<void>;
70
+ }
71
+
72
+ function makeEngine(
73
+ manifestOverrides: Partial<RemoteEngineManifestSnapshot> = {},
74
+ clientOverrides: Partial<ConstructorParameters<typeof EngineClient>[0]> = {},
75
+ ): Fixture {
76
+ const client = new EngineClient({
77
+ engineId: "fake",
78
+ command: process.execPath,
79
+ args: [FAKE_ENGINE],
80
+ hostKind: "test",
81
+ dataDir,
82
+ envPrefixes: [],
83
+ ...clientOverrides,
84
+ });
85
+ const engine = new RemoteEngine({
86
+ engineId: "fake",
87
+ client,
88
+ dataDir,
89
+ hostKind: "test",
90
+ manifest: {
91
+ capabilities: { ...FAKE_MATCHED_CAPS },
92
+ modelCatalog: {
93
+ dynamic: true,
94
+ models: [
95
+ { id: "glm-4.6", aliases: ["glm"], canonicalRef: "zai/glm-4.6" },
96
+ { id: "mimo-v2.5-pro", canonicalRef: "xiaomi-token-plan-cn/mimo-v2.5-pro" },
97
+ ],
98
+ },
99
+ ...manifestOverrides,
100
+ },
101
+ });
102
+ return { engine, client, cleanup: () => client.dispose() };
103
+ }
104
+
105
+ /** RunContext 构造(回调收集器)。 */
106
+ function makeCtx(overrides: Partial<Parameters<RemoteEngine["run"]>[1]> = {}) {
107
+ const events: unknown[] = [];
108
+ return {
109
+ ctx: {
110
+ taskId: "run-1",
111
+ poolKey: "shared",
112
+ onEvent: (e: unknown) => {
113
+ events.push(e);
114
+ },
115
+ ...overrides,
116
+ } as Parameters<RemoteEngine["run"]>[1],
117
+ events,
118
+ };
119
+ }
120
+
121
+ /** 从 fake 引擎的 run-params 回显 event 解析引擎实际收到的帧载荷。 */
122
+ function extractRunParams(events: unknown[]): { task: Record<string, unknown>; ctx: Record<string, unknown> } {
123
+ const echo = events.find(
124
+ (e) => (e as { type: string; message?: string }).type === "error"
125
+ && typeof (e as { message?: string }).message === "string"
126
+ && (e as { message: string }).message.startsWith("run-params:"),
127
+ ) as { message: string };
128
+ expect(echo).toBeDefined();
129
+ return JSON.parse(echo.message.slice("run-params:".length));
130
+ }
131
+
132
+ describe("RemoteEngine 同步成员形态映射(必写死)", () => {
133
+ it("capabilities():直读 manifest 注册期快照(不触发连接——构造同步、无缓存)", () => {
134
+ const { engine, client, cleanup } = makeEngine();
135
+ const caps = engine.capabilities();
136
+ expect(caps.schemaEnforcement).toBe("emulated");
137
+ expect(caps.maxTurns).toBe(false);
138
+ expect(client.currentState).toBe("idle"); // 同步成员不碰协议
139
+ void cleanup;
140
+ });
141
+
142
+ it("listModels() 三态:省略 modelCatalog → null(buildCoreAlignedHint);显式 [] → [](buildEmptyModelsHint);数组原样", async () => {
143
+ const omitted = makeEngine({ modelCatalog: undefined });
144
+ expect(omitted.engine.listModels()).toBeNull();
145
+ const explicitEmpty = makeEngine({ modelCatalog: { dynamic: false, models: [] } });
146
+ expect(explicitEmpty.engine.listModels()).toEqual([]);
147
+ const listed = makeEngine();
148
+ expect(listed.engine.listModels()).toEqual([
149
+ { id: "glm-4.6", aliases: ["glm"], canonicalRef: "zai/glm-4.6" },
150
+ { id: "mimo-v2.5-pro", canonicalRef: "xiaomi-token-plan-cn/mimo-v2.5-pro" },
151
+ ]);
152
+ const nullForm = makeEngine({ modelCatalog: null });
153
+ expect(nullForm.engine.listModels()).toBeNull(); // null 合法等价省略
154
+ await Promise.all([omitted, explicitEmpty, listed, nullForm].map((f) => f.cleanup()));
155
+ });
156
+
157
+ it("validateModel 成员不实现:manifest 省略 → typeof validateModel === 'undefined'(消费方跳过校验恒放行)", () => {
158
+ const { engine } = makeEngine({ modelCatalog: undefined });
159
+ expect(typeof (engine as unknown as { validateModel?: unknown }).validateModel).toBe("undefined");
160
+ const { engine: withCatalog } = makeEngine();
161
+ expect(typeof (withCatalog as unknown as { validateModel?: unknown }).validateModel).toBe("function");
162
+ });
163
+
164
+ it("validateModel 判定:命中回 canonicalRef;alias 命中;未命中 dynamic:false → engine_model_unknown 同步拒;dynamic:true → 放行原样 ref", () => {
165
+ const { engine } = makeEngine();
166
+ expect(engine.validateModel("zai/glm-4.6")).toEqual({ canonicalRef: "zai/glm-4.6" });
167
+ expect(engine.validateModel("glm")).toEqual({ canonicalRef: "zai/glm-4.6" }); // alias
168
+ expect(engine.validateModel("mimo-v2.5-pro")).toEqual({
169
+ canonicalRef: "xiaomi-token-plan-cn/mimo-v2.5-pro",
170
+ });
171
+ // dynamic:true:未命中放行、原样 ref(运行期引擎为权威;无斜杠 ref 拆分 = 契约变更④归 W3)
172
+ expect(engine.validateModel("custom/new-model")).toEqual({ canonicalRef: "custom/new-model" });
173
+
174
+ const staticEngine = makeEngine({ modelCatalog: { dynamic: false, models: [{ id: "only-model" }] } });
175
+ expect(staticEngine.engine.validateModel("only-model")).toEqual({ canonicalRef: "only-model" });
176
+ expect(() => staticEngine.engine.validateModel("unknown-ref")).toThrowError(/engine_model_unknown/);
177
+ expect(() => staticEngine.engine.validateModel(undefined)).toThrowError(/engine_model_unknown/);
178
+ // dynamic:true 下 undefined ref(查引擎缺省)→ 放行(缺省模型运行期自证)
179
+ expect(engine.validateModel(undefined)).toEqual({ canonicalRef: "" });
180
+ });
181
+ });
182
+
183
+ describe("RemoteEngine run 帧映射", () => {
184
+ it("task 子集收窄(model/schemaEnv/cwd/engineFallback 改挂 ctx)+ ctxModel 投影 provider/id", async () => {
185
+ const { engine, cleanup } = makeEngine();
186
+ const { ctx, events } = makeCtx({
187
+ ctxModel: { id: "glm-5.1", name: "GLM", provider: "zai", reasoning: true },
188
+ schemaEnv: "ctx-schema-env",
189
+ engineFallback: { from: "pi", reason: "probe-failed" },
190
+ });
191
+ const task = {
192
+ prompt: "do things",
193
+ model: "zai/glm-4.6",
194
+ cwd: "/tmp/w2-cwd",
195
+ schemaEnv: "task-schema-env",
196
+ engine: "pi",
197
+ timeoutMs: 1_000,
198
+ returnMeta: true,
199
+ maxTurns: 5,
200
+ description: "desc-x",
201
+ worktree: true,
202
+ };
203
+ const result = await engine.run(task, ctx);
204
+ // 宿主自持字段(engine/timeoutMs/returnMeta)与双写字段(model/schemaEnv/cwd)不进 task 帧
205
+ const wire = extractRunParams(events);
206
+ expect(wire.task).toMatchObject({ prompt: "do things", maxTurns: 5, description: "desc-x", worktree: true });
207
+ expect(wire.task).not.toHaveProperty("model");
208
+ expect(wire.task).not.toHaveProperty("engine");
209
+ expect(wire.task).not.toHaveProperty("timeoutMs");
210
+ expect(wire.task).not.toHaveProperty("returnMeta");
211
+ expect(wire.task).not.toHaveProperty("schemaEnv");
212
+ expect(wire.task).not.toHaveProperty("cwd");
213
+ expect(wire.ctx).toMatchObject({
214
+ poolKey: "shared",
215
+ cwd: "/tmp/w2-cwd",
216
+ model: "zai/glm-4.6",
217
+ schemaEnv: "ctx-schema-env", // ctx 优先于 task(协议层单列,不双写)
218
+ ctxModel: "zai/glm-5.1",
219
+ engineFallback: { from: "pi", reason: "probe-failed" },
220
+ });
221
+ expect(wire.ctx).not.toHaveProperty("streamMode"); // 无 stream → 缺省(JSON 序列化丢 undefined 键)
222
+ expect(result.outcome.content).toBe("fake-content-run-1");
223
+ expect(result.handle.data.engineId).toBe("fake");
224
+ expect(result.handle.data.adapterVersion).toBe("fake-adapter");
225
+ await cleanup();
226
+ });
227
+
228
+ it("ctx.stream 存在 → streamMode stream;streamDelta 反向请求 → stream.onDelta(真 SubagentStream flush)", async () => {
229
+ const flushed: Array<string[] | undefined> = [];
230
+ const sink = {
231
+ setWidget: (_key: string, lines: string[] | undefined) => {
232
+ flushed.push(lines);
233
+ },
234
+ };
235
+ const stream = new SubagentStream("w2-rec", sink);
236
+ const { engine, cleanup } = makeEngine(undefined, {
237
+ args: [
238
+ FAKE_ENGINE,
239
+ "--run-actions",
240
+ JSON.stringify([{ op: "streamDelta", delta: "d1" }, { op: "streamDelta", delta: "d2" }]),
241
+ ],
242
+ });
243
+ const { ctx, events } = makeCtx({ stream });
244
+ await engine.run({ prompt: "p" }, ctx);
245
+ const wire = extractRunParams(events);
246
+ expect(wire.ctx.streamMode).toBe("stream");
247
+ // leading edge 同步 flush(["d1"]);trailing edge 100ms 合并——buffer 累积语义
248
+ // (widget 是累积预览,flush 不清 buffer,现状实现特征)
249
+ expect(flushed[0]).toEqual(["d1"]);
250
+ await new Promise((resolve) => setTimeout(resolve, 200));
251
+ expect(flushed[1]).toEqual(["d1d2"]);
252
+ stream.dispose();
253
+ await cleanup();
254
+ });
255
+
256
+ it("poolResolved / handleReady → RunContext 回调(journal 路径权威 + 运行中句柄回填)", async () => {
257
+ const pools: string[] = [];
258
+ const readies: Array<{ sessionRef: Record<string, string>; poolKey: string }> = [];
259
+ const { engine, cleanup } = makeEngine(undefined, {
260
+ args: [
261
+ FAKE_ENGINE,
262
+ "--run-actions",
263
+ JSON.stringify([
264
+ { op: "poolResolved", poolKey: "pool-9" },
265
+ { op: "handleReady", sessionRef: { sessionId: "s-9" }, poolKey: "pool-9" },
266
+ ]),
267
+ ],
268
+ });
269
+ const { ctx } = makeCtx({
270
+ onPoolResolved: (p: string) => pools.push(p),
271
+ onHandleReady: (partial) => readies.push(partial),
272
+ });
273
+ const result = await engine.run({ prompt: "p" }, ctx);
274
+ expect(pools).toEqual(["pool-9"]);
275
+ expect(readies).toEqual([{ sessionRef: { sessionId: "s-9" }, poolKey: "pool-9" }]);
276
+ // run 终态 handle 来自引擎应答(非 handleReady 的 partial——那是运行中回填通道)
277
+ expect(result.handle.data.sessionRef).toEqual({ sessionId: "fake-session-run-1" });
278
+ await cleanup();
279
+ });
280
+ });
281
+
282
+ describe("abort 分级(cancel 帧 + 3s 收敛窗口)", () => {
283
+ it("signal abort → cancel;引擎收敛(终态应答在窗口内)→ aborted 终态正常返回", async () => {
284
+ const controller = new AbortController();
285
+ const { engine, cleanup } = makeEngine(undefined, {
286
+ args: [FAKE_ENGINE, "--cancel-settle", "1", "--run-hang", "1"],
287
+ });
288
+ const { ctx } = makeCtx({ signal: controller.signal });
289
+ const runPromise = engine.run({ prompt: "p" }, ctx);
290
+ setTimeout(() => controller.abort(), 100);
291
+ const result = await runPromise;
292
+ expect(result.outcome.error).toContain("engine_run_failed");
293
+ expect(result.outcome.exitCode).toBeNull(); // 被信号杀死判据
294
+ expect(result.handle.data.engineId).toBe("fake");
295
+ await cleanup();
296
+ }, 15_000);
297
+
298
+ it("cancel 3s 未收敛 → 杀链 → run 合成 abort 终态(不 reject,EnginePort 契约)", async () => {
299
+ const controller = new AbortController();
300
+ const { engine, client, cleanup } = makeEngine(undefined, {
301
+ args: [FAKE_ENGINE, "--run-hang", "1"], // 不收敛(FAKE_CANCEL_SETTLE 缺省 0)
302
+ });
303
+ const { ctx } = makeCtx({ signal: controller.signal });
304
+ const runPromise = engine.run({ prompt: "p" }, ctx);
305
+ await waitForReady(client); // abort 抢在连接完成前会打断握手重建循环——先等 ready
306
+ const enginePid = client.enginePid!;
307
+ controller.abort();
308
+ const result = await runPromise; // ~3s 收敛窗口超时 → 杀链 → 合成终态
309
+ expect(result.outcome.error).toContain("engine_run_failed");
310
+ expect(result.outcome.error).toContain("aborted before terminal answer");
311
+ expect(result.outcome.exitCode).toBeNull();
312
+ await waitForTrue(() => isProcessAlive(enginePid) === false); // 杀链已执行
313
+ await cleanup();
314
+ }, 20_000);
315
+ });
316
+
317
+ describe("失败分界(运行中合成 vs prepare 期 reject)", () => {
318
+ it("运行中引擎崩溃 → 合成 error outcome(engine_crashed)+ 正常 handle(record 必须收尾)", async () => {
319
+ const { engine, cleanup } = makeEngine(undefined, {
320
+ args: [
321
+ FAKE_ENGINE,
322
+ "--run-actions",
323
+ JSON.stringify([{ op: "exit", code: 137, stderr: "boom\n" }]),
324
+ ],
325
+ });
326
+ const { ctx } = makeCtx();
327
+ const result = await engine.run({ prompt: "p" }, ctx);
328
+ expect(result.outcome.error).toContain("engine_crashed");
329
+ expect(result.outcome.error).toContain("boom");
330
+ expect(result.outcome.exitCode).toBeNull();
331
+ expect(result.handle.data.v).toBe(1);
332
+ expect(result.handle.data.poolKey).toBe("shared");
333
+ await cleanup();
334
+ });
335
+
336
+ it("prepare 期失败(版本协商越界)→ reject,不产生 handle(进程创建前失败)", async () => {
337
+ const { engine, cleanup } = makeEngine(undefined, {
338
+ args: [FAKE_ENGINE, "--protocol-version", "99"],
339
+ });
340
+ const { ctx } = makeCtx();
341
+ await expect(engine.run({ prompt: "p" }, ctx)).rejects.toMatchObject({
342
+ code: "engine_protocol_mismatch",
343
+ });
344
+ await cleanup();
345
+ }, 20_000);
346
+ });
347
+
348
+ describe("gate 位多声明 run 期判定(W3 契约⑤接线:manifest vs initialize 应答)", () => {
349
+ it("多声明(manifest maxTurns true vs 引擎应答 false)→ run reject engine_capability_mismatch,run 帧未发出(无事件回显)", async () => {
350
+ const { engine, cleanup } = makeEngine({
351
+ capabilities: { ...FAKE_MATCHED_CAPS, maxTurns: true },
352
+ });
353
+ const { ctx, events } = makeCtx();
354
+ await expect(engine.run({ prompt: "p" }, ctx)).rejects.toMatchObject({
355
+ code: "engine_capability_mismatch",
356
+ });
357
+ expect(events).toHaveLength(0); // gate 拒在 run 帧发出前——引擎未收到 run
358
+ await cleanup();
359
+ });
360
+
361
+ it("多声明(manifest sandbox emulated vs 引擎实态 none)→ 拒,错误消息含方向证据(manifest 声明 vs initialize 应答)", async () => {
362
+ const { engine, cleanup } = makeEngine(undefined, {
363
+ args: [FAKE_ENGINE, "--caps-override", JSON.stringify({ sandbox: "none" })],
364
+ });
365
+ const { ctx } = makeCtx();
366
+ await expect(engine.run({ prompt: "p" }, ctx)).rejects.toThrowError(
367
+ /manifest 声明的能力位 'sandbox(worktree)'(emulated)与引擎 initialize 应答不符(none)/,
368
+ );
369
+ await cleanup();
370
+ });
371
+
372
+ it("一致放行:manifest 与应答逐位一致 → run 正常完成(现有 run 帧映射用例同走此路径的显式锚点)", async () => {
373
+ const { engine, cleanup } = makeEngine();
374
+ const { ctx } = makeCtx();
375
+ const result = await engine.run({ prompt: "p" }, ctx);
376
+ expect(result.outcome.content).toBe("fake-content-run-1");
377
+ await cleanup();
378
+ });
379
+
380
+ it("非 gate 位不一致(personaInjection manifest 'flag' vs 应答 'prompt')→ 不阻断:run 完成 + 诊断面仅 warn 留痕", async () => {
381
+ const manifestCaps = { ...FAKE_MATCHED_CAPS, personaInjection: "flag" as const };
382
+ const { engine, cleanup } = makeEngine(
383
+ { capabilities: manifestCaps },
384
+ { manifestDiagnostics: { capabilities: manifestCaps } },
385
+ );
386
+ const warnSpy = vi.spyOn(getLogger("subagents"), "warn");
387
+ const { ctx } = makeCtx();
388
+ const result = await engine.run({ prompt: "p" }, ctx);
389
+ expect(result.outcome.content).toBe("fake-content-run-1"); // 放行
390
+ expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining("differ from manifest"));
391
+ warnSpy.mockRestore();
392
+ await cleanup();
393
+ });
394
+ });
395
+
396
+ describe("interact / read / probe / dispose 门面", () => {
397
+ it("read 协议帧携带 dataDir(必填:存量池时代相对 dbPath 定位)", async () => {
398
+ const { engine, cleanup } = makeEngine();
399
+ const view = await engine.read({
400
+ data: {
401
+ v: 1,
402
+ engineId: "fake",
403
+ sessionRef: { sessionId: "s-read" },
404
+ poolKey: "shared",
405
+ adapterVersion: "fake-adapter",
406
+ },
407
+ });
408
+ expect(view.engineId).toBe("fake");
409
+ expect(view.source).toBe("native");
410
+ expect(view.turns).toHaveLength(1);
411
+ await cleanup();
412
+ });
413
+
414
+ it("probe → ProbeReport;interact → delivered;dispose → client 停机(幂等)", async () => {
415
+ const { engine, cleanup } = makeEngine();
416
+ const report = await engine.probe();
417
+ expect(report.ok).toBe(true);
418
+ expect(report.engineVersion).toBe("fake-1.0.0");
419
+ const interact = await engine.interact({
420
+ data: {
421
+ v: 1,
422
+ engineId: "fake",
423
+ sessionRef: { sessionId: "s-1" },
424
+ poolKey: "shared",
425
+ adapterVersion: "fake-adapter",
426
+ },
427
+ }, { kind: "message", payload: "hi" });
428
+ expect(interact).toEqual({ ok: true, delivered: true });
429
+ await engine.dispose();
430
+ await expect(engine.dispose()).resolves.toBeUndefined(); // 幂等
431
+ await cleanup();
432
+ });
433
+
434
+ it("host/askUser 经 RemoteEngine 门面(uiRequestHandler 由 EngineClient 承接):请求形状 = UiRequest", async () => {
435
+ const seenRequests: UiRequest[] = [];
436
+ const { engine, cleanup } = makeEngine(undefined, {
437
+ uiRequestHandler: async (req) => {
438
+ seenRequests.push(req);
439
+ return { confirmed: true };
440
+ },
441
+ args: [
442
+ FAKE_ENGINE,
443
+ "--run-actions",
444
+ JSON.stringify([{ op: "askUser", request: { method: "confirm", id: "q-facade" } }]),
445
+ ],
446
+ });
447
+ const { ctx, events } = makeCtx();
448
+ await engine.run({ prompt: "p" }, ctx);
449
+ expect(seenRequests).toEqual([{ method: "confirm", id: "q-facade" }]);
450
+ const echo = events.find(
451
+ (e) => typeof (e as { message?: string }).message === "string"
452
+ && (e as { message: string }).message.startsWith("askUser-result:"),
453
+ ) as { message: string };
454
+ expect(echo.message).toContain('"confirmed":true');
455
+ await cleanup();
456
+ });
457
+ });
@@ -0,0 +1,103 @@
1
+ // src/execution/engine/client/client-options.ts
2
+ //
3
+ // EngineClient 构造参数与 spawn 配套 helper(自 engine-client.ts 拆出——max-lines
4
+ // 纪律)。字段级语义注释随定义走;EngineClient 是唯一消费者。
5
+
6
+ import { basename } from "node:path";
7
+
8
+ import {
9
+ getLogger,
10
+ type EngineCapabilities,
11
+ type EngineRelayEnv,
12
+ type HostLogParams,
13
+ type HostPermissionParams,
14
+ type InitializeResult,
15
+ type ModelCatalogEntry,
16
+ type UiRequestHandler,
17
+ } from "@zhushanwen/subagent-engine-sdk";
18
+
19
+ const logger = getLogger("subagents");
20
+
21
+ import type { MirrorChangeEvent } from "./mirror.ts";
22
+
23
+ /** EngineClient 构造参数。 */
24
+ export interface EngineClientOptions {
25
+ engineId: string;
26
+ /** 引擎 CLI 入口(descriptor command 解析后的绝对路径/可执行名)。 */
27
+ command: string;
28
+ args: readonly string[];
29
+ cwd?: string;
30
+ /** 宿主种类(pidfile 实例维度命名段:'pi' | 'runtime' | …)。 */
31
+ hostKind: string;
32
+ hostVersion?: string;
33
+ /** 引擎数据根(= L0 注入的 XYZ_AGENT_DATA_DIR 值;pidfile 目录与 hostInfo.dataRoot 同源)。 */
34
+ dataDir: string;
35
+ /** 宿主 env 基座(buildEngineChildEnv baseEnv;缺省 = {...process.env})。 */
36
+ baseEnv?: Record<string, string | undefined>;
37
+ // ── buildEngineChildEnv 的 L0/L2 余项(透传)──
38
+ engineNode?: string;
39
+ electronRunAsNode?: boolean;
40
+ relay?: EngineRelayEnv;
41
+ identityEnv?: Record<string, string | undefined>;
42
+ envPrefixes?: readonly string[];
43
+ processEnv?: Record<string, string | undefined>;
44
+ /** L3 显式配置(initialize.engineConfig 透传;缺省 {},不放凭据)。 */
45
+ engineConfig?: Record<string, string>;
46
+ /**
47
+ * host/askUser 应答端([D4-④] 唯一注入入口 = subagent-service init.uiRequestHandler;
48
+ * W6 拆 HostBridge 时接线)。缺省 → 引擎收 {unsupported:true} 自行降级。
49
+ */
50
+ uiRequestHandler?: UiRequestHandler;
51
+ /** host/permission 应答端(v1 骨架注入点)。缺省 → {unsupported:true}。 */
52
+ permissionHandler?: (params: HostPermissionParams) => Promise<{ approved: boolean }>;
53
+ /** host/log 落宿主日志(缺省 SDK logger facade)。 */
54
+ log?: (params: HostLogParams) => void;
55
+ /** 镜像状态广播接线(W6 notify 合并窗口 / 生命周期谓词)。 */
56
+ onMirrorChanged?: (event: MirrorChangeEvent) => void;
57
+ /** 引擎 cmdline 身份谓词覆盖(pidfile 三条件清扫防误杀校验;缺省按 command 词形)。 */
58
+ engineCmdlineMatcher?: (cmdline: string) => boolean;
59
+ /** manifest 诊断源(initialize 应答与 manifest 不一致 → warn 留痕,不参与判据)。 */
60
+ manifestDiagnostics?: {
61
+ capabilities?: EngineCapabilities;
62
+ models?: ModelCatalogEntry[] | null;
63
+ };
64
+ }
65
+
66
+
67
+
68
+ /** 缺省 cmdline 身份谓词:cmdline 含 command 全路径,或含以引擎入口 basename 结尾的词。 */
69
+ export function defaultEngineCmdlineMatcher(command: string): (cmdline: string) => boolean {
70
+ const base = basename(command);
71
+ return (cmdline: string) => {
72
+ if (cmdline.includes(command)) return true;
73
+ return cmdline.split(/\s+/).some((arg) => arg === command || arg.endsWith(`/${base}`));
74
+ };
75
+ }
76
+
77
+ /** manifest 诊断比对(应答仅诊断:不一致 → warn 留痕,不参与判据;EngineClient 握手尾调用)。 */
78
+ export function warnOnManifestDiagnostics(
79
+ engineId: string,
80
+ diag: { capabilities?: EngineCapabilities; models?: ModelCatalogEntry[] | null } | undefined,
81
+ result: InitializeResult,
82
+ ): void {
83
+ if (diag?.capabilities) {
84
+ const answered = JSON.stringify(result.capabilities ?? null);
85
+ const declared = JSON.stringify(diag.capabilities);
86
+ if (answered !== declared) {
87
+ logger.warn(
88
+ `[engine-client:${engineId}] initialize capabilities differ from manifest `
89
+ + `(diagnostic only): manifest=${declared} answered=${answered}`,
90
+ );
91
+ }
92
+ }
93
+ if (diag && "models" in diag) {
94
+ const answered = JSON.stringify(result.models ?? null);
95
+ const declared = JSON.stringify(diag.models ?? null);
96
+ if (answered !== declared) {
97
+ logger.warn(
98
+ `[engine-client:${engineId}] initialize models differ from manifest `
99
+ + `(diagnostic only): manifest=${declared} answered=${answered}`,
100
+ );
101
+ }
102
+ }
103
+ }