@zhushanwen/subagent-core 0.2.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 (330) hide show
  1. package/README.md +114 -0
  2. package/dist/chunk-3VOERJPJ.js +22 -0
  3. package/dist/chunk-A4IVZWVX.js +31 -0
  4. package/dist/execution/engine/engines/zcode/constants.cjs +54 -0
  5. package/dist/execution/engine/engines/zcode/constants.d.cts +31 -0
  6. package/dist/execution/engine/engines/zcode/constants.d.ts +31 -0
  7. package/dist/execution/engine/engines/zcode/constants.js +22 -0
  8. package/dist/execution/engine/engines/zcode/reader.cjs +276 -0
  9. package/dist/execution/engine/engines/zcode/reader.d.cts +20 -0
  10. package/dist/execution/engine/engines/zcode/reader.d.ts +20 -0
  11. package/dist/execution/engine/engines/zcode/reader.js +239 -0
  12. package/dist/execution/engine/paths.cjs +55 -0
  13. package/dist/execution/engine/paths.d.cts +8 -0
  14. package/dist/execution/engine/paths.d.ts +8 -0
  15. package/dist/execution/engine/paths.js +26 -0
  16. package/dist/execution/relay-env.cjs +62 -0
  17. package/dist/execution/relay-env.d.cts +33 -0
  18. package/dist/execution/relay-env.d.ts +33 -0
  19. package/dist/execution/relay-env.js +20 -0
  20. package/dist/index.cjs +1852 -0
  21. package/dist/index.d.cts +1184 -0
  22. package/dist/index.d.ts +1184 -0
  23. package/dist/index.js +1810 -0
  24. package/dist/types-BxyAidGf.d.cts +875 -0
  25. package/dist/types-BxyAidGf.d.ts +875 -0
  26. package/package.json +99 -0
  27. package/src/__tests__/agent-opts-resolver-schema-prompt.test.ts +142 -0
  28. package/src/__tests__/append-system-prompt-assembly.test.ts +242 -0
  29. package/src/__tests__/builtin-workflows-structure.test.ts +180 -0
  30. package/src/__tests__/derive-closed-display-parity.test.ts +93 -0
  31. package/src/__tests__/fr4-get-state-handshake.test.ts +125 -0
  32. package/src/__tests__/fr8-orphan-recovery.test.ts +259 -0
  33. package/src/__tests__/m2-append-content-probe.test.ts +73 -0
  34. package/src/__tests__/manifest-store.test.ts +368 -0
  35. package/src/__tests__/record-store-cache.test.ts +335 -0
  36. package/src/__tests__/record-store-index.test.ts +560 -0
  37. package/src/__tests__/record-store-last-line.test.ts +129 -0
  38. package/src/__tests__/review-fix-loop-script.test.ts +288 -0
  39. package/src/__tests__/review-fix-loop-utils.test.ts +2377 -0
  40. package/src/__tests__/robustness-low-batch1.test.ts +286 -0
  41. package/src/__tests__/robustness-medium-batch1.test.ts +69 -0
  42. package/src/__tests__/robustness-medium-batch2.test.ts +65 -0
  43. package/src/__tests__/robustness-medium-batch3.test.ts +30 -0
  44. package/src/__tests__/robustness-medium-batch4.test.ts +358 -0
  45. package/src/__tests__/session-runner.test.ts +69 -0
  46. package/src/__tests__/smoke.test.ts +9 -0
  47. package/src/__tests__/stream-sink.test.ts +203 -0
  48. package/src/core/__tests__/host-services.test.ts +144 -0
  49. package/src/core/__tests__/logger.test.ts +78 -0
  50. package/src/core/__tests__/notify-ports.test.ts +104 -0
  51. package/src/core/host-services.ts +97 -0
  52. package/src/core/logger.ts +47 -0
  53. package/src/core/notify-ports.ts +126 -0
  54. package/src/execution/__tests__/__fixtures__/notifier-golden-snapshots.json +53 -0
  55. package/src/execution/__tests__/__fixtures__/truncline.snapshot.json +1 -0
  56. package/src/execution/__tests__/agent-result-mapper.test.ts +150 -0
  57. package/src/execution/__tests__/alive-store.test.ts +147 -0
  58. package/src/execution/__tests__/ask-user-transit-e2e.test.ts +490 -0
  59. package/src/execution/__tests__/channel-registry-handshake.test.ts +242 -0
  60. package/src/execution/__tests__/chat-engine-routing.test.ts +610 -0
  61. package/src/execution/__tests__/chatmode-first-round-closure-spawn.test.ts +190 -0
  62. package/src/execution/__tests__/concurrency-pool.test.ts +250 -0
  63. package/src/execution/__tests__/config.test.ts +302 -0
  64. package/src/execution/__tests__/delivery-methods.test.ts +424 -0
  65. package/src/execution/__tests__/dialog-queue.test.ts +299 -0
  66. package/src/execution/__tests__/epipe-fallback.test.ts +241 -0
  67. package/src/execution/__tests__/execute-and-await-worktree.test.ts +243 -0
  68. package/src/execution/__tests__/execute-nesting.test.ts +323 -0
  69. package/src/execution/__tests__/execute-options-mapper.test.ts +200 -0
  70. package/src/execution/__tests__/execution-record.test.ts +1394 -0
  71. package/src/execution/__tests__/explicit-agent-ref-guard.test.ts +171 -0
  72. package/src/execution/__tests__/finalize-record.test.ts +367 -0
  73. package/src/execution/__tests__/finalized-marker.test.ts +104 -0
  74. package/src/execution/__tests__/format-schema-instruction.test.ts +169 -0
  75. package/src/execution/__tests__/gc-timer.test.ts +184 -0
  76. package/src/execution/__tests__/get-record-for-action-restart.test.ts +254 -0
  77. package/src/execution/__tests__/helpers/mock-extension-api.ts +30 -0
  78. package/src/execution/__tests__/helpers/spawn-mock.ts +242 -0
  79. package/src/execution/__tests__/host-mode.test.ts +87 -0
  80. package/src/execution/__tests__/lifecycle-manager-lock.test.ts +211 -0
  81. package/src/execution/__tests__/lifecycle-manager.test.ts +383 -0
  82. package/src/execution/__tests__/lifecycle-predicates.test.ts +116 -0
  83. package/src/execution/__tests__/manifest-parentid.test.ts +117 -0
  84. package/src/execution/__tests__/model-resolver.test.ts +465 -0
  85. package/src/execution/__tests__/nested-visibility-env-propagation.test.ts +287 -0
  86. package/src/execution/__tests__/nested-visibility.test.ts +325 -0
  87. package/src/execution/__tests__/output-collector.test.ts +358 -0
  88. package/src/execution/__tests__/path-encoding.test.ts +104 -0
  89. package/src/execution/__tests__/pi-invocation.test.ts +134 -0
  90. package/src/execution/__tests__/record-store.test.ts +1057 -0
  91. package/src/execution/__tests__/records-cwd-isolation.test.ts +91 -0
  92. package/src/execution/__tests__/recursive-visibility-baseline.test.ts +339 -0
  93. package/src/execution/__tests__/recursive-visibility-env.test.ts +273 -0
  94. package/src/execution/__tests__/relay-env.test.ts +42 -0
  95. package/src/execution/__tests__/resource-policy.test.ts +109 -0
  96. package/src/execution/__tests__/rpc-mode.test.ts +89 -0
  97. package/src/execution/__tests__/run-and-finalize-chatmode.test.ts +267 -0
  98. package/src/execution/__tests__/run-spawn-chatmode-settled.test.ts +253 -0
  99. package/src/execution/__tests__/run-spawn-edges.test.ts +687 -0
  100. package/src/execution/__tests__/run-spawn-integration.test.ts +933 -0
  101. package/src/execution/__tests__/run-spawn-resume.test.ts +322 -0
  102. package/src/execution/__tests__/run-spawn-rpc-mode.test.ts +196 -0
  103. package/src/execution/__tests__/run-spawn-stdout-callback-throw.test.ts +199 -0
  104. package/src/execution/__tests__/session-context-resolver.test.ts +167 -0
  105. package/src/execution/__tests__/session-file-gc.test.ts +293 -0
  106. package/src/execution/__tests__/session-reconstructor.test.ts +379 -0
  107. package/src/execution/__tests__/session-runner-epipe.test.ts +178 -0
  108. package/src/execution/__tests__/session-runner-schema-env.test.ts +350 -0
  109. package/src/execution/__tests__/spawn-args.test.ts +445 -0
  110. package/src/execution/__tests__/spawn-event-adapter-rpc.test.ts +189 -0
  111. package/src/execution/__tests__/spawn-event-adapter.test.ts +167 -0
  112. package/src/execution/__tests__/spawn-worktree-guidance.test.ts +207 -0
  113. package/src/execution/__tests__/spawned-children.test.ts +92 -0
  114. package/src/execution/__tests__/start-sync-model-guard.test.ts +150 -0
  115. package/src/execution/__tests__/stdin-writer.test.ts +462 -0
  116. package/src/execution/__tests__/stream-sink-retirement.test.ts +261 -0
  117. package/src/execution/__tests__/subagent-service-abort.test.ts +60 -0
  118. package/src/execution/__tests__/subagent-service-message-close.test.ts +629 -0
  119. package/src/execution/__tests__/subagent-service-parent-guard.test.ts +180 -0
  120. package/src/execution/__tests__/subagent-service.test.ts +788 -0
  121. package/src/execution/__tests__/subprocess-agent-runner-routing.test.ts +310 -0
  122. package/src/execution/__tests__/subprocess-agent-runner.test.ts +612 -0
  123. package/src/execution/__tests__/temp-prompt.test.ts +53 -0
  124. package/src/execution/__tests__/timeout-integration.test.ts +615 -0
  125. package/src/execution/__tests__/tombstone-store.test.ts +73 -0
  126. package/src/execution/__tests__/turn-limiter-semantics.test.ts +194 -0
  127. package/src/execution/__tests__/turn-limiter.test.ts +65 -0
  128. package/src/execution/__tests__/ui-channels.test.ts +187 -0
  129. package/src/execution/__tests__/ui-interaction-model.test.ts +67 -0
  130. package/src/execution/__tests__/ui-request-handler-factory.test.ts +369 -0
  131. package/src/execution/__tests__/ui-request-handler.test.ts +204 -0
  132. package/src/execution/__tests__/ui-request-observability.test.ts +113 -0
  133. package/src/execution/__tests__/ui-request-queue.test.ts +133 -0
  134. package/src/execution/__tests__/worktree-manager.test.ts +633 -0
  135. package/src/execution/__tests__/worktree-pid-registration.integration.test.ts +229 -0
  136. package/src/execution/__tests__/worktree-reconcile.integration.test.ts +181 -0
  137. package/src/execution/__tests__/worktree-registry.test.ts +199 -0
  138. package/src/execution/agent-registry.ts +199 -0
  139. package/src/execution/agent-result-mapper.ts +90 -0
  140. package/src/execution/alive-store.ts +119 -0
  141. package/src/execution/argv-mirror.ts +113 -0
  142. package/src/execution/best-effort.ts +38 -0
  143. package/src/execution/channel-registry-access.ts +144 -0
  144. package/src/execution/concurrency-pool.ts +116 -0
  145. package/src/execution/config.ts +165 -0
  146. package/src/execution/dialog-queue.ts +330 -0
  147. package/src/execution/engine/__tests__/common/data-dir.test.ts +78 -0
  148. package/src/execution/engine/__tests__/common/errors.test.ts +132 -0
  149. package/src/execution/engine/__tests__/common/event-journal.test.ts +177 -0
  150. package/src/execution/engine/__tests__/common/kill-chain.test.ts +192 -0
  151. package/src/execution/engine/__tests__/common/nesting-guard.test.ts +81 -0
  152. package/src/execution/engine/__tests__/common/persona-router.test.ts +123 -0
  153. package/src/execution/engine/__tests__/common/pool-manager.test.ts +154 -0
  154. package/src/execution/engine/__tests__/common/schema-emulation.test.ts +128 -0
  155. package/src/execution/engine/__tests__/conformance/__fixtures__/pi-golden-events.json +28 -0
  156. package/src/execution/engine/__tests__/conformance/agent-event-invariants.ts +141 -0
  157. package/src/execution/engine/__tests__/conformance/contract.abort.test.ts +109 -0
  158. package/src/execution/engine/__tests__/conformance/contract.agent-events.test.ts +101 -0
  159. package/src/execution/engine/__tests__/conformance/contract.probe.test.ts +77 -0
  160. package/src/execution/engine/__tests__/conformance/contract.read-degradation.test.ts +104 -0
  161. package/src/execution/engine/__tests__/conformance/engine-conformance.live.test.ts +201 -0
  162. package/src/execution/engine/__tests__/conformance/golden-replay.pi.test.ts +76 -0
  163. package/src/execution/engine/__tests__/conformance/golden-replay.zcode.test.ts +79 -0
  164. package/src/execution/engine/__tests__/engine-discovery.test.ts +87 -0
  165. package/src/execution/engine/__tests__/model-prompt.test.ts +197 -0
  166. package/src/execution/engine/__tests__/paths.test.ts +39 -0
  167. package/src/execution/engine/__tests__/registry.test.ts +120 -0
  168. package/src/execution/engine/__tests__/routing.test.ts +231 -0
  169. package/src/execution/engine/common/data-dir.ts +66 -0
  170. package/src/execution/engine/common/errors.ts +183 -0
  171. package/src/execution/engine/common/event-journal.ts +254 -0
  172. package/src/execution/engine/common/journal-replay.ts +62 -0
  173. package/src/execution/engine/common/kill-chain.ts +221 -0
  174. package/src/execution/engine/common/nesting-guard.ts +50 -0
  175. package/src/execution/engine/common/persona-router.ts +108 -0
  176. package/src/execution/engine/common/pool-manager.ts +226 -0
  177. package/src/execution/engine/common/schema-emulation.ts +189 -0
  178. package/src/execution/engine/common/session-view-projection.ts +51 -0
  179. package/src/execution/engine/engine-discovery.ts +65 -0
  180. package/src/execution/engine/engines/pi/__tests__/pi-engine.test.ts +469 -0
  181. package/src/execution/engine/engines/pi/__tests__/reader.test.ts +155 -0
  182. package/src/execution/engine/engines/pi/__tests__/task-spec-mapper.test.ts +164 -0
  183. package/src/execution/engine/engines/pi/pi-engine.ts +415 -0
  184. package/src/execution/engine/engines/pi/reader.ts +48 -0
  185. package/src/execution/engine/engines/pi/registration.ts +35 -0
  186. package/src/execution/engine/engines/pi/task-spec-mapper.ts +100 -0
  187. package/src/execution/engine/engines/zcode/__tests__/__fixtures__/zcode-golden-spawn.json +39 -0
  188. package/src/execution/engine/engines/zcode/__tests__/launcher.test.ts +150 -0
  189. package/src/execution/engine/engines/zcode/__tests__/parser.test.ts +246 -0
  190. package/src/execution/engine/engines/zcode/__tests__/preparer.test.ts +228 -0
  191. package/src/execution/engine/engines/zcode/__tests__/reader.test.ts +210 -0
  192. package/src/execution/engine/engines/zcode/__tests__/registration.test.ts +71 -0
  193. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.live.test.ts +127 -0
  194. package/src/execution/engine/engines/zcode/__tests__/zcode-engine.test.ts +580 -0
  195. package/src/execution/engine/engines/zcode/constants.ts +43 -0
  196. package/src/execution/engine/engines/zcode/golden-sample.ts +39 -0
  197. package/src/execution/engine/engines/zcode/launcher.ts +161 -0
  198. package/src/execution/engine/engines/zcode/parser.ts +436 -0
  199. package/src/execution/engine/engines/zcode/preparer.ts +363 -0
  200. package/src/execution/engine/engines/zcode/reader.ts +381 -0
  201. package/src/execution/engine/engines/zcode/registration.ts +37 -0
  202. package/src/execution/engine/engines/zcode/zcode-engine.ts +658 -0
  203. package/src/execution/engine/host-task-spec.ts +47 -0
  204. package/src/execution/engine/model-prompt.ts +158 -0
  205. package/src/execution/engine/paths.ts +42 -0
  206. package/src/execution/engine/port.ts +153 -0
  207. package/src/execution/engine/registry.ts +133 -0
  208. package/src/execution/engine/routing.ts +218 -0
  209. package/src/execution/engine/types.ts +309 -0
  210. package/src/execution/execute-options-mapper.ts +115 -0
  211. package/src/execution/execution-record.ts +984 -0
  212. package/src/execution/finalize-record.ts +254 -0
  213. package/src/execution/finalized-marker.ts +70 -0
  214. package/src/execution/get-state-handshake.ts +104 -0
  215. package/src/execution/host-mode.ts +56 -0
  216. package/src/execution/idle-gc.ts +47 -0
  217. package/src/execution/lifecycle-manager.ts +513 -0
  218. package/src/execution/lifecycle-predicates.ts +65 -0
  219. package/src/execution/manifest-store.ts +256 -0
  220. package/src/execution/model-config-service.ts +250 -0
  221. package/src/execution/model-resolver.ts +248 -0
  222. package/src/execution/notifier.ts +372 -0
  223. package/src/execution/notify-ledger.ts +580 -0
  224. package/src/execution/output-collector.ts +228 -0
  225. package/src/execution/path-encoding.ts +60 -0
  226. package/src/execution/pi-invocation.ts +120 -0
  227. package/src/execution/record-entry.ts +132 -0
  228. package/src/execution/record-store.ts +1265 -0
  229. package/src/execution/relay-env.ts +37 -0
  230. package/src/execution/session-context-resolver.ts +64 -0
  231. package/src/execution/session-file-gc.ts +120 -0
  232. package/src/execution/session-pending.ts +170 -0
  233. package/src/execution/session-reconstructor.ts +678 -0
  234. package/src/execution/session-runner.ts +1789 -0
  235. package/src/execution/sessions-index.ts +304 -0
  236. package/src/execution/spawn-event-adapter.ts +363 -0
  237. package/src/execution/stdin-writer.ts +198 -0
  238. package/src/execution/stream-sink.ts +126 -0
  239. package/src/execution/subagent-service.ts +2268 -0
  240. package/src/execution/subprocess-agent-runner.ts +317 -0
  241. package/src/execution/temp-prompt.ts +62 -0
  242. package/src/execution/tombstone-store.ts +72 -0
  243. package/src/execution/turn-limiter.ts +102 -0
  244. package/src/execution/types.ts +1023 -0
  245. package/src/execution/ui-channels.ts +216 -0
  246. package/src/execution/ui-interaction-model.ts +48 -0
  247. package/src/execution/ui-request-handler-factory.ts +243 -0
  248. package/src/execution/ui-request-observability.ts +81 -0
  249. package/src/execution/ui-request-queue.ts +184 -0
  250. package/src/execution/worktree-manager.ts +688 -0
  251. package/src/execution/worktree-registry.ts +279 -0
  252. package/src/index.ts +87 -0
  253. package/src/orchestration/__tests__/__fixtures__/worker-template.snapshot.txt +349 -0
  254. package/src/orchestration/__tests__/agent-call-catch-fallback.test.ts +202 -0
  255. package/src/orchestration/__tests__/agent-call-stream.test.ts +152 -0
  256. package/src/orchestration/__tests__/args-validator.test.ts +143 -0
  257. package/src/orchestration/__tests__/config-loader.test.ts +503 -0
  258. package/src/orchestration/__tests__/error-recovery-handlers.test.ts +809 -0
  259. package/src/orchestration/__tests__/error-recovery-postmessage-defense.test.ts +404 -0
  260. package/src/orchestration/__tests__/error-recovery-serialize-failed-result.test.ts +56 -0
  261. package/src/orchestration/__tests__/error-recovery-workflow-call.test.ts +166 -0
  262. package/src/orchestration/__tests__/execute-agent-call.test.ts +451 -0
  263. package/src/orchestration/__tests__/launcher-nested-workflow.test.ts +600 -0
  264. package/src/orchestration/__tests__/lifecycle-runid-injection.test.ts +96 -0
  265. package/src/orchestration/__tests__/lifecycle.test.ts +659 -0
  266. package/src/orchestration/__tests__/non-cloneable-return-e2e.test.ts +95 -0
  267. package/src/orchestration/__tests__/review-fix-loop-scriptpath-failfast.test.ts +183 -0
  268. package/src/orchestration/__tests__/script-lint.test.ts +831 -0
  269. package/src/orchestration/__tests__/skill-discovery.test.ts +210 -0
  270. package/src/orchestration/__tests__/test-mocks.ts +197 -0
  271. package/src/orchestration/__tests__/worker-exit-without-result.test.ts +368 -0
  272. package/src/orchestration/__tests__/worker-host.test.ts +120 -0
  273. package/src/orchestration/__tests__/worker-returnmeta-passthrough.test.ts +164 -0
  274. package/src/orchestration/__tests__/worker-script-builder-runtime.test.ts +563 -0
  275. package/src/orchestration/__tests__/worker-script-builder.test.ts +309 -0
  276. package/src/orchestration/__tests__/worker-script-template-snapshot.test.ts +129 -0
  277. package/src/orchestration/__tests__/workflow-nesting-e2e.test.ts +317 -0
  278. package/src/orchestration/__tests__/workflow-script-lint-memo.test.ts +110 -0
  279. package/src/orchestration/agent-opts-resolver.ts +174 -0
  280. package/src/orchestration/args-validator.ts +127 -0
  281. package/src/orchestration/config-loader.ts +316 -0
  282. package/src/orchestration/error-recovery.ts +1018 -0
  283. package/src/orchestration/execute-agent-call.ts +256 -0
  284. package/src/orchestration/launcher.ts +455 -0
  285. package/src/orchestration/lifecycle.ts +399 -0
  286. package/src/orchestration/models/__tests__/budget.test.ts +307 -0
  287. package/src/orchestration/models/agent-call.ts +83 -0
  288. package/src/orchestration/models/budget.ts +118 -0
  289. package/src/orchestration/models/ports.ts +164 -0
  290. package/src/orchestration/models/run-runtime.ts +104 -0
  291. package/src/orchestration/models/run-spec.ts +83 -0
  292. package/src/orchestration/models/run-state.ts +44 -0
  293. package/src/orchestration/models/trace.ts +183 -0
  294. package/src/orchestration/models/types.ts +301 -0
  295. package/src/orchestration/models/workflow-run.ts +254 -0
  296. package/src/orchestration/models/workflow-script-registry.ts +35 -0
  297. package/src/orchestration/models/workflow-script.ts +117 -0
  298. package/src/orchestration/script-lint.ts +766 -0
  299. package/src/orchestration/skill-discovery.ts +135 -0
  300. package/src/orchestration/worker-handle.ts +115 -0
  301. package/src/orchestration/worker-host.ts +98 -0
  302. package/src/orchestration/worker-script-builder.ts +421 -0
  303. package/src/orchestration/workflow-files.ts +85 -0
  304. package/src/orchestration/workflow-script-registry-impl.ts +133 -0
  305. package/src/shared/__tests__/agent-ref.test.ts +34 -0
  306. package/src/shared/__tests__/meta-parser.test.ts +304 -0
  307. package/src/shared/__tests__/model-ref.test.ts +306 -0
  308. package/src/shared/__tests__/resource-discovery-manifest-cache.test.ts +288 -0
  309. package/src/shared/__tests__/resource-discovery.test.ts +749 -0
  310. package/src/shared/__tests__/resource-meta.test.ts +51 -0
  311. package/src/shared/__tests__/schema-jsonify.test.ts +81 -0
  312. package/src/shared/__tests__/timer-delay.test.ts +61 -0
  313. package/src/shared/agent-event.ts +13 -0
  314. package/src/shared/agent-ref.ts +57 -0
  315. package/src/shared/meta-parser.ts +261 -0
  316. package/src/shared/model-ref.ts +286 -0
  317. package/src/shared/resource-discovery.ts +835 -0
  318. package/src/shared/resource-meta.ts +65 -0
  319. package/src/shared/schema-env.ts +44 -0
  320. package/src/shared/schema-jsonify.ts +58 -0
  321. package/src/shared/timer-delay.ts +54 -0
  322. package/src/shared/xml-injection.ts +35 -0
  323. package/workflows/README.md +81 -0
  324. package/workflows/_shared/agent-refs.cjs +40 -0
  325. package/workflows/chain.js +137 -0
  326. package/workflows/map-reduce.js +180 -0
  327. package/workflows/parallel.js +154 -0
  328. package/workflows/review-fix-loop-utils.cjs +1542 -0
  329. package/workflows/review-fix-loop.js +1605 -0
  330. package/workflows/scatter-gather.js +184 -0
