@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,34 @@
1
+ /**
2
+ * displayAgentName 测试:agent ref 绝对路径 → UI 显示短名(basename 去 .md)。
3
+ *
4
+ * 数据层不变式(displayAgentName 只服务显示出口):非路径值原样返回。
5
+ */
6
+ import { describe, expect, it } from "vitest";
7
+
8
+ import { displayAgentName } from "../agent-ref.ts";
9
+
10
+ describe("displayAgentName", () => {
11
+ it("mac/linux 绝对路径取 basename 并去 .md", () => {
12
+ expect(displayAgentName("/Users/x/.agents/agents/worker.md")).toBe("worker");
13
+ expect(displayAgentName("/home/u/project/.agents/agents/reviewer.md")).toBe("reviewer");
14
+ });
15
+
16
+ it("windows 路径(反斜杠分隔)同样取短名", () => {
17
+ expect(displayAgentName("C:\\Users\\x\\agents\\worker.md")).toBe("worker");
18
+ expect(displayAgentName("C:/Users/x/agents/worker.md")).toBe("worker");
19
+ });
20
+
21
+ it("非路径 agent 名原样返回(DEFAULT_AGENT_NAME 等)", () => {
22
+ expect(displayAgentName("general-purpose")).toBe("general-purpose");
23
+ expect(displayAgentName("worker")).toBe("worker");
24
+ });
25
+
26
+ it("无 .md 扩展名的 basename 原样返回(不去别的后缀)", () => {
27
+ expect(displayAgentName("/a/b/worker")).toBe("worker");
28
+ expect(displayAgentName("/a/b/agent.spec.ts")).toBe("agent.spec.ts");
29
+ });
30
+
31
+ it("裸文件名(无目录)也去 .md", () => {
32
+ expect(displayAgentName("worker.md")).toBe("worker");
33
+ });
34
+ });
@@ -0,0 +1,304 @@
1
+ /**
2
+ * meta-parser 测试(v5 §11.1 / TC1-TC10 + 边界 + exec-review 回归)
3
+ *
4
+ * [P-yaml] 探针由 TC5/TC10 承载:验证 eemeli/yaml 真实抛错对象有 linePos 字段且结构为
5
+ * {line,col}(取自 linePos[0])。不 mock parseYaml——必须用真实库验证此运行时断言。
6
+ *
7
+ * exec-review 回归:major-1(星斜杠截断)+ minor-2(kind 串类)+ minor-3(description 必填)
8
+ * + minor-4(detail 非字符串)+ minor-5(parameters 非对象)+ minor-6(空块 vs 缺块)+ minor-7(CRLF)。
9
+ *
10
+ * 框架:vitest(项目规范:禁 node:test,import from "vitest")。
11
+ */
12
+ import { describe, expect, it } from "vitest";
13
+
14
+ import {
15
+ parseResourceMeta,
16
+ parseResourceMetaDetailed,
17
+ } from "../meta-parser.ts";
18
+ import type { AgentMeta, WorkflowMeta } from "../resource-meta.ts";
19
+
20
+ // ── 固定 content fixtures ────────────────────────────────────
21
+
22
+ const VALID_WORKFLOW = `// 顶部可有任意代码注释
23
+ /* @pi-meta
24
+ name: chain
25
+ description: 通用编排三步链
26
+ when: 用户要顺序编排
27
+ phases: [analyze, transform, synthesize]
28
+ parameters:
29
+ type: object
30
+ properties:
31
+ task: { type: string }
32
+ required: [task]
33
+ usage: |
34
+ ## 使用说明
35
+ - task 必填
36
+ */
37
+ const agent = require("./agent");
38
+ `;
39
+
40
+ const VALID_AGENT = `---
41
+ name: code-review
42
+ description: 审查代码变更
43
+ when: 用户要求 review
44
+ notFor: 非 xyz-agent 项目
45
+ examples:
46
+ - match: "帮我 review 一下"
47
+ action: 调用 code-review
48
+ positive: true
49
+ - match: 今天天气
50
+ action: 不调用
51
+ positive: false
52
+ tools: [read, bash, grep]
53
+ model: claude-3.5
54
+ ---
55
+ # code-review agent 正文
56
+ `;
57
+
58
+ const MALFORMED_INDENT = `/* @pi-meta
59
+ name: x
60
+ description: bad
61
+ description: bad indent
62
+ phases: [a]
63
+ */`;
64
+
65
+ const REGEX_SINGLE_BACKSLASH = `/* @pi-meta
66
+ name: rfl
67
+ description: 测试正则转义
68
+ phases: [Review, Fix]
69
+ parameters:
70
+ type: object
71
+ patternProperties:
72
+ "^batch\\d+$": { type: string }
73
+ */`;
74
+
75
+ const LEGACY_CONST_META = `const meta = {
76
+ name: chain,
77
+ description: 旧格式,
78
+ phases: ["a"]
79
+ };
80
+ const agent = require("./agent");
81
+ `;
82
+
83
+ const NO_META_BLOCK = `const agent = require("./agent");
84
+ // 纯代码,无 @pi-meta
85
+ `;
86
+
87
+ const EMPTY_PI_META = `/* @pi-meta
88
+
89
+ */
90
+ const x = 1;
91
+ `;
92
+
93
+ const EMPTY_PHASES = `/* @pi-meta
94
+ name: x
95
+ description: 空 phases
96
+ phases: []
97
+ */`;
98
+
99
+ // ── parseResourceMeta(IF1,discovery)──────────────────────
100
+
101
+ describe("parseResourceMeta (discovery, fail-safe null)", () => {
102
+ it("TC1: 合法 workflow @pi-meta 返 WorkflowMeta", () => {
103
+ const meta = parseResourceMeta(VALID_WORKFLOW, "workflow");
104
+ expect(meta).not.toBeNull();
105
+ const wf = meta as WorkflowMeta;
106
+ expect(wf.kind).toBe("workflow");
107
+ expect(wf.name).toBe("chain");
108
+ expect(wf.phases).toEqual(["analyze", "transform", "synthesize"]);
109
+ expect(wf.when).toBe("用户要顺序编排");
110
+ expect(wf.parameters).toBeDefined();
111
+ expect(wf.usage).toContain("task 必填");
112
+ });
113
+
114
+ it("TC2: 合法 agent frontmatter 返 AgentMeta", () => {
115
+ const meta = parseResourceMeta(VALID_AGENT, "agent");
116
+ expect(meta).not.toBeNull();
117
+ const a = meta as AgentMeta;
118
+ expect(a.kind).toBe("agent");
119
+ expect(a.name).toBe("code-review");
120
+ expect(a.notFor).toBe("非 xyz-agent 项目");
121
+ expect(a.examples).toHaveLength(2);
122
+ expect(a.examples![0]).toEqual({ match: "帮我 review 一下", action: "调用 code-review", positive: true });
123
+ expect(a.examples![1]!.positive).toBe(false);
124
+ expect(a.tools).toEqual(["read", "bash", "grep"]);
125
+ expect(a.model).toBe("claude-3.5");
126
+ });
127
+
128
+ it("TC3: malformed YAML(缩进错)返 null,不抛 [P-yaml]", () => {
129
+ expect(() => parseResourceMeta(MALFORMED_INDENT, "workflow")).not.toThrow();
130
+ expect(parseResourceMeta(MALFORMED_INDENT, "workflow")).toBeNull();
131
+ });
132
+
133
+ it("TC4: 缺 meta 块返 null", () => {
134
+ expect(parseResourceMeta(NO_META_BLOCK, "workflow")).toBeNull();
135
+ });
136
+
137
+ it("TC8(修正): workflow 字段(phases)当 agent kind 解析,typecheckMeta 串类 reject [minor-2]", () => {
138
+ // 用 frontmatter 包裹 workflow 字段,确保 FRONTMATTER_RE 匹配、typecheckMeta 真正被触达
139
+ const content = `---
140
+ name: chainlike
141
+ description: has phases but parsed as agent
142
+ phases: [analyze, transform]
143
+ parameters:
144
+ type: object
145
+ ---
146
+ # agent body`;
147
+ expect(parseResourceMeta(content, "agent")).toBeNull();
148
+ });
149
+
150
+ it("TC8b: agent 字段(examples)当 workflow kind 解析,串类 reject [minor-2]", () => {
151
+ const content = `/* @pi-meta
152
+ name: x
153
+ description: has examples but workflow
154
+ phases: [A]
155
+ examples:
156
+ - match: m
157
+ action: a
158
+ positive: true
159
+ */`;
160
+ expect(parseResourceMeta(content, "workflow")).toBeNull();
161
+ });
162
+
163
+ it("TC9: legacy const meta(无 @pi-meta 块)返 null(无 adapter)[D1]", () => {
164
+ expect(parseResourceMeta(LEGACY_CONST_META, "workflow")).toBeNull();
165
+ });
166
+
167
+ it("TC10: patternProperties 单反斜杠正则致 YAML 解析失败返 null [P-yaml]", () => {
168
+ expect(parseResourceMeta(REGEX_SINGLE_BACKSLASH, "workflow")).toBeNull();
169
+ });
170
+
171
+ it("边界:空 @pi-meta 块(内容为空)返 null", () => {
172
+ expect(parseResourceMeta(EMPTY_PI_META, "workflow")).toBeNull();
173
+ });
174
+
175
+ it("边界:phases: [] 空数组合法(workflow 可无 phase)", () => {
176
+ const meta = parseResourceMeta(EMPTY_PHASES, "workflow");
177
+ expect(meta).not.toBeNull();
178
+ expect((meta as WorkflowMeta).phases).toEqual([]);
179
+ });
180
+
181
+ // ── exec-review 回归 ──────────────────────────────────────
182
+
183
+ it("major-1: usage 块标量含 */ 不再截断,parameters 不丢失", () => {
184
+ const content = `/* @pi-meta
185
+ name: x
186
+ description: d
187
+ phases: [A]
188
+ when: present
189
+ usage: |
190
+ see */ for details
191
+ parameters:
192
+ type: object
193
+ properties:
194
+ task: { type: string }
195
+ */`;
196
+ const meta = parseResourceMeta(content, "workflow");
197
+ expect(meta).not.toBeNull();
198
+ const wf = meta as WorkflowMeta;
199
+ expect(wf.parameters).toBeDefined(); // major-1:此前被 */ 截断静默丢失
200
+ expect(wf.when).toBe("present"); // 此前同样丢失
201
+ expect(wf.usage).toContain("see */ for details");
202
+ });
203
+
204
+ it("major-1b: patternProperties 正则含 * 后接 / 不再误截断", () => {
205
+ const content = `/* @pi-meta
206
+ name: re
207
+ description: regex
208
+ phases: [A]
209
+ parameters:
210
+ type: object
211
+ patternProperties:
212
+ "^\\\\d*/\\\\w+$": { type: string }
213
+ */`;
214
+ const meta = parseResourceMeta(content, "workflow");
215
+ expect(meta).not.toBeNull();
216
+ expect((meta as WorkflowMeta).parameters).toBeDefined();
217
+ });
218
+
219
+ it("minor-3: 缺 description 返 null(必填,不再静默空串)", () => {
220
+ const content = `/* @pi-meta
221
+ name: nodesc
222
+ phases: [A]
223
+ */`;
224
+ expect(parseResourceMeta(content, "workflow")).toBeNull();
225
+ });
226
+
227
+ it("minor-4: phase detail 非字符串返 null(不再静默丢弃)", () => {
228
+ const content = `/* @pi-meta
229
+ name: x
230
+ description: d
231
+ phases:
232
+ - title: A
233
+ detail: 123
234
+ */`;
235
+ expect(parseResourceMeta(content, "workflow")).toBeNull();
236
+ });
237
+
238
+ it("minor-5: parameters 是数组返 null(不再静默当 undefined)", () => {
239
+ const content = `/* @pi-meta
240
+ name: x
241
+ description: d
242
+ phases: [A]
243
+ parameters: [a, b]
244
+ */`;
245
+ expect(parseResourceMeta(content, "workflow")).toBeNull();
246
+ });
247
+
248
+ it("minor-7: CRLF 行尾 frontmatter 正确提取", () => {
249
+ const content = "---\r\nname: a\r\ndescription: d\r\n---\r\n# body";
250
+ const meta = parseResourceMeta(content, "agent");
251
+ expect(meta).not.toBeNull();
252
+ expect((meta as AgentMeta).name).toBe("a");
253
+ });
254
+ });
255
+
256
+ // ── parseResourceMetaDetailed(IF2,generate 闭环)──────────
257
+
258
+ describe("parseResourceMetaDetailed (generate, linePos)", () => {
259
+ it("TC6: 合法返 {ok:true, meta}", () => {
260
+ const r = parseResourceMetaDetailed(VALID_WORKFLOW, "workflow");
261
+ expect(r.ok).toBe(true);
262
+ if (r.ok) expect(r.meta.name).toBe("chain");
263
+ });
264
+
265
+ it("TC5: malformed YAML 返 {ok:false, error, linePos:{line,col}} [P-yaml]", () => {
266
+ const r = parseResourceMetaDetailed(MALFORMED_INDENT, "workflow");
267
+ expect(r.ok).toBe(false);
268
+ if (!r.ok) {
269
+ expect(r.error).toBeTruthy();
270
+ expect(r.linePos).toBeDefined();
271
+ expect(typeof r.linePos!.line).toBe("number");
272
+ expect(typeof r.linePos!.col).toBe("number");
273
+ expect(r.linePos!.line).toBeGreaterThan(0);
274
+ }
275
+ });
276
+
277
+ it("TC7: 缺块返 {ok:false, error} 无 linePos", () => {
278
+ const r = parseResourceMetaDetailed(NO_META_BLOCK, "workflow");
279
+ expect(r.ok).toBe(false);
280
+ if (!r.ok) expect(r.linePos).toBeUndefined();
281
+ });
282
+
283
+ it("TC10: 单反斜杠正则返 {ok:false, error 含 escape, linePos} [P-yaml]", () => {
284
+ const r = parseResourceMetaDetailed(REGEX_SINGLE_BACKSLASH, "workflow");
285
+ expect(r.ok).toBe(false);
286
+ if (!r.ok) {
287
+ expect(r.error).toContain("escape");
288
+ expect(r.linePos).toBeDefined();
289
+ expect(r.linePos!.line).toBeGreaterThan(0);
290
+ }
291
+ });
292
+
293
+ it("minor-6: 空块与缺块错误消息区分(空块不报「未找到」)", () => {
294
+ const empty = parseResourceMetaDetailed(EMPTY_PI_META, "workflow");
295
+ const missing = parseResourceMetaDetailed(NO_META_BLOCK, "workflow");
296
+ expect(empty.ok).toBe(false);
297
+ expect(missing.ok).toBe(false);
298
+ if (!empty.ok && !missing.ok) {
299
+ // 缺块报「未找到 meta 块」;空块报类型校验失败(块在但内容空/非法)
300
+ expect(missing.error).toContain("未找到");
301
+ expect(empty.error).not.toContain("未找到");
302
+ }
303
+ });
304
+ });
@@ -0,0 +1,306 @@
1
+ // src/shared/__tests__/model-ref.test.ts
2
+ //
3
+ // [U1 ModelRef 全等裁决] assertCanonicalModelRef / modelRefFromVerified 单测族。
4
+ // 设计权威:docs/design/subagent-dispatch-reliability.md §3.3 D1(规则①~⑤含孪生守卫)、D2(继承路径豁免口径)。
5
+ //
6
+ // P-A2(实施期门):孪生守卫行为验证——含孪生的 registry 快照实测拒单路径 +
7
+ // 无孪生快照实测放行路径。「有孪生仍放行」= 守卫失效,阻断合入。
8
+
9
+ import { describe, expect, it } from "vitest";
10
+
11
+ import {
12
+ type ModelRefSource,
13
+ assertCanonicalModelRef,
14
+ assertThinkingLevel,
15
+ modelRefFromVerified,
16
+ stripThinkingSuffix,
17
+ THINKING_ORDER,
18
+ } from "../model-ref.ts";
19
+
20
+ // ============================================================
21
+ // helpers
22
+ // ============================================================
23
+
24
+ /** registry 快照构造(getAvailable 顺序即判定顺序)。 */
25
+ function makeSource(entries: ReadonlyArray<{ provider: string; id: string }>): ModelRefSource {
26
+ return { getAvailable: () => entries };
27
+ }
28
+
29
+ /** 基线快照:模拟真实 registry 形态(大小写混合 id + 无关条目)。 */
30
+ const BASELINE: ReadonlyArray<{ provider: string; id: string }> = [
31
+ { provider: "zai-coding-cn", id: "GLM-5.3-Flash" },
32
+ { provider: "zai-coding-cn", id: "GLM-5.3" },
33
+ { provider: "xiaomi-token-plan-cn", id: "mimo-v2.5-pro" },
34
+ { provider: "deepseek", id: "deepseek-v4-pro" },
35
+ ];
36
+
37
+ // ============================================================
38
+ // 全等放行(规则③)+ 无孪生快照(P-A2 放行路径)
39
+ // ============================================================
40
+
41
+ describe("assertCanonicalModelRef — exact match passes (rule ③)", () => {
42
+ it("全等命中 → 返回 {provider, id},与 registry 条目逐字段全等", () => {
43
+ const ref = assertCanonicalModelRef("zai-coding-cn/GLM-5.3-Flash", makeSource(BASELINE));
44
+ expect(ref).toEqual({ provider: "zai-coding-cn", id: "GLM-5.3-Flash" });
45
+ });
46
+
47
+ it("P-A2 放行路径:无孪生快照下全等写法放行(含大小写差异的 id 原样保留)", () => {
48
+ // 快照中该 id 仅一种大小写形态(GLM-5.3-Flash ≠ glm-5.3,后者是不同 id 而非孪生)
49
+ const ref = assertCanonicalModelRef("zai-coding-cn/GLM-5.3-Flash", makeSource(BASELINE));
50
+ expect(ref.id).toBe("GLM-5.3-Flash");
51
+ expect(ref.provider).toBe("zai-coding-cn");
52
+ });
53
+
54
+ it("无关大小写的不同 id(GLM-5.3 vs GLM-5.3-Flash)不构成孪生,全等命中各自放行", () => {
55
+ const source = makeSource(BASELINE);
56
+ expect(assertCanonicalModelRef("zai-coding-cn/GLM-5.3", source)).toEqual({
57
+ provider: "zai-coding-cn",
58
+ id: "GLM-5.3",
59
+ });
60
+ });
61
+ });
62
+
63
+ // ============================================================
64
+ // 非全等同步拒单(规则②⑤)+ case variant 首位建议
65
+ // ============================================================
66
+
67
+ describe("assertCanonicalModelRef — non-exact rejected synchronously (rule ⑤)", () => {
68
+ it("小写变体输入 → 同步抛错(不采纳、不改写),错误含原始输入", () => {
69
+ expect(() =>
70
+ assertCanonicalModelRef("zai-coding-cn/glm-5.3-flash", makeSource(BASELINE)),
71
+ ).toThrow(/is not a registry entry/);
72
+ });
73
+
74
+ it("case variant 是首个建议且标注 ← case variant(S2 负面路径)", () => {
75
+ let msg = "";
76
+ try {
77
+ assertCanonicalModelRef("zai-coding-cn/glm-5.3-flash", makeSource(BASELINE), { source: "paramOverride" });
78
+ } catch (e) {
79
+ msg = (e as Error).message;
80
+ }
81
+ expect(msg).toMatch(/is not a registry entry/);
82
+ expect(msg).toContain('Model "zai-coding-cn/glm-5.3-flash" (paramOverride)');
83
+ expect(msg).toMatch(/Registry match is case-sensitive/);
84
+ expect(msg).toMatch(/Did you mean one of these\?/);
85
+ // case variant 排首位并标注
86
+ const didYouMeanIdx = msg.indexOf("Did you mean one of these?");
87
+ const variantLine = msg.indexOf("zai-coding-cn/GLM-5.3-Flash ← case variant of \"glm-5.3-flash\"");
88
+ expect(variantLine).toBeGreaterThan(didYouMeanIdx);
89
+ // 其他模糊候选在 case variant 之后
90
+ const otherIdx = msg.indexOf("Other models you may have meant");
91
+ expect(otherIdx).toBeGreaterThan(variantLine);
92
+ expect(msg).toContain("zai-coding-cn/GLM-5.3");
93
+ });
94
+
95
+ it("错误末尾附「省略 model 继承主 agent」指引", () => {
96
+ let msg = "";
97
+ try {
98
+ assertCanonicalModelRef("zai-coding-cn/glm-5.3-flash", makeSource(BASELINE));
99
+ } catch (e) {
100
+ msg = (e as Error).message;
101
+ }
102
+ expect(msg).toMatch(/Or omit the `model` param to inherit the main agent model\./);
103
+ });
104
+
105
+ it("provider 大小写不同(Zai-Coding-CN)→ 不命中(provider 精确匹配,规则②)", () => {
106
+ let msg = "";
107
+ try {
108
+ assertCanonicalModelRef("Zai-Coding-CN/GLM-5.3-Flash", makeSource(BASELINE));
109
+ } catch (e) {
110
+ msg = (e as Error).message;
111
+ }
112
+ expect(msg).toMatch(/is not a registry entry/);
113
+ // provider 拼写相近 → general 候选命中
114
+ expect(msg).toMatch(/Other models you may have meant/);
115
+ });
116
+
117
+ it("同 provider 但 id 不存在 → provider 相似度候选(同 provider 下其他条目)", () => {
118
+ let msg = "";
119
+ try {
120
+ assertCanonicalModelRef("zai-coding-cn/nonexistent-probe", makeSource(BASELINE), { source: "agentConfig" });
121
+ } catch (e) {
122
+ msg = (e as Error).message;
123
+ }
124
+ expect(msg).toMatch(/is not a registry entry/);
125
+ expect(msg).not.toMatch(/Did you mean/);
126
+ expect(msg).toMatch(/Other models you may have meant/);
127
+ expect(msg).toContain("zai-coding-cn/GLM-5.3-Flash");
128
+ expect(msg).toContain("(agentConfig)");
129
+ });
130
+
131
+ it("provider 与 id 均无相似物 → 列合法串全集 + 继承指引(S2 前者路径)", () => {
132
+ let msg = "";
133
+ try {
134
+ assertCanonicalModelRef("ghost-provider/zzz-unknown", makeSource(BASELINE));
135
+ } catch (e) {
136
+ msg = (e as Error).message;
137
+ }
138
+ expect(msg).toMatch(/is not a registry entry/);
139
+ expect(msg).not.toMatch(/Did you mean/);
140
+ expect(msg).toMatch(/No similar models found\./);
141
+ expect(msg).toMatch(/Available models:/);
142
+ // 合法串全集(canonical 形态,可复制)
143
+ expect(msg).toContain("zai-coding-cn/GLM-5.3-Flash");
144
+ expect(msg).toContain("xiaomi-token-plan-cn/mimo-v2.5-pro");
145
+ expect(msg).toMatch(/Or omit the `model` param to inherit the main agent model\./);
146
+ });
147
+
148
+ it("无 slash / 空 id → 同步拒单(结构非法)", () => {
149
+ const source = makeSource(BASELINE);
150
+ expect(() => assertCanonicalModelRef("no-slash", source)).toThrow(/is not a registry entry/);
151
+ expect(() => assertCanonicalModelRef("only-provider/", source)).toThrow(/is not a registry entry/);
152
+ });
153
+
154
+ it("空 registry → 明确提示 + 继承指引", () => {
155
+ let msg = "";
156
+ try {
157
+ assertCanonicalModelRef("p/m", makeSource([]));
158
+ } catch (e) {
159
+ msg = (e as Error).message;
160
+ }
161
+ expect(msg).toMatch(/Registry has no available models\./);
162
+ expect(msg).toMatch(/Or omit the `model` param/);
163
+ });
164
+ });
165
+
166
+ // ============================================================
167
+ // strip thinking 后缀(规则①)
168
+ // ============================================================
169
+
170
+ describe("assertCanonicalModelRef — strips legal thinking suffix (rule ①)", () => {
171
+ it('":xhigh" 后缀剥离后全等命中', () => {
172
+ const ref = assertCanonicalModelRef("deepseek/deepseek-v4-pro:xhigh", makeSource(BASELINE));
173
+ expect(ref).toEqual({ provider: "deepseek", id: "deepseek-v4-pro" });
174
+ });
175
+
176
+ it('":max" 后缀剥离后全等命中(THINKING_ORDER 含 max)', () => {
177
+ const ref = assertCanonicalModelRef("zai-coding-cn/GLM-5.3-Flash:max", makeSource(BASELINE));
178
+ expect(ref.id).toBe("GLM-5.3-Flash");
179
+ });
180
+
181
+ it('非白名单后缀(":foo")不剥离 → 未命中拒单', () => {
182
+ expect(() =>
183
+ assertCanonicalModelRef("zai-coding-cn/GLM-5.3-Flash:foo", makeSource(BASELINE)),
184
+ ).toThrow(/is not a registry entry/);
185
+ });
186
+ });
187
+
188
+ // ============================================================
189
+ // 孪生守卫(规则④)—— P-A2 拒单路径
190
+ // ============================================================
191
+
192
+ describe("assertCanonicalModelRef — twin guard (rule ④, P-A2)", () => {
193
+ /** 含大小写孪生的 registry 快照(模拟 models-store 刷新后 GLM-5.3-Flash 与 glm-5.3-flash 并存)。 */
194
+ const TWIN_SNAPSHOT: ReadonlyArray<{ provider: string; id: string }> = [
195
+ { provider: "zai-coding-cn", id: "GLM-5.3-Flash" },
196
+ { provider: "zai-coding-cn", id: "glm-5.3-flash" },
197
+ { provider: "deepseek", id: "deepseek-v4-pro" },
198
+ ];
199
+
200
+ it("P-A2 拒单路径:有孪生仍放行 = 守卫失效——全等命中(大写形态)也必须拒绝", () => {
201
+ let msg = "";
202
+ try {
203
+ // 输入与快照条目逐字符全等,但 registry 存在 case-insensitive 相等的另一条目
204
+ assertCanonicalModelRef("zai-coding-cn/GLM-5.3-Flash", makeSource(TWIN_SNAPSHOT));
205
+ } catch (e) {
206
+ msg = (e as Error).message;
207
+ }
208
+ expect(msg).toContain('Model "zai-coding-cn/GLM-5.3-Flash" matches a registry entry exactly');
209
+ expect(msg).toMatch(
210
+ /registry contains ambiguous case variants for zai-coding-cn\/GLM-5\.3-Flash: \[zai-coding-cn\/GLM-5\.3-Flash, zai-coding-cn\/glm-5\.3-flash\]/,
211
+ );
212
+ // 恢复指引:清理重复条目后重试
213
+ expect(msg).toMatch(/Recovery: remove the duplicate case variant from models\.json/);
214
+ expect(msg).toMatch(/then retry with the exact registry string\./);
215
+ });
216
+
217
+ it("P-A2 拒单路径:孪生快照下小写全等形态同样拒绝(两个方向都不放行)", () => {
218
+ expect(() =>
219
+ assertCanonicalModelRef("zai-coding-cn/glm-5.3-flash", makeSource(TWIN_SNAPSHOT)),
220
+ ).toThrow(/ambiguous case variants/);
221
+ });
222
+
223
+ it("无孪生快照下同串放行(守卫零误伤)", () => {
224
+ const ref = assertCanonicalModelRef("zai-coding-cn/GLM-5.3-Flash", makeSource(BASELINE));
225
+ expect(ref.id).toBe("GLM-5.3-Flash");
226
+ });
227
+
228
+ it("不同 provider 下的同形 id 不构成孪生(provider 精确作用域)", () => {
229
+ const snapshot = [
230
+ { provider: "zai-coding-cn", id: "GLM-5.3-Flash" },
231
+ { provider: "other-cn", id: "glm-5.3-flash" },
232
+ ];
233
+ const ref = assertCanonicalModelRef("zai-coding-cn/GLM-5.3-Flash", makeSource(snapshot));
234
+ expect(ref.id).toBe("GLM-5.3-Flash");
235
+ });
236
+ });
237
+
238
+ // ============================================================
239
+ // 继承路径包装(D2 豁免口径):modelRefFromVerified
240
+ // ============================================================
241
+
242
+ describe("modelRefFromVerified — ctxModel inheritance path (D2)", () => {
243
+ it("无孪生 → 包装为 ModelRef({provider, id} 形态,供 ${provider}/${id} 拼接)", () => {
244
+ const ref = modelRefFromVerified(
245
+ { provider: "zai-coding-cn", id: "GLM-5.3-Flash" },
246
+ makeSource(BASELINE),
247
+ );
248
+ expect(ref).toEqual({ provider: "zai-coding-cn", id: "GLM-5.3-Flash" });
249
+ });
250
+
251
+ it("豁免存在性复查:ctxModel 不在 registry 快照中仍放行(运行时已验证)", () => {
252
+ // registry 为空(快照过期等)不阻塞继承——缺省继承是输入缺省而非变体放行
253
+ const ref = modelRefFromVerified(
254
+ { provider: "runtime-only", id: "live-model" },
255
+ makeSource([]),
256
+ );
257
+ expect(ref).toEqual({ provider: "runtime-only", id: "live-model" });
258
+ });
259
+
260
+ it("P-A2 双路径生效:含孪生快照下继承路径同样拒绝(豁免的只是存在性复查,不是孪生守卫)", () => {
261
+ const TWIN_SNAPSHOT = [
262
+ { provider: "zai-coding-cn", id: "GLM-5.3-Flash" },
263
+ { provider: "zai-coding-cn", id: "glm-5.3-flash" },
264
+ ];
265
+ expect(() =>
266
+ modelRefFromVerified({ provider: "zai-coding-cn", id: "GLM-5.3-Flash" }, makeSource(TWIN_SNAPSHOT)),
267
+ ).toThrow(/ambiguous case variants/);
268
+ });
269
+ });
270
+
271
+ // ============================================================
272
+ // stripThinkingSuffix / assertThinkingLevel
273
+ // ============================================================
274
+
275
+ describe("stripThinkingSuffix", () => {
276
+ it("剥白名单后缀", () => {
277
+ expect(stripThinkingSuffix("p/m:high")).toBe("p/m");
278
+ expect(stripThinkingSuffix("p/m:off")).toBe("p/m");
279
+ expect(stripThinkingSuffix("p/m")).toBe("p/m");
280
+ });
281
+
282
+ it("非白名单冒号后缀不剥", () => {
283
+ expect(stripThinkingSuffix("p/m:foo")).toBe("p/m:foo");
284
+ });
285
+ });
286
+
287
+ describe("assertThinkingLevel", () => {
288
+ it("undefined 透传", () => {
289
+ expect(assertThinkingLevel(undefined)).toBeUndefined();
290
+ });
291
+
292
+ it("白名单值放行并窄化为字面量联合", () => {
293
+ expect(assertThinkingLevel("high")).toBe("high");
294
+ expect(assertThinkingLevel("max")).toBe("max");
295
+ });
296
+
297
+ it("非白名单值同步抛错(可操作文案:列出合法值)", () => {
298
+ expect(() => assertThinkingLevel("ultra")).toThrow(
299
+ /Invalid thinkingLevel "ultra"\. Allowed values: off, minimal, low, medium, high, xhigh, max\./,
300
+ );
301
+ });
302
+
303
+ it("THINKING_ORDER SSOT:白名单全集顺序稳定(低→高,含 max)", () => {
304
+ expect(THINKING_ORDER).toEqual(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
305
+ });
306
+ });