@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
@@ -1,473 +0,0 @@
1
- // connection.test.ts —— R2 连接层测试:全部跑 __fixtures__/fake-appserver.mjs 子进程
2
- // (移植自 zsw 仓 84b63a0^ fake-server 测试模式,vitest 形态改造),绝不 spawn 真
3
- // zcode.cjs。覆盖验收条款:四帧型分发逐型 / 请求-应答 id 关联(并发不串)/
4
- // requestRuntimePreferences 常量应答逐字段 / 未知反向回空 result / onClose 全部在途
5
- // reject(含 stderr 尾 400 截断)/ 惰性启动与进程死后重建 / stderr tee 落盘 /
6
- // protocol 自报忽略 / 无 jsonrpc 字段的帧收发(出站精确键集 = 对面 strict 拒未知键
7
- // 的镜像义务)/ env 惯例 / 关闭原语幂等。
8
-
9
- import { existsSync, mkdtempSync, readFileSync } from "node:fs";
10
- import { tmpdir } from "node:os";
11
- import { join } from "node:path";
12
- import { fileURLToPath } from "node:url";
13
-
14
- import { afterEach, describe, expect, it } from "vitest";
15
-
16
- import { ZCODE_APPSERVER_STDERR_TAIL_CHARS } from "../constants.ts";
17
- import {
18
- AppServerConnection,
19
- RUNTIME_PREFERENCES,
20
- buildAppServerEnv,
21
- isAppServerRpcError,
22
- } from "../connection.ts";
23
-
24
- const TMP = mkdtempSync(join(tmpdir(), "zcode-conn-"));
25
- const FAKE_CLI = fileURLToPath(new URL("./__fixtures__/fake-appserver.mjs", import.meta.url));
26
-
27
- const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
28
-
29
- /** fake 侧事件流水条目(JSONL:boot/env/recv/reverse-answer)。 */
30
- interface StateEvent {
31
- seq: number;
32
- ev: string;
33
- [key: string]: unknown;
34
- }
35
-
36
- function readState(file: string): StateEvent[] {
37
- try {
38
- return readFileSync(file, "utf8")
39
- .trim()
40
- .split("\n")
41
- .filter(Boolean)
42
- .map((l) => JSON.parse(l) as StateEvent);
43
- } catch {
44
- return [];
45
- }
46
- }
47
-
48
- function isRecord(v: unknown): v is Record<string, unknown> {
49
- return typeof v === "object" && v !== null && !Array.isArray(v);
50
- }
51
-
52
- /** 轮询等待 fake 侧异步效果可见(fake 异步处理 stdin,不能假设即时)。 */
53
- async function waitFor(cond: () => boolean, ms = 5_000, step = 25): Promise<boolean> {
54
- const end = Date.now() + ms;
55
- while (Date.now() < end) {
56
- if (cond()) return true;
57
- await sleep(step);
58
- }
59
- return cond();
60
- }
61
-
62
- interface FakeHandle {
63
- conn: AppServerConnection;
64
- stateFile: string;
65
- stderrLog: string;
66
- cwd: string;
67
- }
68
-
69
- interface FakeOpts {
70
- /** FAKE_STDERR=1:启动时往 stderr 写两行。 */
71
- stderrLines?: boolean;
72
- /** FAKE_EXTRA_KEYS=1:fake 应答/推送帧携带未知键(客户端宽容解析断言)。 */
73
- extraKeys?: boolean;
74
- /** FAKE_PROTOCOL_PUSH=1:协议自报走 {method:'protocol'} 推送形态。 */
75
- protocolPush?: boolean;
76
- /** 基 env 预置引擎原生嵌套标记(nesting guard 剥离断言)。 */
77
- polluteNested?: boolean;
78
- /** 注入故障反向 handler(fire-and-forget catch 断言)。 */
79
- reverseHandlers?: Readonly<Record<string, (params: unknown) => unknown>>;
80
- }
81
-
82
- const CONNECTIONS: AppServerConnection[] = [];
83
- let connSeq = 0;
84
-
85
- /** 建一个连到 fake 的连接(env 面向子进程固化,测试间零共享状态)。 */
86
- function makeConnection(opts: FakeOpts = {}): FakeHandle {
87
- connSeq += 1;
88
- const stateFile = join(TMP, `state-${connSeq}.jsonl`);
89
- const stderrLog = join(TMP, `stderr-${connSeq}.log`);
90
- const cwd = join(TMP, `cwd-${connSeq}`);
91
- const base: NodeJS.ProcessEnv = {
92
- PATH: process.env.PATH ?? "",
93
- HOME: "/fake-host-home",
94
- FAKE_STATE_FILE: stateFile,
95
- ...(opts.stderrLines ? { FAKE_STDERR: "1" } : {}),
96
- ...(opts.extraKeys ? { FAKE_EXTRA_KEYS: "1" } : {}),
97
- ...(opts.protocolPush ? { FAKE_PROTOCOL_PUSH: "1" } : {}),
98
- ...(opts.polluteNested ? { ZSW_NESTED: "1", CLAUDECODE: "1", PI_SUBAGENT_DEPTH: "2" } : {}),
99
- };
100
- const conn = new AppServerConnection({
101
- cliPath: FAKE_CLI,
102
- cwd,
103
- env: buildAppServerEnv(base),
104
- stderrLogPath: stderrLog,
105
- ...(opts.reverseHandlers ? { reverseHandlers: opts.reverseHandlers } : {}),
106
- });
107
- CONNECTIONS.push(conn);
108
- return { conn, stateFile, stderrLog, cwd };
109
- }
110
-
111
- afterEach(async () => {
112
- await Promise.allSettled(CONNECTIONS.splice(0).map((c) => c.shutdown({ graceMs: 1_000 })));
113
- });
114
-
115
- // ------------------------------------------- 惰性启动与重建(D1 / 不变量 4)
116
-
117
- describe("惰性启动与惰性重建", () => {
118
- it("构造不 spawn:无流水、无 pid、alive=false(连接构建零成本)", async () => {
119
- const { conn, stateFile } = makeConnection();
120
- expect(conn.alive).toBe(false);
121
- expect(conn.pid).toBeUndefined();
122
- await sleep(80);
123
- expect(existsSync(stateFile)).toBe(false);
124
- expect(conn.pid).toBeUndefined();
125
- });
126
-
127
- it("首次请求才 spawn:boot 流水出现,argv 形态 = `app-server --cwd <dir>`(D10 基线,不带 --stdio/--surface)", async () => {
128
- const { conn, stateFile, cwd } = makeConnection();
129
- const r = await conn.request("test/echo", { marker: "first" });
130
- expect(r).toEqual({ marker: "first" });
131
- const boots = readState(stateFile).filter((e) => e.ev === "boot");
132
- expect(boots).toHaveLength(1);
133
- expect(boots[0].argv).toEqual(["app-server", "--cwd", cwd]);
134
- expect(conn.pid).toBeGreaterThan(0);
135
- expect(conn.alive).toBe(true);
136
- }, 10_000);
137
-
138
- it("进程死后下次请求自动重建(与惰性启动同路径):boot 两次、pid 变化", async () => {
139
- const { conn, stateFile } = makeConnection();
140
- await conn.request("test/echo", { n: 1 });
141
- const pidBefore = conn.pid;
142
- await expect(conn.request("test/suicide")).rejects.toThrow(/app-server/);
143
- expect(await waitFor(() => !conn.alive)).toBe(true);
144
- const r2 = await conn.request("test/echo", { n: 2 });
145
- expect(r2).toEqual({ n: 2 });
146
- const boots = readState(stateFile).filter((e) => e.ev === "boot");
147
- expect(boots).toHaveLength(2);
148
- expect(boots[1].pid).not.toBe(boots[0].pid);
149
- expect(conn.pid).not.toBe(pidBefore);
150
- }, 15_000);
151
-
152
- it("spawn 失败(nodeBin ENOENT)走同一收割路径:请求 reject 带 spawn 失败语境,连接不悬挂", async () => {
153
- const conn = new AppServerConnection({
154
- cliPath: FAKE_CLI,
155
- cwd: TMP,
156
- nodeBin: join(TMP, "no-such-node-bin"),
157
- env: { PATH: process.env.PATH ?? "" },
158
- stderrLogPath: join(TMP, "enoent.log"),
159
- });
160
- CONNECTIONS.push(conn);
161
- await expect(conn.request("test/echo", {})).rejects.toThrow(/spawn 失败/);
162
- expect(await waitFor(() => !conn.alive)).toBe(true);
163
- }, 10_000);
164
- });
165
-
166
- // ------------------------------------------- 四帧型分发(附录 A.1)
167
-
168
- describe("四帧型分发(NDJSON,无 jsonrpc 字段)", () => {
169
- it("客户端请求帧:出站精确键集 {id, method, params}——无 jsonrpc、无未知键(对面 strict 拒未知键)", async () => {
170
- const { conn, stateFile } = makeConnection();
171
- await conn.request("test/echo", { a: 1 });
172
- const frames = readState(stateFile)
173
- .map((e) => (e as Record<string, unknown>).frame)
174
- .filter((f): f is Record<string, unknown> => isRecord(f) && f.method === "test/echo");
175
- expect(frames).toHaveLength(1);
176
- expect(Object.keys(frames[0]).sort()).toEqual(["id", "method", "params"]);
177
- expect(typeof frames[0].id).toBe("number");
178
- expect("jsonrpc" in frames[0]).toBe(false);
179
- });
180
-
181
- it("应答帧(result):resolve 应答值;帧级未知键宽容解析(我们是客户端,strict 校验是对面)", async () => {
182
- const { conn } = makeConnection({ extraKeys: true });
183
- const r = await conn.request<{ marker: string }>("test/echo", { marker: "lenient" });
184
- expect(r).toEqual({ marker: "lenient" });
185
- // 推送帧携带未知键同样不破坏分发
186
- const received: unknown[] = [];
187
- conn.onNotification("test/notification", (p) => received.push(p));
188
- await conn.request("test/push", { pushMethod: "test/notification", pushParams: { delta: "hi" } });
189
- expect(received).toEqual([{ delta: "hi" }]);
190
- });
191
-
192
- it("应答帧(error):code/message/data 挂到 reject Error(A.3 错误码通道,-32602 zod 诊断随 data 带出)", async () => {
193
- const { conn } = makeConnection();
194
- const err = await conn
195
- .request("test/fail", {
196
- code: -32602,
197
- message: "Invalid params",
198
- data: [{ path: ["model"], message: "not an object" }],
199
- })
200
- .then(
201
- () => {
202
- throw new Error("should reject");
203
- },
204
- (e: unknown) => e,
205
- );
206
- expect(isAppServerRpcError(err)).toBe(true);
207
- if (isAppServerRpcError(err)) {
208
- expect(err.code).toBe(-32602);
209
- expect(err.data).toEqual([{ path: ["model"], message: "not an object" }]);
210
- expect(err.message).toContain("[-32602]");
211
- expect(err.message).toContain("Invalid params");
212
- }
213
- });
214
-
215
- it("服务端推送帧:onNotification(method) 收到 params;handler 异常不拖死帧泵;退订生效", async () => {
216
- const { conn } = makeConnection();
217
- const received: unknown[] = [];
218
- const receivedBoom: unknown[] = [];
219
- const receivedOff: unknown[] = [];
220
- conn.onNotification("test/notification", (params) => {
221
- received.push(params);
222
- });
223
- conn.onNotification("test/notification", (params) => {
224
- receivedBoom.push(params);
225
- throw new Error("handler boom");
226
- });
227
- const off = conn.onNotification("test/notification", (params) => {
228
- receivedOff.push(params);
229
- });
230
- off();
231
- await conn.request("test/push", { pushMethod: "test/notification", pushParams: { delta: "hello" } });
232
- await conn.request("test/push", { pushMethod: "test/notification", pushParams: { delta: "again" } });
233
- expect(received).toEqual([{ delta: "hello" }, { delta: "again" }]);
234
- expect(receivedBoom).toEqual([{ delta: "hello" }, { delta: "again" }]);
235
- expect(receivedOff).toEqual([]);
236
- // 抛过异常后连接仍可用
237
- const r = await conn.request("test/echo", { ok: 1 });
238
- expect(r).toEqual({ ok: 1 });
239
- });
240
- });
241
-
242
- // ------------------------------------------- 请求-应答 id 关联
243
-
244
- describe("请求-应答 id 关联", () => {
245
- it("并发多请求不串线:3 个在途请求各自 resolve 自己的回显;id 自增互异", async () => {
246
- const { conn, stateFile } = makeConnection();
247
- const [a, b, c] = await Promise.all([
248
- conn.request("test/delay-echo", { who: "a", delayMs: 60 }),
249
- conn.request("test/delay-echo", { who: "b", delayMs: 30 }),
250
- conn.request("test/echo", { who: "c" }),
251
- ]);
252
- expect(a).toEqual({ who: "a", delayMs: 60 });
253
- expect(b).toEqual({ who: "b", delayMs: 30 });
254
- expect(c).toEqual({ who: "c" });
255
- const ids = readState(stateFile)
256
- .map((e) => (e as Record<string, unknown>).frame)
257
- .filter((f): f is { id: number } => isRecord(f) && typeof f.method === "string" && typeof f.id === "number")
258
- .map((f) => f.id);
259
- expect(ids).toHaveLength(3);
260
- expect(new Set(ids).size).toBe(3);
261
- expect([...ids].sort((x, y) => x - y)).toEqual([1, 2, 3]);
262
- });
263
-
264
- it("超时预算参数化:小预算 reject 超时,pending 清理后后续请求正常", async () => {
265
- const { conn } = makeConnection();
266
- await expect(conn.request("test/delay-echo", { delayMs: 5_000 }, { timeoutMs: 120 })).rejects.toThrow(/超时/);
267
- const r = await conn.request("test/echo", { after: "timeout" });
268
- expect(r).toEqual({ after: "timeout" });
269
- }, 10_000);
270
- });
271
-
272
- // ------------------------------------------- 反向请求应答(D9)
273
-
274
- describe("反向请求应答(D9)", () => {
275
- it("session/requestRuntimePreferences → 常量表应答逐字段(真实协议:create 先发反向请求并等应答)", async () => {
276
- const { conn, stateFile } = makeConnection();
277
- const created = await conn.request<{ session: { sessionId: string } }>("session/create", {
278
- workspace: { workspacePath: "/w", workspaceKey: "ws-x" },
279
- mode: "yolo",
280
- });
281
- expect(typeof created.session.sessionId).toBe("string");
282
- const answers = readState(stateFile).filter((e) => e.ev === "reverse-answer");
283
- expect(answers).toHaveLength(1);
284
- const result = (answers[0].answer as { result?: Record<string, unknown> }).result;
285
- // 逐字段断言(D9 常量逐字来自旧实现实测)
286
- expect(result).toEqual({ ...RUNTIME_PREFERENCES });
287
- expect(result?.nativeSearchEnhancementsEnabled).toBe(true);
288
- expect(result?.memoryEnabled).toBe(false);
289
- expect(result?.askUserQuestionAutoResolutionEnabled).toBe(true);
290
- expect(result?.modelContextBudgetStrategy).toBe("preflight-v1");
291
- expect(Object.keys(result ?? {}).sort()).toEqual([
292
- "askUserQuestionAutoResolutionEnabled",
293
- "memoryEnabled",
294
- "modelContextBudgetStrategy",
295
- "nativeSearchEnhancementsEnabled",
296
- ]);
297
- // 反向应答帧出站精确键集 {id, result}(无 jsonrpc、无未知键)
298
- const answerFrame = readState(stateFile)
299
- .map((e) => (e as Record<string, unknown>).frame)
300
- .find((f): f is Record<string, unknown> => isRecord(f) && typeof f.id === "string");
301
- expect(answerFrame).toBeDefined();
302
- expect(Object.keys(answerFrame!).sort()).toEqual(["id", "result"]);
303
- });
304
-
305
- it("未知反向请求 → 回 {id, result:{}}(不答会 15s 超时断连,旧实测 -32022)", async () => {
306
- const { conn, stateFile } = makeConnection();
307
- await conn.request("test/reverse", { reverseMethod: "permission/request", reverseParams: { tool: "Bash" } });
308
- const answers = readState(stateFile).filter((e) => e.ev === "reverse-answer");
309
- expect(answers).toHaveLength(1);
310
- const answer = answers[0].answer as { result?: unknown; error?: unknown };
311
- expect(answer.result).toEqual({});
312
- expect(answer.error).toBeUndefined();
313
- });
314
-
315
- it("应答失败不拖死连接(fire-and-forget + catch):故障 handler → 回 error 帧,连接仍可用", async () => {
316
- const { conn, stateFile } = makeConnection({
317
- reverseHandlers: {
318
- "session/requestRuntimePreferences": () => {
319
- throw new Error("prefs boom");
320
- },
321
- },
322
- });
323
- const created = await conn.request("session/create", {});
324
- expect(created).toBeTruthy();
325
- const answers = readState(stateFile).filter((e) => e.ev === "reverse-answer");
326
- expect(answers).toHaveLength(1);
327
- const answer = answers[0].answer as { error?: { code?: number; message?: string } };
328
- expect(answer.error?.code).toBe(-32000);
329
- expect(answer.error?.message).toContain("prefs boom");
330
- const r = await conn.request("test/echo", { still: "alive" });
331
- expect(r).toEqual({ still: "alive" });
332
- });
333
- });
334
-
335
- // ------------------------------------------- 崩溃收割(onClose)
336
-
337
- describe("崩溃收割(onClose/exit)", () => {
338
- it("进程意外退出:全部在途 reject,错误信息附 stderr 尾(400 字符截断)", async () => {
339
- const { conn } = makeConnection();
340
- await conn.request("test/echo", { warm: true });
341
- const errors = await Promise.all(
342
- [
343
- conn.request("test/delay-echo", { who: "a", delayMs: 10_000 }),
344
- conn.request("test/delay-echo", { who: "b", delayMs: 10_000 }),
345
- conn.request("test/suicide"),
346
- ].map((p) => p.then(
347
- () => {
348
- throw new Error("should reject");
349
- },
350
- (e: unknown) => e,
351
- )),
352
- );
353
- expect(errors).toHaveLength(3);
354
- for (const err of errors) {
355
- expect(err).toBeInstanceOf(Error);
356
- const msg = (err as Error).message;
357
- expect(msg).toContain("app-server");
358
- expect(msg).toContain("code=1");
359
- expect(msg).toContain("CRASH-MARK-TAIL");
360
- // 截断到常量值:600 个 A 只保留尾部(< 400),整段噪声不允许进错误信息
361
- expect(msg).not.toMatch(new RegExp(`A{${ZCODE_APPSERVER_STDERR_TAIL_CHARS}}`));
362
- }
363
- expect(await waitFor(() => !conn.alive)).toBe(true);
364
- }, 15_000);
365
- });
366
-
367
- // ------------------------------------------- stderr tee 落盘
368
-
369
- describe("stderr tee 落盘", () => {
370
- it("stderr 实时 append 到参数化路径(引擎数据目录注入点;测试用 tmp)", async () => {
371
- const { conn, stderrLog } = makeConnection({ stderrLines: true });
372
- await conn.request("test/echo", {});
373
- const ok = await waitFor(() => {
374
- try {
375
- const text = readFileSync(stderrLog, "utf8");
376
- return text.includes("fake-appserver stderr line A") && text.includes("fake-appserver stderr line B");
377
- } catch {
378
- return false;
379
- }
380
- });
381
- expect(ok).toBe(true);
382
- });
383
- });
384
-
385
- // ------------------------------------------- env 惯例(构造注入面)
386
-
387
- describe("env 惯例(共享宿主 HOME 形态)", () => {
388
- it("buildAppServerEnv:HOME 正向透传(共享宿主)+ 遥测关闭 + nesting guard 剥离/注入(fake 侧 env 快照)", async () => {
389
- const { conn, stateFile } = makeConnection({ polluteNested: true });
390
- await conn.request("test/echo", {});
391
- const envEvents = readState(stateFile).filter((e) => e.ev === "env");
392
- expect(envEvents).toHaveLength(1);
393
- const snap = envEvents[0] as {
394
- home?: string;
395
- telemetry?: string;
396
- nested?: string;
397
- unifiedNested?: string;
398
- };
399
- // 共享宿主 HOME 契约(2026-09 起):base env 携带的宿主 HOME 值原样透传到子进程
400
- // env(不覆写、不剥离——db/plugins/MCP 全继承宿主 HOME)
401
- expect(snap.home).toBe("/fake-host-home");
402
- expect(snap.telemetry).toBe("false");
403
- expect(snap.nested).toBeUndefined();
404
- expect(snap.unifiedNested).toBe("1");
405
- });
406
- });
407
-
408
- // ------------------------------------------- 关闭原语(R4 dispose 的连接侧原语)
409
-
410
- describe("关闭原语", () => {
411
- it("close():同步 SIGTERM 面——进程随后死亡;幂等;只起过一代", async () => {
412
- const { conn, stateFile } = makeConnection();
413
- await conn.request("test/echo", {});
414
- expect(conn.alive).toBe(true);
415
- conn.close();
416
- conn.close();
417
- expect(await waitFor(() => !conn.alive)).toBe(true);
418
- expect(readState(stateFile).filter((e) => e.ev === "boot")).toHaveLength(1);
419
- }, 10_000);
420
-
421
- it("shutdown():杀链走完进程退出;幂等;之后首个请求自动重建(不变量 4:与崩溃重建同路径)", async () => {
422
- const { conn, stateFile } = makeConnection();
423
- await conn.request("test/echo", {});
424
- await conn.shutdown({ graceMs: 1_500 });
425
- await conn.shutdown();
426
- expect(await waitFor(() => !conn.alive)).toBe(true);
427
- const r = await conn.request("test/echo", { reborn: true });
428
- expect(r).toEqual({ reborn: true });
429
- expect(readState(stateFile).filter((e) => e.ev === "boot")).toHaveLength(2);
430
- }, 15_000);
431
-
432
- it("close() 后调 shutdown():await close 发起的同一杀链,resolve 不早于进程退出(幂等——同一 promise)", async () => {
433
- const { conn, stateFile } = makeConnection();
434
- await conn.request("test/echo", {});
435
- const pid = conn.pid;
436
- conn.close();
437
- let shutdownSettled = false;
438
- const shutdownPromise = conn.shutdown().then(() => {
439
- shutdownSettled = true;
440
- });
441
- await shutdownPromise;
442
- expect(shutdownSettled).toBe(true);
443
- // killChain 挂 exit 事件 resolve(Node exit 前已收尸)——shutdown resolve 时进程
444
- // 必已退出:kill(0) 探活抛 ESRCH 即「resolve 不早于杀链完成」的机制性证据
445
- expect(() => process.kill(pid ?? -1, 0)).toThrow(/ESRCH/);
446
- // 幂等:再次 shutdown 复用同一 promise,无新杀链、无新进程
447
- await conn.shutdown();
448
- expect(readState(stateFile).filter((e) => e.ev === "boot")).toHaveLength(1);
449
- }, 10_000);
450
- });
451
-
452
- // ------------------------------------------- protocol 自报与宽容解析
453
-
454
- describe("protocol 自报与宽容解析", () => {
455
- it("首帧 {protocol:{name,version}} 自报忽略不抛,protocolInfo 记录,连接可用", async () => {
456
- const { conn } = makeConnection();
457
- const r = await conn.request("test/echo", { ok: 1 });
458
- expect(r).toEqual({ ok: 1 });
459
- expect(conn.protocolInfo).toEqual({ name: "ZCode Protocol", version: 1 });
460
- });
461
-
462
- it("protocol 推送形态 {method:'protocol'} 同样收敛到 protocolInfo", async () => {
463
- const { conn } = makeConnection({ protocolPush: true });
464
- await conn.request("test/echo", {});
465
- expect(conn.protocolInfo).toEqual({ name: "ZCode Protocol", version: 1 });
466
- });
467
-
468
- it("坏行跳过不断流:非 JSON 行之后正常应答", async () => {
469
- const { conn } = makeConnection();
470
- const r = await conn.request("test/malformed-first", {});
471
- expect(r).toEqual({ survived: true });
472
- });
473
- });
@@ -1,43 +0,0 @@
1
- // parser.test.ts —— usage 字段映射(事件层 execution 版)+ coarse 事件合成不变量回归。
2
- // 原 stdout 有界收集/终 JSON 解析/运行失败文案一族(含 golden spawn 样本回归)已随
3
- // CLI spawn 链删除(2026-09 单一 app-server 形态——终态来自 session-channel 收尾帧,
4
- // 不经 stdout 解析);mapZcodeOutcomeUsage(终态层映射)经 zcode-engine-appserver.test.ts
5
- // 的 outcome.usage 断言覆盖。
6
-
7
- import { describe, expect, it } from "vitest";
8
-
9
- import { mapZcodeUsage, synthesizeCoarseEvents } from "../parser.ts";
10
-
11
- describe("mapZcodeUsage:字段名映射", () => {
12
- it("usage 字段名映射:inputTokens→input 等四项(cost 无来源不出现)", () => {
13
- const u = mapZcodeUsage({
14
- inputTokens: 10,
15
- outputTokens: 20,
16
- cacheReadTokens: 5,
17
- cacheWriteTokens: 0,
18
- });
19
- expect(u).toEqual({ input: 10, output: 20, cacheRead: 5, cacheWrite: 0 });
20
- expect(u !== undefined && "cost" in u).toBe(false);
21
- });
22
-
23
- it("usage 形状不完整时显式 undefined(不给残缺对象)", () => {
24
- expect(mapZcodeUsage(undefined)).toBeUndefined();
25
- expect(mapZcodeUsage("nope")).toBeUndefined();
26
- expect(mapZcodeUsage({})).toBeUndefined();
27
- });
28
- });
29
-
30
- describe("synthesizeCoarseEvents:coarse 不变量", () => {
31
- it("message_end(含 usage)在前、turn_end 最后", () => {
32
- const usage = { input: 1, output: 2, cacheRead: 0, cacheWrite: 0 };
33
- const evs = synthesizeCoarseEvents("ok", usage);
34
- expect(evs).toEqual([{ type: "message_end", usage }, { type: "turn_end" }]);
35
- });
36
-
37
- it("无 usage 时 message_end 不带残缺字段", () => {
38
- const evs = synthesizeCoarseEvents("ok");
39
- expect(evs[0]).toEqual({ type: "message_end" });
40
- expect("usage" in evs[0]!).toBe(false);
41
- expect(evs[evs.length - 1]).toEqual({ type: "turn_end" });
42
- });
43
- });
@@ -1,124 +0,0 @@
1
- // preparer.test.ts —— v2 单源模型解析 / 模型可发现性 / 凭据与模型前置错误。
2
- // 凭据源 = v2 config 单源(2026-08-25 拍板:不读 ~/.zcode/cli/config.json——GUI 不
3
- // 管理该文件,可能残留历史验证配置)。原 HOME 池化面(池目录 SSOT / 原子写 /
4
- // mtime 免重写 / 池 config 无 plugins 块)已随「共享宿主 HOME」重构删除(2026-09:
5
- // app-server 侧凭据经 appserver-launcher fs 拦截注入,不再建池写 config)。
6
-
7
- import * as fs from "node:fs";
8
- import * as os from "node:os";
9
- import * as path from "node:path";
10
-
11
- import { beforeEach, describe, expect, it } from "vitest";
12
-
13
- import { ZcodePrepareError, listZcodeModels, resolveZcodeModelRef } from "../preparer.ts";
14
-
15
- let tmpRoot: string;
16
- let v2Path: string;
17
-
18
- function writeJson(p: string, v: unknown): void {
19
- fs.mkdirSync(path.dirname(p), { recursive: true });
20
- fs.writeFileSync(p, JSON.stringify(v, null, 2));
21
- }
22
-
23
- const PROVIDER_A = "builtin:bigmodel-coding-plan";
24
- const PROVIDER_B = "e512d53e-test-provider";
25
-
26
- function seedSources(): void {
27
- writeJson(v2Path, {
28
- provider: {
29
- [PROVIDER_A]: { options: { apiKey: "key-a", baseURL: "https://a.example" }, models: { "GLM-5.3": {}, "GLM-5.2": {} } },
30
- [PROVIDER_B]: { name: "test-router", options: { apiKey: "key-b", baseURL: "https://b.example" }, models: { "mimo-v2.5-pro": {} } },
31
- "no-key-provider": { options: { baseURL: "https://x.example" }, models: { "M1": {} } },
32
- },
33
- });
34
- }
35
-
36
- beforeEach(() => {
37
- tmpRoot = fs.mkdtempSync(path.join(os.tmpdir(), "zcode-preparer-"));
38
- v2Path = path.join(tmpRoot, "v2-config.json");
39
- seedSources();
40
- });
41
-
42
- describe("resolveZcodeModelRef(v2 单源)", () => {
43
- it("显式全名解析 + 规范化", () => {
44
- expect(resolveZcodeModelRef(`${PROVIDER_B}/mimo-v2.5-pro`, { v2ConfigPath: v2Path })).toBe(
45
- `${PROVIDER_B}/mimo-v2.5-pro`,
46
- );
47
- });
48
-
49
- it("短名按默认 provider(builtin:bigmodel-coding-plan)解析", () => {
50
- expect(resolveZcodeModelRef("GLM-5.3", { v2ConfigPath: v2Path })).toBe(
51
- `${PROVIDER_A}/GLM-5.3`,
52
- );
53
- });
54
-
55
- it("未指定时落官方兜底(不受任何本机 CLI 配置影响)", () => {
56
- expect(resolveZcodeModelRef(undefined, { v2ConfigPath: v2Path })).toBe(
57
- `${PROVIDER_A}/GLM-5.3`,
58
- );
59
- });
60
-
61
- it("未知模型 → model_not_available(列该 provider 可用模型)", () => {
62
- try {
63
- resolveZcodeModelRef(`${PROVIDER_A}/nope`, { v2ConfigPath: v2Path });
64
- expect.unreachable("should throw");
65
- } catch (err) {
66
- expect(err).toBeInstanceOf(ZcodePrepareError);
67
- const e = err as ZcodePrepareError;
68
- expect(e.code).toBe("model_not_available");
69
- expect(e.message).toContain("GLM-5.3, GLM-5.2");
70
- }
71
- });
72
-
73
- it("未知 provider → model_not_available(列带凭据 provider)", () => {
74
- try {
75
- resolveZcodeModelRef("ghost/m", { v2ConfigPath: v2Path });
76
- expect.unreachable("should throw");
77
- } catch (err) {
78
- expect((err as ZcodePrepareError).code).toBe("model_not_available");
79
- expect((err as ZcodePrepareError).message).toContain(PROVIDER_A);
80
- expect((err as ZcodePrepareError).message).toContain(PROVIDER_B);
81
- }
82
- });
83
-
84
- it("provider 存在但无 apiKey → engine_credential_missing", () => {
85
- try {
86
- resolveZcodeModelRef("no-key-provider/M1", { v2ConfigPath: v2Path });
87
- expect.unreachable("should throw");
88
- } catch (err) {
89
- expect((err as ZcodePrepareError).code).toBe("engine_credential_missing");
90
- }
91
- });
92
-
93
- it("v2 无任何带 apiKey 的 provider → engine_credential_missing(指向配置说明)", () => {
94
- writeJson(v2Path, { provider: {} });
95
- try {
96
- resolveZcodeModelRef(undefined, { v2ConfigPath: v2Path });
97
- expect.unreachable("should throw");
98
- } catch (err) {
99
- const e = err as ZcodePrepareError;
100
- expect(e.code).toBe("engine_credential_missing");
101
- expect(e.message).toContain("ZCode 桌面端");
102
- expect(e.message).toContain("docs/research/agent-engine-zcode.md");
103
- }
104
- });
105
- });
106
-
107
- describe("listZcodeModels(U7 可发现性)", () => {
108
- it("聚合 v2 带凭据 provider × models(含 name 拼接),无凭据/空清单过滤", () => {
109
- const models = listZcodeModels({ v2ConfigPath: v2Path });
110
- const ids = models.map((m) => m.id);
111
- expect(ids).toContain(`${PROVIDER_A}/GLM-5.3`);
112
- expect(ids).toContain(`${PROVIDER_A}/GLM-5.2`);
113
- expect(ids).toContain(`${PROVIDER_B}/mimo-v2.5-pro`);
114
- // 无凭据 provider 不进清单
115
- expect(ids.some((id) => id.startsWith("no-key-provider/"))).toBe(false);
116
- // name = "<provider.name> · <model>"(v2 有 name 字段时)
117
- const withName = models.find((m) => m.id === `${PROVIDER_B}/mimo-v2.5-pro`);
118
- expect(withName?.name).toBe("test-router · mimo-v2.5-pro");
119
- });
120
-
121
- it("v2 不可读 → 空清单(fail-safe)", () => {
122
- expect(listZcodeModels({ v2ConfigPath: path.join(tmpRoot, "absent.json") })).toEqual([]);
123
- });
124
- });