@@ -0,0 +1,465 @@
1
+ // src/__tests__/model-resolver.test.ts
2
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
3
+
4
+ import {
5
+ availableThinkingLevels,
6
+ type ModelInfo,
7
+ type ModelRegistryLike,
8
+ resolveModel,
9
+ } from "../model-resolver.ts";
10
+
11
+ // ============================================================
12
+ // helpers
13
+ // ============================================================
14
+
15
+ function makeModel(over: Partial<ModelInfo> = {}): ModelInfo {
16
+ return {
17
+ id: over.id ?? "sonnet-4-5",
18
+ name: over.name ?? "Claude Sonnet 4.5",
19
+ provider: over.provider ?? "anthropic",
20
+ reasoning: over.reasoning ?? false,
21
+ thinkingLevelMap: over.thinkingLevelMap,
22
+ contextWindow: over.contextWindow,
23
+ };
24
+ }
25
+
26
+ /** 构造 mock registry:registered 表 (provider/modelId) → ModelInfo;authed 集合控制鉴权。 */
27
+ function makeRegistry(models: ModelInfo[], authed: string[] = models.map((m) => `${m.provider}/${m.id}`)): ModelRegistryLike {
28
+ const authSet = new Set(authed);
29
+ return {
30
+ getAvailable: () => models,
31
+ find: (provider, modelId) => models.find((m) => m.provider === provider && m.id === modelId),
32
+ hasConfiguredAuth: (m) => {
33
+ if (!m || typeof m !== "object") return false;
34
+ const mm = m as ModelInfo;
35
+ return authSet.has(`${mm.provider}/${mm.id}`);
36
+ },
37
+ };
38
+ }
39
+
40
+ /** 主 agent model(第三层兼底)。 */
41
+ const ctxModel = makeModel({ id: "main-model", provider: "main" });
42
+
43
+ // ============================================================
44
+ // resolveModel — 三层优先级
45
+ // ============================================================
46
+
47
+ describe("resolveModel — three-layer priority", () => {
48
+ it("L1: paramOverride.model wins over agentConfig and ctxModel", () => {
49
+ const m1 = makeModel({ id: "explicit", provider: "p1" });
50
+ const reg = makeRegistry([m1]);
51
+ const r = resolveModel(
52
+ { name: "worker", systemPrompt: "", model: "main/agent-md" },
53
+ reg,
54
+ { model: "p1/explicit" },
55
+ ctxModel,
56
+ );
57
+ expect(r.model.id).toBe("explicit");
58
+ });
59
+
60
+ it("L2: agentConfig.model used when no paramOverride", () => {
61
+ const m = makeModel({ id: "agent-md-model", provider: "ap" });
62
+ const reg = makeRegistry([m]);
63
+ const r = resolveModel(
64
+ { name: "worker", systemPrompt: "", model: "ap/agent-md-model" },
65
+ reg,
66
+ undefined,
67
+ ctxModel,
68
+ );
69
+ expect(r.model.id).toBe("agent-md-model");
70
+ });
71
+
72
+ it("L3: ctxModel (main agent model) used when no override and no agentConfig.model", () => {
73
+ const reg = makeRegistry([]);
74
+ const r = resolveModel(
75
+ { name: "worker", systemPrompt: "" },
76
+ reg,
77
+ undefined,
78
+ ctxModel,
79
+ );
80
+ expect(r.model).toBe(ctxModel);
81
+ expect(r.thinkingLevel).toBeUndefined();
82
+ });
83
+
84
+ it("L3: ctxModel used even when registry is empty (no lookup needed)", () => {
85
+ const reg = makeRegistry([]);
86
+ const r = resolveModel(undefined, reg, undefined, ctxModel);
87
+ expect(r.model).toBe(ctxModel);
88
+ });
89
+ });
90
+
91
+ // ============================================================
92
+ // 显式指定失败的错误行为
93
+ // ============================================================
94
+
95
+ describe("resolveModel — explicit override failures throw (no silent fallback)", () => {
96
+ it("paramOverride.model not in registry → throws (does NOT fall back to ctxModel)", () => {
97
+ const reg = makeRegistry([]);
98
+ expect(() =>
99
+ resolveModel(undefined, reg, { model: "x/nonexistent" }, ctxModel),
100
+ ).toThrow(/is not a registry entry/);
101
+ });
102
+
103
+ it("paramOverride.model found but auth missing → throws auth-specific message", () => {
104
+ const m = makeModel({ id: "unauthed", provider: "u" });
105
+ const reg = makeRegistry([m], []); // 无鉴权
106
+ expect(() =>
107
+ resolveModel(undefined, reg, { model: "u/unauthed" }, ctxModel),
108
+ ).toThrow(/exists but auth is not configured/);
109
+ });
110
+
111
+ it("agentConfig.model not in registry → throws", () => {
112
+ const reg = makeRegistry([]);
113
+ expect(() =>
114
+ resolveModel(
115
+ { name: "worker", systemPrompt: "", model: "x/missing" },
116
+ reg,
117
+ undefined,
118
+ ctxModel,
119
+ ),
120
+ ).toThrow(/is not a registry entry/);
121
+ });
122
+
123
+ it("no override, no agentConfig.model, no ctxModel → throws listing available", () => {
124
+ const m = makeModel({ id: "visible", provider: "v" });
125
+ const reg = makeRegistry([m]);
126
+ expect(() => resolveModel(undefined, reg, undefined, undefined)).toThrow(
127
+ /No available model.*Available models/s,
128
+ );
129
+ });
130
+
131
+ it("invalid model string (no slash) → throws", () => {
132
+ const reg = makeRegistry([]);
133
+ expect(() =>
134
+ resolveModel(undefined, reg, { model: "no-slash" }, ctxModel),
135
+ ).toThrow(/is not a registry entry/);
136
+ });
137
+ });
138
+
139
+ // ============================================================
140
+ // thinkingLevel 解析
141
+ // ============================================================
142
+
143
+ describe("resolveModel — thinkingLevel resolution", () => {
144
+ it("override path: returns requested thinkingLevel when model supports it", () => {
145
+ const m = makeModel({
146
+ id: "reasoning-model",
147
+ provider: "rp",
148
+ reasoning: true,
149
+ thinkingLevelMap: { low: 1, high: 2, xhigh: 3 },
150
+ });
151
+ const reg = makeRegistry([m]);
152
+ const r = resolveModel(undefined, reg, { model: "rp/reasoning-model", thinkingLevel: "high" });
153
+ expect(r.thinkingLevel).toBe("high");
154
+ });
155
+
156
+ it("override path: clamps down to highest available when requested unsupported", () => {
157
+ const m = makeModel({
158
+ id: "limited-model",
159
+ provider: "lp",
160
+ reasoning: true,
161
+ thinkingLevelMap: { low: 1, medium: 2 }, // 不含 xhigh
162
+ });
163
+ const reg = makeRegistry([m]);
164
+ const r = resolveModel(undefined, reg, { model: "lp/limited-model", thinkingLevel: "xhigh" });
165
+ expect(r.thinkingLevel).toBe("medium");
166
+ });
167
+
168
+ it("override path: returns undefined when model.reasoning === false", () => {
169
+ const m = makeModel({ id: "non-reasoning", provider: "nr", reasoning: false });
170
+ const reg = makeRegistry([m]);
171
+ const r = resolveModel(undefined, reg, { model: "nr/non-reasoning", thinkingLevel: "high" });
172
+ expect(r.thinkingLevel).toBeUndefined();
173
+ });
174
+
175
+ it("ctxModel path: thinkingLevel from paramOverride.thinkingLevel (pass-through, no clamp)", () => {
176
+ const reg = makeRegistry([]);
177
+ const r = resolveModel(undefined, reg, { thinkingLevel: "high" }, ctxModel);
178
+ // ctxModel reasoning=false,但 ctxModel 路径不 clamp(主 agent model 直接透传)
179
+ expect(r.thinkingLevel).toBe("high");
180
+ });
181
+
182
+ it("ctxModel path: thinkingLevel from agentConfig.thinkingLevel when no paramOverride", () => {
183
+ const reg = makeRegistry([]);
184
+ const r = resolveModel(
185
+ { name: "worker", systemPrompt: "", thinkingLevel: "medium" },
186
+ reg,
187
+ undefined,
188
+ ctxModel,
189
+ );
190
+ expect(r.thinkingLevel).toBe("medium");
191
+ });
192
+
193
+ it("ctxModel path: thinkingLevel undefined when no override and ctxModel non-reasoning", () => {
194
+ const reg = makeRegistry([]);
195
+ // ctxModel(makeModel 默认 reasoning=false)无 override → 非 reasoning 不支持 thinking
196
+ const r = resolveModel(undefined, reg, undefined, ctxModel);
197
+ expect(r.thinkingLevel).toBeUndefined();
198
+ });
199
+
200
+ // ── 默认 thinking 兜底:无显式指定时用「模型最高可用档」(不落 pi 默认 medium)──
201
+
202
+ it("ctxModel path: no override + reasoning model with map → highest mapped level", () => {
203
+ const reg = makeRegistry([]);
204
+ const m = makeModel({ id: "r-main", provider: "rm", reasoning: true, thinkingLevelMap: { low: 1, high: 2, xhigh: 3 } });
205
+ const r = resolveModel(undefined, reg, undefined, m);
206
+ expect(r.thinkingLevel).toBe("xhigh");
207
+ });
208
+
209
+ it("ctxModel path: no override + reasoning model with map containing max → max", () => {
210
+ const reg = makeRegistry([]);
211
+ const m = makeModel({ id: "r-max", provider: "rm", reasoning: true, thinkingLevelMap: { high: 2, max: 99 } });
212
+ const r = resolveModel(undefined, reg, undefined, m);
213
+ expect(r.thinkingLevel).toBe("max");
214
+ });
215
+
216
+ it("ctxModel path: no override + reasoning model without map → xhigh (pi-legal ceiling)", () => {
217
+ const reg = makeRegistry([]);
218
+ // 无 map 时不能用 "max"——pi 侧 model:"max" 后缀非法会导致 model 解析失败
219
+ const m = makeModel({ id: "r-nomap", provider: "rm", reasoning: true });
220
+ const r = resolveModel(undefined, reg, undefined, m);
221
+ expect(r.thinkingLevel).toBe("xhigh");
222
+ });
223
+
224
+ it("override path: no thinkingLevel param → defaults to model's highest mapped level", () => {
225
+ const m = makeModel({
226
+ id: "default-thinking",
227
+ provider: "dt",
228
+ reasoning: true,
229
+ thinkingLevelMap: { minimal: 1, medium: 2, high: 3 },
230
+ });
231
+ const reg = makeRegistry([m]);
232
+ const r = resolveModel(undefined, reg, { model: "dt/default-thinking" });
233
+ expect(r.thinkingLevel).toBe("high");
234
+ });
235
+ });
236
+
237
+ // ============================================================
238
+ // lookupModel 容错:剥离 ":thinkingLevel" 后缀(A)
239
+ // ============================================================
240
+
241
+ describe('resolveModel — strips ":thinkingLevel" suffix from model string (A)', () => {
242
+ it('resolves model passed with ":xhigh" suffix', () => {
243
+ const m = makeModel({ id: "ds-pro", provider: "deepseek-router", reasoning: true, thinkingLevelMap: { xhigh: 3 } });
244
+ const reg = makeRegistry([m]);
245
+ const r = resolveModel(undefined, reg, { model: "deepseek-router/ds-pro:xhigh" }, ctxModel);
246
+ expect(r.model.id).toBe("ds-pro");
247
+ });
248
+
249
+ it('resolves model passed with ":high" suffix (registry has no suffix)', () => {
250
+ const m = makeModel({ id: "sonnet", provider: "anthropic", reasoning: true, thinkingLevelMap: { high: 2 } });
251
+ const reg = makeRegistry([m]);
252
+ const r = resolveModel(undefined, reg, { model: "anthropic/sonnet:high" });
253
+ expect(r.model.id).toBe("sonnet");
254
+ });
255
+
256
+ it('strips ":off" suffix (off is a valid thinking level)', () => {
257
+ const m = makeModel({ id: "m1", provider: "p", reasoning: false });
258
+ const reg = makeRegistry([m]);
259
+ const r = resolveModel(undefined, reg, { model: "p/m1:off" });
260
+ expect(r.model.id).toBe("m1");
261
+ });
262
+
263
+ it('does NOT strip unrelated colon suffix (e.g. ":foo")', () => {
264
+ // ":foo" 不是合法 thinking level,不剥离 → 查不到 → 抛 not found
265
+ const m = makeModel({ id: "m1", provider: "p", reasoning: false });
266
+ const reg = makeRegistry([m]);
267
+ expect(() => resolveModel(undefined, reg, { model: "p/m1:foo" })).toThrow(/is not a registry entry/);
268
+ });
269
+
270
+ it('suffix-stripped resolve still respects explicit thinkingLevel param', () => {
271
+ const m = makeModel({ id: "ds-pro", provider: "deepseek-router", reasoning: true, thinkingLevelMap: { high: 2, xhigh: 3 } });
272
+ const reg = makeRegistry([m]);
273
+ // model 带 ":xhigh" 但 thinkingLevel param 指定 high -> thinking 取 high
274
+ const r = resolveModel(undefined, reg, { model: "deepseek-router/ds-pro:xhigh", thinkingLevel: "high" });
275
+ expect(r.model.id).toBe("ds-pro");
276
+ expect(r.thinkingLevel).toBe("high");
277
+ });
278
+ });
279
+
280
+ // ============================================================
281
+ // not-found 错误信息:列出相近可用 model(B)
282
+ // ============================================================
283
+
284
+ describe("resolveModel — not-found error suggests similar models (B)", () => {
285
+ it("not-found with available registry lists similar models", () => {
286
+ const m = makeModel({ id: "ds-pro", provider: "deepseek-router" });
287
+ const reg = makeRegistry([m]);
288
+ let msg = "";
289
+ try {
290
+ resolveModel(undefined, reg, { model: "deepseek-router/ds-por" }, ctxModel); // 拼写错误
291
+ } catch (e) {
292
+ msg = (e as Error).message;
293
+ }
294
+ expect(msg).toMatch(/is not a registry entry/);
295
+ expect(msg).toMatch(/deepseek-router\/ds-pro/); // 建议列表含正确拼写
296
+ });
297
+
298
+ it("not-found with empty registry reports no available models", () => {
299
+ const reg = makeRegistry([]);
300
+ expect(() => resolveModel(undefined, reg, { model: "x/none" }, ctxModel)).toThrow(
301
+ /Registry has no available models/,
302
+ );
303
+ });
304
+
305
+ it("auth-missing error does NOT list models, points to models.json", () => {
306
+ const m = makeModel({ id: "unauthed", provider: "u" });
307
+ const reg = makeRegistry([m], []);
308
+ let msg = "";
309
+ try {
310
+ resolveModel(undefined, reg, { model: "u/unauthed" }, ctxModel);
311
+ } catch (e) {
312
+ msg = (e as Error).message;
313
+ }
314
+ expect(msg).toMatch(/auth is not configured/);
315
+ expect(msg).toMatch(/models\.json/);
316
+ expect(msg).not.toMatch(/Similar available models/); // auth 错误不列 model
317
+ });
318
+
319
+ it("P1: resolves model passed with ':max' suffix (THINKING_ORDER includes 'max')", () => {
320
+ const m = makeModel({ id: "mock-model", provider: "p1", reasoning: true, thinkingLevelMap: { max: 4 } });
321
+ const reg = makeRegistry([m]);
322
+ const r = resolveModel(
323
+ { name: "worker", systemPrompt: "" },
324
+ reg,
325
+ { model: "p1/mock-model:max" },
326
+ ctxModel,
327
+ );
328
+ expect(r.model).toBeTruthy();
329
+ expect(r.model.id).toBe("mock-model");
330
+ expect(r.thinkingLevel).toBe("max");
331
+ });
332
+ });
333
+
334
+ describe("availableThinkingLevels", () => {
335
+ it("returns [] when reasoning false", () => {
336
+ expect(availableThinkingLevels({ reasoning: false })).toEqual([]);
337
+ });
338
+ it("returns [] when no thinkingLevelMap", () => {
339
+ expect(availableThinkingLevels({ reasoning: true })).toEqual([]);
340
+ });
341
+ it("filters THINKING_ORDER to non-null map entries, ascending", () => {
342
+ expect(
343
+ availableThinkingLevels({ reasoning: true, thinkingLevelMap: { off: 0, high: 2, low: 1, xhigh: 3 } }),
344
+ ).toEqual(["off", "low", "high", "xhigh"]);
345
+ });
346
+
347
+ it("P2: includes 'max' as the last level (THINKING_ORDER SSOT derived)", () => {
348
+ const levels = availableThinkingLevels({ reasoning: true, thinkingLevelMap: { off: 0, low: 1, high: 2, xhigh: 3, max: 4 } });
349
+ expect(levels).toEqual(["off", "low", "high", "xhigh", "max"]);
350
+ expect(levels.slice(-3)).toEqual(["high", "xhigh", "max"]);
351
+ });
352
+ });
353
+
354
+ // ============================================================
355
+ // [U1 ModelRef 全等裁决] 孪生守卫(D1 规则④,两路径共用)
356
+ // ============================================================
357
+
358
+ describe("resolveModel — U1 twin guard (D1 rule ④)", () => {
359
+ /** 含大小写孪生的 registry 快照(模拟 models-store 刷新后大小写两形态并存)。 */
360
+ function makeTwinRegistry(): ModelRegistryLike {
361
+ return makeRegistry([
362
+ makeModel({ id: "GLM-5.3-Flash", provider: "zai-coding-cn" }),
363
+ makeModel({ id: "glm-5.3-flash", provider: "zai-coding-cn" }),
364
+ ]);
365
+ }
366
+
367
+ it("显式入参全等命中但 registry 含孪生 → 拒单(ambiguous,不代改输入)", () => {
368
+ expect(() =>
369
+ resolveModel(undefined, makeTwinRegistry(), { model: "zai-coding-cn/GLM-5.3-Flash" }, ctxModel),
370
+ ).toThrow(/ambiguous case variants/);
371
+ });
372
+
373
+ it("ctxModel 继承路径孪生守卫同等生效(D2 豁免的只是存在性复查)", () => {
374
+ const main = makeModel({ id: "GLM-5.3-Flash", provider: "zai-coding-cn" });
375
+ expect(() => resolveModel(undefined, makeTwinRegistry(), undefined, main)).toThrow(
376
+ /ambiguous case variants/,
377
+ );
378
+ });
379
+
380
+ it("无孪生时 ctxModel 路径零误伤(引用透传不变,P-A2 放行路径)", () => {
381
+ const main = makeModel({ id: "main-model", provider: "main" });
382
+ const r = resolveModel(undefined, makeRegistry([]), undefined, main);
383
+ expect(r.model).toBe(main);
384
+ });
385
+
386
+ it("小写入参(存在 case variant)→ 首个建议为正确大写串并标注(S2 负面路径)", () => {
387
+ const reg = makeRegistry([makeModel({ id: "GLM-5.3-Flash", provider: "zai-coding-cn" })]);
388
+ let msg = "";
389
+ try {
390
+ resolveModel(undefined, reg, { model: "zai-coding-cn/glm-5.3-flash" }, ctxModel);
391
+ } catch (e) {
392
+ msg = (e as Error).message;
393
+ }
394
+ expect(msg).toMatch(/is not a registry entry/);
395
+ expect(msg).toContain('zai-coding-cn/GLM-5.3-Flash ← case variant of "glm-5.3-flash"');
396
+ });
397
+
398
+ it("放行后回显与入参全等(L1 路径 model 字段保持 registry 原始大小写)", () => {
399
+ const m = makeModel({ id: "GLM-5.3-Flash", provider: "zai-coding-cn" });
400
+ const r = resolveModel(undefined, makeRegistry([m]), { model: "zai-coding-cn/GLM-5.3-Flash" }, ctxModel);
401
+ expect(r.model.id).toBe("GLM-5.3-Flash");
402
+ expect(r.model.provider).toBe("zai-coding-cn");
403
+ });
404
+ });
405
+
406
+ // ============================================================
407
+ // ModelConfigService: initModel(ctxModel) → resolveModel L3 plumb-through
408
+ // changeset 主打路径的 runtime 契约保护(EA-4)。
409
+ // 链路:session_start initModel({ctxModel}) → _ctxModel 缓存 → resolveModel 第三层命中
410
+ // ============================================================
411
+
412
+ import * as fs from "node:fs";
413
+ import * as os from "node:os";
414
+ import * as path from "node:path";
415
+
416
+ import { ModelConfigService } from "../model-config-service.ts";
417
+
418
+ describe("ModelConfigService: ctx.model plumb-through (EA-4)", () => {
419
+ let tmpDir: string;
420
+
421
+ beforeEach(() => {
422
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "subagents-resolver-test-"));
423
+ });
424
+ afterEach(() => {
425
+ fs.rmSync(tmpDir, { recursive: true, force: true });
426
+ });
427
+
428
+ /** 构造已 initModel 的 ModelConfigService,ctxModel 注入缓存。 */
429
+ function makeService(ctxModel: ModelInfo | undefined, registry: ModelRegistryLike = makeRegistry([])): ModelConfigService {
430
+ const svc = new ModelConfigService({ agentDir: tmpDir });
431
+ svc.initModel({
432
+ modelRegistry: registry,
433
+ sessionId: "test-session",
434
+ ctxModel,
435
+ });
436
+ return svc;
437
+ }
438
+
439
+ it("initModel caches ctxModel; resolveModel L3 returns it (no override, no agentConfig.model)", () => {
440
+ const main = makeModel({ id: "main-model", provider: "main" });
441
+ const svc = makeService(main);
442
+
443
+ // 无 override、无 agentConfig.model → 第三层命中缓存的 ctxModel
444
+ const r = svc.resolveModel("general-purpose", undefined);
445
+ expect(r.model).toBe(main);
446
+ });
447
+
448
+ it("resolveModel L3 hits cached ctxModel even with agent that has no model in frontmatter", () => {
449
+ const main = makeModel({ id: "inherited", provider: "parent" });
450
+ const svc = makeService(main);
451
+
452
+ // worker.md 无 model frontmatter → 跳过 L2,命中 L3 ctxModel
453
+ const r = svc.resolveModel("worker", undefined);
454
+ expect(r.model).toBe(main);
455
+ });
456
+
457
+ it("resolveModel still prefers explicit paramOverride over cached ctxModel (L1 > L3)", () => {
458
+ const main = makeModel({ id: "main", provider: "main" });
459
+ const explicit = makeModel({ id: "explicit", provider: "p1" });
460
+ const svc = makeService(main, makeRegistry([explicit]));
461
+
462
+ const r = svc.resolveModel("worker", { model: "p1/explicit" });
463
+ expect(r.model.id).toBe("explicit");
464
+ });
465
+ });