@iowarp/clio-coder 0.3.7 → 0.3.9

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 (400) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/README.md +13 -4
  3. package/dist/{acp-SK4MD6MM.js → acp-7LOELQFP.js} +13 -13
  4. package/dist/{agents-2FN2K6ME.js → agents-FIBG2SHA.js} +41 -37
  5. package/dist/assets/codewiki.json +1 -1
  6. package/dist/{auth-QIYZWM5I.js → auth-OI4LIH2I.js} +31 -24
  7. package/dist/builtins-AD25UL3C.js +17 -0
  8. package/dist/{chunk-5FR74PWO.js → chunk-2JDWVJND.js} +2 -2
  9. package/dist/chunk-3DPEIQKN.js +113 -0
  10. package/dist/{chunk-EOOQZZDE.js → chunk-3DUR4WUA.js} +19 -19
  11. package/dist/{chunk-WHJYKASB.js → chunk-3MRC2YSQ.js} +2 -2
  12. package/dist/{chunk-EBEFWSGL.js → chunk-3UUY7R3Z.js} +14 -10
  13. package/dist/{chunk-LADCF22A.js → chunk-3V5AYSEQ.js} +113 -54
  14. package/dist/{chunk-BMWK7ZIZ.js → chunk-465CC7FK.js} +16 -13
  15. package/dist/{chunk-5WIGXA4T.js → chunk-47CMYGET.js} +111 -4
  16. package/dist/{chunk-CEYBNUGC.js → chunk-4H6ULJ3H.js} +378 -36
  17. package/dist/{chunk-YTYFXUI3.js → chunk-4LJX2PUC.js} +9 -9
  18. package/dist/{chunk-DOOEX22V.js → chunk-56KB5IJP.js} +5 -5
  19. package/dist/{chunk-SPULKLCF.js → chunk-5DQRIYDZ.js} +2 -2
  20. package/dist/{chunk-TSHXZTOQ.js → chunk-5HFBWUMU.js} +23 -11
  21. package/dist/{chunk-5UJ6ECTS.js → chunk-5PVQ4SRS.js} +80 -8
  22. package/dist/{chunk-ZWLZP4ZT.js → chunk-5QKCQQ3E.js} +359 -17
  23. package/dist/{chunk-6M7VS3J3.js → chunk-5T7RBWN2.js} +111 -5
  24. package/dist/chunk-774ILSRL.js +172 -0
  25. package/dist/chunk-7C6RYZGQ.js +391 -0
  26. package/dist/{chunk-GH5622CP.js → chunk-A2NJGIB3.js} +2 -2
  27. package/dist/{chunk-C4JBQ5SR.js → chunk-AD7Y7STJ.js} +6 -6
  28. package/dist/{chunk-GEYXPTRF.js → chunk-AEYBF3TB.js} +33 -12
  29. package/dist/{chunk-2SFS6XQE.js → chunk-AMKHQW3C.js} +3 -2
  30. package/dist/{chunk-D4MDIG46.js → chunk-B5CSFE7B.js} +7 -7
  31. package/dist/{chunk-MXI6J5JF.js → chunk-B5XRQOLB.js} +10 -10
  32. package/dist/{chunk-X2KV5FXT.js → chunk-BVDVID7E.js} +2 -2
  33. package/dist/{chunk-JNXPYBB4.js → chunk-CA42X6KT.js} +3 -3
  34. package/dist/{chunk-VREKEFLL.js → chunk-D73KXYPF.js} +3 -3
  35. package/dist/{chunk-JTSEDYVQ.js → chunk-DG4M6ZUE.js} +7 -7
  36. package/dist/{chunk-DQA7QLMD.js → chunk-EBOC7MT3.js} +10 -25
  37. package/dist/{chunk-KZ2H5X4G.js → chunk-ECUO3KDP.js} +129 -14
  38. package/dist/{chunk-JRIO5UD2.js → chunk-EQ63NRB7.js} +5 -5
  39. package/dist/{chunk-YD734TPH.js → chunk-FALJGAWU.js} +2 -2
  40. package/dist/{chunk-GWS3VEIW.js → chunk-FWDFM5ZU.js} +24 -3
  41. package/dist/{chunk-XEGB6BCN.js → chunk-GAYUJ7LE.js} +68 -14
  42. package/dist/{chunk-UND3GU2L.js → chunk-H7IXIC72.js} +2 -2
  43. package/dist/{chunk-IR4CFBFN.js → chunk-HAY4ZE2P.js} +12 -12
  44. package/dist/{chunk-UVDSQ6LW.js → chunk-HCBCAYZU.js} +74 -147
  45. package/dist/{chunk-4DWFMQDR.js → chunk-HJB5IUKP.js} +89 -145
  46. package/dist/{chunk-M4AKACEO.js → chunk-HKO36JWF.js} +33 -5
  47. package/dist/{chunk-KCMKRQX4.js → chunk-HPCTNZM2.js} +45 -82
  48. package/dist/{chunk-465YSENW.js → chunk-IFBNV6H6.js} +3 -3
  49. package/dist/{chunk-FJ3H4MN5.js → chunk-IHKBWSXF.js} +2 -2
  50. package/dist/chunk-JEQQR47K.js +3025 -0
  51. package/dist/{chunk-FO5ZOVUY.js → chunk-KV2AOLDF.js} +27 -7
  52. package/dist/chunk-LU7P4LHA.js +33 -0
  53. package/dist/{chunk-6TUKSZVF.js → chunk-LXPJXFM5.js} +11 -11
  54. package/dist/{chunk-VQNODYQ4.js → chunk-MIX5N5AC.js} +488 -3668
  55. package/dist/chunk-MLOK6ZOS.js +2888 -0
  56. package/dist/{chunk-ZZMN5OM4.js → chunk-MV2VUEJC.js} +2 -2
  57. package/dist/{chunk-MVVUPGPW.js → chunk-MXHC5QYU.js} +6 -6
  58. package/dist/{chunk-OBMAI2DP.js → chunk-N3PBVRTZ.js} +12 -388
  59. package/dist/{chunk-WJHBC77E.js → chunk-N5XKWMDW.js} +17 -7
  60. package/dist/{chunk-5C3AQNDW.js → chunk-NNNWO6F2.js} +124 -36
  61. package/dist/{chunk-UFQ3F4FW.js → chunk-NQ6UCCOD.js} +4 -4
  62. package/dist/chunk-NUGM5KR6.js +165 -0
  63. package/dist/{chunk-DMD2AGVS.js → chunk-NZU6YDNV.js} +20 -18
  64. package/dist/{chunk-WHGPSPT5.js → chunk-O6I4CIEU.js} +151 -13
  65. package/dist/{chunk-XN3L4EYL.js → chunk-OEDBCISO.js} +2 -2
  66. package/dist/{chunk-PD3MESLB.js → chunk-P3JGPQFL.js} +4 -4
  67. package/dist/{chunk-UHXRNZ2J.js → chunk-PNY46YEY.js} +23 -6
  68. package/dist/{chunk-THKY7CD7.js → chunk-PZ4I4JE2.js} +134 -29
  69. package/dist/{chunk-SROCI7ZU.js → chunk-QQ7EKM72.js} +5 -5
  70. package/dist/{chunk-QCTRSGHQ.js → chunk-R7LNVMCS.js} +91 -53
  71. package/dist/{chunk-GOXNB3AO.js → chunk-RAPCMZL4.js} +75 -4
  72. package/dist/chunk-RKKLTLYB.js +45 -0
  73. package/dist/{chunk-OB5HIGJY.js → chunk-RKRLDWD3.js} +4 -1
  74. package/dist/{chunk-DJNLUABN.js → chunk-S4COXYBG.js} +588 -32
  75. package/dist/{chunk-3HAPLH5M.js → chunk-T3Z6VAAF.js} +172 -11
  76. package/dist/{chunk-FOT2FX5J.js → chunk-TD7UE2L5.js} +12 -10
  77. package/dist/{chunk-UUANF5CR.js → chunk-TEO2TLVN.js} +856 -967
  78. package/dist/{chunk-FCSXB6T2.js → chunk-UOSL25KY.js} +14 -2
  79. package/dist/{chunk-EELBMBT6.js → chunk-VKBMFOYV.js} +74 -15
  80. package/dist/chunk-VO2LKSTM.js +165 -0
  81. package/dist/{chunk-5C77SEEY.js → chunk-VPTUJU4P.js} +3 -3
  82. package/dist/{chunk-WEH5XRJQ.js → chunk-WIE7ZOSW.js} +2 -2
  83. package/dist/{chunk-J7PIKKWC.js → chunk-WXCJ7VME.js} +8 -8
  84. package/dist/{chunk-4DGYLA73.js → chunk-XDOQXGFO.js} +22 -7
  85. package/dist/{chunk-PPAMZ32Z.js → chunk-XK56QHLX.js} +6 -1
  86. package/dist/{chunk-AB4XIIVB.js → chunk-YKOFT37S.js} +6 -6
  87. package/dist/chunk-YSEHGPCT.js +127 -0
  88. package/dist/{chunk-HFSBBKSQ.js → chunk-YW7UVM5V.js} +138 -3
  89. package/dist/cli/index.js +32 -32
  90. package/dist/{clio-WBVQEBKO.js → clio-LT5V7SSZ.js} +9 -9
  91. package/dist/{code-nav-FGGFIE7L.js → code-nav-LMW275PA.js} +5 -5
  92. package/dist/codewiki/build-worker.js +4 -4
  93. package/dist/{components-F7OEATSO.js → components-ZFA3SAER.js} +8 -8
  94. package/dist/{config-TRBL3RCF.js → config-RXS5T3JT.js} +98 -65
  95. package/dist/{configure-OLCVPHNM.js → configure-2WYWSCSD.js} +26 -22
  96. package/dist/{context-MJIJ6GOX.js → context-I3BTOTCS.js} +12 -12
  97. package/dist/{context-XEWE3MOJ.js → context-MVOORGMF.js} +54 -47
  98. package/dist/{context-WFPKQSM6.js → context-PALKKQYL.js} +28 -28
  99. package/dist/{context-clear-KNOS2JPB.js → context-clear-N2WOYZ2K.js} +53 -46
  100. package/dist/{context-index-SSR5ECNE.js → context-index-HNG3MOME.js} +6 -6
  101. package/dist/{context-working-set-EUXAZI6N.js → context-working-set-MIEVECVZ.js} +17 -18
  102. package/dist/{dispatch-runner-B7MTOVKL.js → dispatch-runner-VVA4SRRH.js} +90 -61
  103. package/dist/{docs-FLJTIDSE.js → docs-7LQ23DLM.js} +8 -8
  104. package/dist/doctor-TWBWFK5V.js +165 -0
  105. package/dist/eval-IJ5VEZDJ.js +4483 -0
  106. package/dist/{evidence-JZNBUOQZ.js → evidence-L5APPXNV.js} +68 -61
  107. package/dist/{evolve-FJVC4KKI.js → evolve-RGNKFJ52.js} +47 -40
  108. package/dist/{extensions-IQL36S7K.js → extensions-7WYWUX5A.js} +13 -7
  109. package/dist/{fleet-BDKYJFCP.js → fleet-6CNVBZZP.js} +113 -76
  110. package/dist/{fleet-commands-ZFIWZSB3.js → fleet-commands-L2SXSYEI.js} +10 -10
  111. package/dist/{fleet-graph-Y6HPXIVF.js → fleet-graph-2J3OOIPO.js} +17 -15
  112. package/dist/{fleet-preflight-BHSNPBMH.js → fleet-preflight-CZRJ4JP5.js} +5 -6
  113. package/dist/{fleet-validate-BIYREGIK.js → fleet-validate-C5RI6DP7.js} +20 -19
  114. package/dist/{init-LQUB5COQ.js → init-VBN2ACVA.js} +70 -63
  115. package/dist/{library-NJAHIGG4.js → library-JHGUMLY2.js} +22 -20
  116. package/dist/{memory-OG6HOYKM.js → memory-K4OQIYWG.js} +49 -42
  117. package/dist/{models-5ZG5XY7J.js → models-2NCZUWDD.js} +35 -29
  118. package/dist/{monitor-TJ7AMTGB.js → monitor-MMVTJABD.js} +64 -45
  119. package/dist/{orchestrator-WZYB54DM.js → orchestrator-ZKBPCHW6.js} +1971 -520
  120. package/dist/{paths-XUC7GS6E.js → paths-DBXMZMDU.js} +5 -5
  121. package/dist/registry-LG64LTF4.js +11 -0
  122. package/dist/{reset-PXQT45IY.js → reset-DD5JGOY3.js} +11 -11
  123. package/dist/{run-FQ74YF62.js → run-QEGNX7FL.js} +89 -83
  124. package/dist/{share-FW7SVCL3.js → share-JKD3BQMW.js} +20 -18
  125. package/dist/{skills-7E7IRB3R.js → skills-LMQIKDOZ.js} +23 -21
  126. package/dist/{skills-eval-LI75W6OK.js → skills-eval-I7X2774U.js} +59 -52
  127. package/dist/{steer-GGWFUJUD.js → steer-CF5TDANS.js} +3 -3
  128. package/dist/support-I7LOJLIF.js +38 -0
  129. package/dist/{targets-4CIFKCTW.js → targets-RUSR6B5Z.js} +77 -42
  130. package/dist/{terminal-lease-WUZY7ZV5.js → terminal-lease-QYVORFR4.js} +6 -4
  131. package/dist/{trace-PNCASAXC.js → trace-ODOQIVIW.js} +61 -6
  132. package/dist/{uninstall-7FV7IP4E.js → uninstall-ZJF5H5ZN.js} +8 -8
  133. package/dist/{upgrade-K2HVIVMQ.js → upgrade-XANW3FXB.js} +29 -26
  134. package/dist/{usage-GTZELZQX.js → usage-4H7ZRXQT.js} +110 -61
  135. package/dist/{verifiers-RLAHT27O.js → verifiers-UZXNBZEB.js} +13 -13
  136. package/dist/{verify-BX3BRKH5.js → verify-BVKWTNDL.js} +9 -9
  137. package/dist/{wiki-generate-ASIFASCN.js → wiki-generate-MY7WV2QI.js} +76 -69
  138. package/dist/worker/entry.js +69 -66
  139. package/docs/alcf-provider.md +1 -1
  140. package/docs/architecture.md +1 -1
  141. package/docs/artifact-versions.md +11 -5
  142. package/docs/built-in-agents.md +1 -1
  143. package/docs/capacity-and-scheduling.md +23 -2
  144. package/docs/commands-and-modes.md +2 -2
  145. package/docs/configuration-and-targets.md +37 -5
  146. package/docs/context-engine.md +63 -4
  147. package/docs/documentation-coverage.md +3 -3
  148. package/docs/documentation-guide.md +1 -1
  149. package/docs/environment-variables.md +2 -0
  150. package/docs/eval-runner.md +262 -11
  151. package/docs/evals-internal.md +72 -2
  152. package/docs/evidence-and-memory.md +77 -12
  153. package/docs/evolution.md +1 -1
  154. package/docs/extensions-and-sharing.md +3 -1
  155. package/docs/fleet-dispatch.md +34 -9
  156. package/docs/glossary.md +21 -1
  157. package/docs/installation-and-lifecycle.md +1 -1
  158. package/docs/middleware-and-components.md +1 -1
  159. package/docs/model-catalog.md +1 -1
  160. package/docs/observability.md +54 -3
  161. package/docs/proactive-memory.md +127 -14
  162. package/docs/prompt-envelope-and-tools.md +22 -2
  163. package/docs/provider-adapter-cookbook.md +1 -1
  164. package/docs/release-cut-checklist.md +60 -41
  165. package/docs/safety-model.md +1 -1
  166. package/docs/scientific-validation.md +1 -1
  167. package/docs/skills-marketplace.md +1 -1
  168. package/docs/tool-usage.md +1 -1
  169. package/docs/trace-store.md +1 -1
  170. package/docs/troubleshooting.md +87 -0
  171. package/docs/tui-design.md +1 -1
  172. package/docs/worker-dispatch-mechanics.md +1 -1
  173. package/package.json +2 -2
  174. package/src/cli/agents.ts +1 -1
  175. package/src/cli/argv.ts +5 -0
  176. package/src/cli/config-inspect.ts +33 -6
  177. package/src/cli/config.ts +1 -1
  178. package/src/cli/configure.ts +107 -23
  179. package/src/cli/doctor-state-size.ts +82 -0
  180. package/src/cli/doctor.ts +7 -1
  181. package/src/cli/eval.ts +80 -16
  182. package/src/cli/evidence.ts +30 -25
  183. package/src/cli/extensions.ts +5 -1
  184. package/src/cli/fleet-preflight.ts +2 -12
  185. package/src/cli/fleet.ts +32 -3
  186. package/src/cli/shared.ts +1 -0
  187. package/src/cli/targets.ts +45 -11
  188. package/src/cli/trace.ts +63 -4
  189. package/src/cli/usage.ts +63 -14
  190. package/src/cli/validate-model.ts +60 -5
  191. package/src/core/bus-events.ts +54 -1
  192. package/src/core/cache-telemetry.ts +42 -0
  193. package/src/core/commit-attribution.ts +4 -4
  194. package/src/core/config.ts +18 -0
  195. package/src/core/defaults.ts +36 -6
  196. package/src/core/endpoint-key.ts +27 -0
  197. package/src/core/path-boundary.ts +100 -0
  198. package/src/core/residency-target-key.ts +25 -0
  199. package/src/core/response-schema.ts +36 -2
  200. package/src/domains/agents/extension.ts +2 -11
  201. package/src/domains/agents/fleet-contract.ts +30 -12
  202. package/src/domains/agents/recipe.ts +7 -1
  203. package/src/domains/agents/registry.ts +73 -5
  204. package/src/domains/agents/result-contract.ts +128 -17
  205. package/src/domains/agents/write-boundary.ts +15 -50
  206. package/src/domains/config/classify.ts +3 -0
  207. package/src/domains/context/codewiki/coordinator.ts +12 -4
  208. package/src/domains/context/project-rules.ts +51 -1
  209. package/src/domains/dispatch/admission.ts +40 -3
  210. package/src/domains/dispatch/assignment-reconcile.ts +22 -5
  211. package/src/domains/dispatch/assignment-store.ts +151 -14
  212. package/src/domains/dispatch/capacity-lease.ts +98 -9
  213. package/src/domains/dispatch/contract.ts +26 -1
  214. package/src/domains/dispatch/delegation-plan.ts +2 -5
  215. package/src/domains/dispatch/execution-plan.ts +44 -4
  216. package/src/domains/dispatch/execution-role.ts +9 -1
  217. package/src/domains/dispatch/extension.ts +309 -96
  218. package/src/domains/dispatch/fleet-run.ts +78 -4
  219. package/src/domains/dispatch/gate-role-prompts.ts +38 -0
  220. package/src/domains/dispatch/heartbeat.ts +32 -8
  221. package/src/domains/dispatch/index.ts +6 -1
  222. package/src/domains/dispatch/intent-requirements.ts +40 -0
  223. package/src/domains/dispatch/intent.ts +84 -8
  224. package/src/domains/dispatch/orphan-recovery.ts +5 -0
  225. package/src/domains/dispatch/path-scope.ts +370 -0
  226. package/src/domains/dispatch/receipt-integrity.ts +2 -1
  227. package/src/domains/dispatch/reservation-store.ts +116 -8
  228. package/src/domains/dispatch/state.ts +4 -0
  229. package/src/domains/dispatch/types.ts +14 -7
  230. package/src/domains/dispatch/validation.ts +6 -3
  231. package/src/domains/dispatch/worker-spawn.ts +25 -11
  232. package/src/domains/dispatch/write-boundary-enforcer.ts +62 -0
  233. package/src/domains/dispatch/write-boundary.ts +262 -22
  234. package/src/domains/eval/artifacts/store.ts +62 -0
  235. package/src/domains/eval/compare/behavioral.ts +224 -0
  236. package/src/domains/eval/compare/compare.ts +355 -2
  237. package/src/domains/eval/compare/envelope.ts +128 -0
  238. package/src/domains/eval/compare/gates.ts +24 -6
  239. package/src/domains/eval/compare/thresholds.ts +30 -3
  240. package/src/domains/eval/execution-provenance.ts +240 -0
  241. package/src/domains/eval/metrics/aggregate.ts +136 -0
  242. package/src/domains/eval/metrics/call-ledger-stream.ts +112 -0
  243. package/src/domains/eval/metrics/evidence.ts +79 -2
  244. package/src/domains/eval/metrics/tracked.ts +413 -0
  245. package/src/domains/eval/provenance.ts +117 -0
  246. package/src/domains/eval/reports/comparison.ts +128 -0
  247. package/src/domains/eval/reports/junit.ts +17 -3
  248. package/src/domains/eval/reports/markdown.ts +3 -3
  249. package/src/domains/eval/reports/text.ts +14 -0
  250. package/src/domains/eval/run-compare.ts +20 -0
  251. package/src/domains/eval/runners/clio-run.ts +139 -2
  252. package/src/domains/eval/runners/external-command.ts +28 -3
  253. package/src/domains/eval/schema/adapter.ts +111 -0
  254. package/src/domains/eval/schema/artifact.ts +20 -0
  255. package/src/domains/eval/schema/behavioral-metrics.ts +204 -0
  256. package/src/domains/eval/schema/behavioral.ts +520 -0
  257. package/src/domains/eval/schema/execution-envelope.ts +194 -0
  258. package/src/domains/eval/schema/serving.ts +74 -0
  259. package/src/domains/eval/schema/suite.ts +38 -8
  260. package/src/domains/eval/schema/validate.ts +58 -3
  261. package/src/domains/eval/schema/verdict.ts +237 -0
  262. package/src/domains/eval/suites/resolve.ts +2 -0
  263. package/src/domains/eval/suites/run.ts +264 -33
  264. package/src/domains/eval/verifiers/command.ts +2 -1
  265. package/src/domains/eval/workspaces/temp-copy.ts +145 -13
  266. package/src/domains/evidence/build.ts +68 -21
  267. package/src/domains/evidence/eval.ts +2 -12
  268. package/src/domains/evidence/findings-markdown.ts +33 -0
  269. package/src/domains/evidence/index.ts +21 -0
  270. package/src/domains/evidence/provenance.ts +46 -11
  271. package/src/domains/evidence/run-trust.ts +7 -113
  272. package/src/domains/evidence/trust-projection.ts +274 -0
  273. package/src/domains/evidence/trust-status.ts +145 -17
  274. package/src/domains/evidence/types.ts +4 -0
  275. package/src/domains/extensions/compatibility.ts +285 -0
  276. package/src/domains/extensions/discovery.ts +126 -4
  277. package/src/domains/extensions/resources.ts +21 -9
  278. package/src/domains/extensions/state.ts +18 -5
  279. package/src/domains/extensions/types.ts +6 -1
  280. package/src/domains/lifecycle/doctor.ts +209 -2
  281. package/src/domains/memory/index.ts +14 -0
  282. package/src/domains/memory/task-bank-promotion.ts +64 -0
  283. package/src/domains/memory/task-memory-policy.ts +77 -8
  284. package/src/domains/memory/task-memory-spend.ts +131 -0
  285. package/src/domains/memory/task-memory-status.ts +7 -0
  286. package/src/domains/memory/task-memory-telemetry.ts +2 -0
  287. package/src/domains/middleware/index.ts +1 -0
  288. package/src/domains/middleware/memory-intervention.ts +69 -5
  289. package/src/domains/middleware/memory-step-endpoint.ts +71 -0
  290. package/src/domains/observability/background-memory-usage.ts +140 -0
  291. package/src/domains/observability/cost.ts +1 -1
  292. package/src/domains/observability/index.ts +7 -0
  293. package/src/domains/observability/out-of-turn-usage.ts +51 -2
  294. package/src/domains/observability/trace-store.ts +192 -2
  295. package/src/domains/prompts/compiler.ts +100 -13
  296. package/src/domains/prompts/contract.ts +3 -5
  297. package/src/domains/providers/endpoint-capacity.ts +96 -0
  298. package/src/domains/providers/extension.ts +30 -2
  299. package/src/domains/providers/index.ts +10 -0
  300. package/src/domains/providers/models/local-models/clio-local-coding-targets.yaml +243 -1
  301. package/src/domains/providers/runtime-resolution.ts +8 -1
  302. package/src/domains/providers/runtimes/common/probe-helpers.ts +31 -9
  303. package/src/domains/providers/runtimes/local-native/llamacpp-anthropic.ts +1 -1
  304. package/src/domains/providers/runtimes/local-native/llamacpp-completion.ts +1 -1
  305. package/src/domains/providers/runtimes/local-native/llamacpp-embed.ts +1 -1
  306. package/src/domains/providers/runtimes/local-native/llamacpp-rerank.ts +1 -1
  307. package/src/domains/providers/runtimes/local-native/llamacpp.ts +4 -1
  308. package/src/domains/providers/runtimes/local-native/lmstudio.ts +4 -1
  309. package/src/domains/providers/runtimes/local-native/ollama-native.ts +6 -1
  310. package/src/domains/providers/types/capability-flags.ts +2 -0
  311. package/src/domains/providers/types/target-descriptor.ts +2 -0
  312. package/src/domains/resources/common-loader.ts +3 -0
  313. package/src/domains/resources/prompts/loader.ts +184 -17
  314. package/src/domains/safety/call-target.ts +52 -0
  315. package/src/domains/safety/policy-engine.ts +5 -5
  316. package/src/domains/safety/run-effects.ts +96 -2
  317. package/src/domains/safety/scope.ts +7 -12
  318. package/src/domains/session/context-accounting.ts +52 -1
  319. package/src/domains/session/context-ledger.ts +37 -13
  320. package/src/domains/session/index.ts +6 -0
  321. package/src/domains/session/prompt-cache.ts +140 -0
  322. package/src/domains/session/prompt-manifest.ts +42 -0
  323. package/src/engine/acp/adapter.ts +18 -3
  324. package/src/engine/acp/server.ts +4 -1
  325. package/src/engine/ai.ts +35 -0
  326. package/src/engine/apis/llamacpp-residency.ts +55 -3
  327. package/src/engine/apis/lmstudio.ts +25 -5
  328. package/src/engine/apis/ollama-native.ts +2 -1
  329. package/src/engine/apis/openai-completions.ts +80 -17
  330. package/src/engine/apis/residency-lock.ts +3 -1
  331. package/src/engine/apis/residency.ts +34 -1
  332. package/src/engine/prompt-templates.ts +18 -1
  333. package/src/engine/provider-payload.ts +29 -1
  334. package/src/engine/worker-runtime.ts +6 -3
  335. package/src/entry/orchestrator.ts +176 -30
  336. package/src/interactive/chat-loop-messages.ts +26 -7
  337. package/src/interactive/chat-loop.ts +318 -41
  338. package/src/interactive/chat-panel.ts +62 -8
  339. package/src/interactive/clio-editor.ts +45 -8
  340. package/src/interactive/context-activity.ts +5 -1
  341. package/src/interactive/context-meter.ts +1 -1
  342. package/src/interactive/context-overlay.ts +40 -10
  343. package/src/interactive/cost-overlay.ts +64 -6
  344. package/src/interactive/dispatch-board.ts +127 -5
  345. package/src/interactive/fleet-run-preview.ts +41 -15
  346. package/src/interactive/handoff-round.ts +41 -2
  347. package/src/interactive/interactive-application.ts +24 -1
  348. package/src/interactive/interactive-event-projection.ts +14 -0
  349. package/src/interactive/interactive-input-runtime.ts +8 -0
  350. package/src/interactive/interactive-presentation.ts +4 -0
  351. package/src/interactive/interactive-shell.ts +20 -17
  352. package/src/interactive/interactive-slash-runtime.ts +27 -4
  353. package/src/interactive/memory-overlay.ts +8 -0
  354. package/src/interactive/mutation-preview.ts +295 -0
  355. package/src/interactive/overlay-general-openers.ts +16 -0
  356. package/src/interactive/overlay-key-routing.ts +38 -0
  357. package/src/interactive/overlay-lifecycle.ts +38 -5
  358. package/src/interactive/overlay-permission-lifecycle.ts +22 -2
  359. package/src/interactive/overlay-session-lifecycle.ts +73 -9
  360. package/src/interactive/overlays/ask-user.ts +91 -19
  361. package/src/interactive/overlays/help-reference.ts +4 -0
  362. package/src/interactive/overlays/prompts.ts +11 -1
  363. package/src/interactive/overlays/settings.ts +176 -48
  364. package/src/interactive/permission-hint.ts +34 -2
  365. package/src/interactive/permission-overlay.ts +159 -9
  366. package/src/interactive/prewarm.ts +197 -0
  367. package/src/interactive/render-trace.ts +162 -15
  368. package/src/interactive/renderers/tool-execution.ts +4 -0
  369. package/src/interactive/side-question.ts +58 -1
  370. package/src/interactive/slash-commands.ts +7 -2
  371. package/src/interactive/status/controller.ts +11 -0
  372. package/src/interactive/status/state-machine.ts +54 -2
  373. package/src/interactive/status/types.ts +7 -0
  374. package/src/interactive/terminal-lease.ts +2 -0
  375. package/src/interactive/turn-context.ts +299 -31
  376. package/src/interactive/turn-persistence.ts +14 -4
  377. package/src/interactive/turn-prewarm.ts +364 -0
  378. package/src/interactive/turn-queues.ts +7 -4
  379. package/src/interactive/turn-runtime.ts +8 -1
  380. package/src/interactive/turn-state.ts +23 -0
  381. package/src/interactive/view/artifacts.ts +42 -9
  382. package/src/interactive/view/view-overlay.ts +43 -6
  383. package/src/interactive/worker-receipts.ts +14 -2
  384. package/src/interactive/worker-stream.ts +8 -0
  385. package/src/tools/ask-user.ts +43 -2
  386. package/src/tools/dispatch-admission.ts +12 -13
  387. package/src/tools/dispatch-arguments.ts +27 -0
  388. package/src/tools/dispatch-plan.ts +46 -9
  389. package/src/tools/dispatch-runner.ts +48 -13
  390. package/src/tools/dispatch-scout.ts +1 -1
  391. package/src/tools/monitor.ts +13 -0
  392. package/src/tools/registry.ts +16 -0
  393. package/src/tools/worker-evidence.ts +19 -13
  394. package/src/worker/spec-contract.ts +2 -1
  395. package/dist/chunk-AOCYTWAV.js +0 -449
  396. package/dist/chunk-HWUFFB6L.js +0 -83
  397. package/dist/chunk-R346GLFC.js +0 -31
  398. package/dist/chunk-ZGH7FGS5.js +0 -1079
  399. package/dist/doctor-RN4YKO2X.js +0 -87
  400. package/dist/eval-RUBJVSNQ.js +0 -2557
@@ -0,0 +1,370 @@
1
+ import {
2
+ asDirectoryPathBoundary,
3
+ normalizePathBoundaryEntry,
4
+ PathBoundaryError,
5
+ pathBoundaryCovers,
6
+ resolvePathBoundary,
7
+ } from "../../core/path-boundary.js";
8
+ import type { DispatchRequest } from "./contract.js";
9
+
10
+ /** Path-like tokens retained for requests that do not carry typed intent. */
11
+ const DISPATCH_PATH_TOKEN_RE = /(?:[\w.-]+\/)+[\w.-]+|\b[\w-]+\.[A-Za-z0-9]{1,8}\b/g;
12
+ const HTTP_URL_RE = /\bhttps?:\/\/\S+/gu;
13
+ const POSIX_ABSOLUTE_TOKEN_RE = /(?:^|[\s("'])((?:\/[\w.~+-]+)+)/gu;
14
+ const WINDOWS_ABSOLUTE_TOKEN_RE = /(?:^|[\s("'])([A-Za-z]:[\\/][^\s"']+)/gu;
15
+ const BACKSLASH_PATH_RE = /\b[A-Za-z_.-][\w.-]*\\(?:(?:[\w.-]+\\)+[\w.-]+|[\w-]+\.[A-Za-z0-9]{1,8})\b/gu;
16
+ const DOUBLE_SEPARATOR_PATH_RE = /\b[\w.-]+\/\/[\w./-]+/gu;
17
+
18
+ export type DispatchPathProvenanceKind = "declared" | "derived" | "inferred";
19
+ export type DispatchPathConfidence = "certain" | "high" | "medium" | "low";
20
+ export type DispatchPathProvenanceSource =
21
+ | "intent.read_roots"
22
+ | "intent.write_roots"
23
+ | "intent.relevant_paths"
24
+ | "writeRoots"
25
+ | "task"
26
+ | "briefing";
27
+ export type DispatchPathProvenanceReason =
28
+ | "explicit_intent"
29
+ | "legacy_write_roots"
30
+ | "task_path_token"
31
+ | "briefing_path_token";
32
+
33
+ export interface DispatchPathProvenanceEvidence {
34
+ provenance: DispatchPathProvenanceKind;
35
+ source: DispatchPathProvenanceSource;
36
+ confidence: DispatchPathConfidence;
37
+ reason: DispatchPathProvenanceReason;
38
+ }
39
+
40
+ export interface DispatchPathProvenanceEntry {
41
+ path: string;
42
+ evidence: DispatchPathProvenanceEvidence[];
43
+ }
44
+
45
+ export interface DispatchPathScopeProvenance {
46
+ version: 1;
47
+ mode: "declared" | "legacy-inferred";
48
+ workingContextPaths: DispatchPathProvenanceEntry[];
49
+ writeBoundaries: DispatchPathProvenanceEntry[];
50
+ }
51
+
52
+ export interface DispatchPathScope {
53
+ source: "declared" | "inferred";
54
+ workingContextPaths: ReadonlyArray<string>;
55
+ writeBoundaries: ReadonlyArray<string>;
56
+ inferredOnlyPaths: ReadonlyArray<string>;
57
+ provenance: DispatchPathScopeProvenance;
58
+ }
59
+
60
+ export interface DeclaredScopeReplacementNotice {
61
+ code: "typed_scope_replaced_inferred_paths";
62
+ level: "warning";
63
+ omittedPaths: ReadonlyArray<string>;
64
+ message: string;
65
+ }
66
+
67
+ export interface LegacyScopeInferenceNotice {
68
+ code: "legacy_scope_inferred" | "legacy_scope_empty";
69
+ level: "warning";
70
+ paths: ReadonlyArray<{
71
+ path: string;
72
+ policy: "working-context" | "write-boundary";
73
+ provenance: DispatchPathProvenanceKind;
74
+ source: DispatchPathProvenanceSource;
75
+ confidence: DispatchPathConfidence;
76
+ }>;
77
+ message: string;
78
+ }
79
+
80
+ export type DispatchPathScopeInferenceErrorCode = "legacy_scope_path_absolute" | "legacy_scope_path_malformed";
81
+
82
+ export class DispatchPathScopeInferenceError extends Error {
83
+ readonly code: DispatchPathScopeInferenceErrorCode;
84
+ readonly source: "task" | "briefing";
85
+ readonly token: string;
86
+
87
+ constructor(code: DispatchPathScopeInferenceErrorCode, source: "task" | "briefing", token: string, detail: string) {
88
+ super(`${code}: ${source} contains ${detail} '${token}'; declare intent explicitly`);
89
+ this.name = "DispatchPathScopeInferenceError";
90
+ this.code = code;
91
+ this.source = source;
92
+ this.token = token;
93
+ }
94
+ }
95
+
96
+ const DECLARED_EVIDENCE = {
97
+ readRoots: {
98
+ provenance: "declared",
99
+ source: "intent.read_roots",
100
+ confidence: "certain",
101
+ reason: "explicit_intent",
102
+ },
103
+ writeRoots: {
104
+ provenance: "declared",
105
+ source: "intent.write_roots",
106
+ confidence: "certain",
107
+ reason: "explicit_intent",
108
+ },
109
+ relevantPaths: {
110
+ provenance: "declared",
111
+ source: "intent.relevant_paths",
112
+ confidence: "certain",
113
+ reason: "explicit_intent",
114
+ },
115
+ } as const satisfies Record<"readRoots" | "writeRoots" | "relevantPaths", DispatchPathProvenanceEvidence>;
116
+
117
+ function cloneEvidence(evidence: DispatchPathProvenanceEvidence): DispatchPathProvenanceEvidence {
118
+ return { ...evidence };
119
+ }
120
+
121
+ function addProvenance(
122
+ entries: Map<string, DispatchPathProvenanceEntry>,
123
+ path: string,
124
+ evidence: DispatchPathProvenanceEvidence,
125
+ ): void {
126
+ const current = entries.get(path);
127
+ if (current === undefined) {
128
+ entries.set(path, { path, evidence: [cloneEvidence(evidence)] });
129
+ return;
130
+ }
131
+ if (!current.evidence.some((entry) => entry.source === evidence.source)) {
132
+ current.evidence.push(cloneEvidence(evidence));
133
+ }
134
+ }
135
+
136
+ function sortedEntries(entries: Map<string, DispatchPathProvenanceEntry>): DispatchPathProvenanceEntry[] {
137
+ return [...entries.values()]
138
+ .sort((left, right) => (left.path < right.path ? -1 : left.path > right.path ? 1 : 0))
139
+ .map((entry) => ({ path: entry.path, evidence: entry.evidence.map(cloneEvidence) }));
140
+ }
141
+
142
+ /** Build the required field-level provenance for normalized declared intent. */
143
+ export function declaredIntentPathProvenance(input: {
144
+ readRoots: ReadonlyArray<string>;
145
+ writeRoots: ReadonlyArray<string>;
146
+ relevantPaths: ReadonlyArray<string>;
147
+ }): DispatchPathProvenanceEntry[] {
148
+ const entries = new Map<string, DispatchPathProvenanceEntry>();
149
+ for (const path of input.readRoots) addProvenance(entries, path, DECLARED_EVIDENCE.readRoots);
150
+ for (const path of input.writeRoots) addProvenance(entries, path, DECLARED_EVIDENCE.writeRoots);
151
+ for (const path of input.relevantPaths) addProvenance(entries, path, DECLARED_EVIDENCE.relevantPaths);
152
+ return sortedEntries(entries);
153
+ }
154
+
155
+ function inferredPaths(req: Pick<DispatchRequest, "task" | "briefing">): string[] {
156
+ const paths = new Set<string>();
157
+ const text = `${req.task}\n${req.briefing ?? ""}`;
158
+ for (const match of text.matchAll(DISPATCH_PATH_TOKEN_RE)) {
159
+ const token = inferredPathToken(match[0]);
160
+ if (token.startsWith("http://") || token.startsWith("https://")) continue;
161
+ paths.add(token);
162
+ }
163
+ return [...paths];
164
+ }
165
+
166
+ function inferredPathToken(token: string): string {
167
+ return token.replace(/\.+$/u, "");
168
+ }
169
+
170
+ function failMalformedText(text: string, source: "task" | "briefing"): void {
171
+ const withoutUrls = text.replace(HTTP_URL_RE, (url) => " ".repeat(url.length));
172
+ const absolute = POSIX_ABSOLUTE_TOKEN_RE.exec(withoutUrls)?.[1];
173
+ POSIX_ABSOLUTE_TOKEN_RE.lastIndex = 0;
174
+ if (absolute !== undefined) {
175
+ throw new DispatchPathScopeInferenceError("legacy_scope_path_absolute", source, absolute, "an absolute path token");
176
+ }
177
+ const windowsAbsolute = WINDOWS_ABSOLUTE_TOKEN_RE.exec(withoutUrls)?.[1];
178
+ WINDOWS_ABSOLUTE_TOKEN_RE.lastIndex = 0;
179
+ if (windowsAbsolute !== undefined) {
180
+ throw new DispatchPathScopeInferenceError(
181
+ "legacy_scope_path_absolute",
182
+ source,
183
+ windowsAbsolute,
184
+ "an absolute path token",
185
+ );
186
+ }
187
+ for (const pattern of [BACKSLASH_PATH_RE, DOUBLE_SEPARATOR_PATH_RE]) {
188
+ const malformed = pattern.exec(withoutUrls)?.[0];
189
+ pattern.lastIndex = 0;
190
+ if (malformed !== undefined) {
191
+ throw new DispatchPathScopeInferenceError(
192
+ "legacy_scope_path_malformed",
193
+ source,
194
+ malformed,
195
+ "a malformed path token",
196
+ );
197
+ }
198
+ }
199
+ }
200
+
201
+ function strictInferredPaths(
202
+ text: string,
203
+ source: "task" | "briefing",
204
+ ): Array<{ path: string; evidence: DispatchPathProvenanceEvidence }> {
205
+ failMalformedText(text, source);
206
+ const withoutUrls = text.replace(HTTP_URL_RE, (url) => " ".repeat(url.length));
207
+ const evidence: DispatchPathProvenanceEvidence =
208
+ source === "task"
209
+ ? { provenance: "inferred", source, confidence: "medium", reason: "task_path_token" }
210
+ : { provenance: "inferred", source, confidence: "low", reason: "briefing_path_token" };
211
+ const result: Array<{ path: string; evidence: DispatchPathProvenanceEvidence }> = [];
212
+ for (const match of withoutUrls.matchAll(DISPATCH_PATH_TOKEN_RE)) {
213
+ const token = inferredPathToken(match[0]);
214
+ try {
215
+ result.push({ path: normalizePathBoundaryEntry(token), evidence });
216
+ } catch (error) {
217
+ const code =
218
+ error instanceof PathBoundaryError && error.code === "absolute"
219
+ ? "legacy_scope_path_absolute"
220
+ : "legacy_scope_path_malformed";
221
+ throw new DispatchPathScopeInferenceError(
222
+ code,
223
+ source,
224
+ token,
225
+ code === "legacy_scope_path_absolute" ? "an absolute path token" : "a malformed path token",
226
+ );
227
+ }
228
+ }
229
+ return result;
230
+ }
231
+
232
+ function legacyPathScope(req: DispatchRequest, cwd: string): DispatchPathScope {
233
+ const workingEntries = new Map<string, DispatchPathProvenanceEntry>();
234
+ const writeEntries = new Map<string, DispatchPathProvenanceEntry>();
235
+ const writeEvidence: DispatchPathProvenanceEvidence = {
236
+ provenance: "derived",
237
+ source: "writeRoots",
238
+ confidence: "high",
239
+ reason: "legacy_write_roots",
240
+ };
241
+ for (const root of req.writeRoots ?? []) {
242
+ addProvenance(workingEntries, root, writeEvidence);
243
+ addProvenance(writeEntries, asDirectoryPathBoundary(resolvePathBoundary(cwd, root)), writeEvidence);
244
+ }
245
+ for (const candidate of strictInferredPaths(req.task, "task")) {
246
+ addProvenance(workingEntries, candidate.path, candidate.evidence);
247
+ }
248
+ if (req.briefing !== undefined) {
249
+ for (const candidate of strictInferredPaths(req.briefing, "briefing")) {
250
+ addProvenance(workingEntries, candidate.path, candidate.evidence);
251
+ }
252
+ }
253
+ const workingContext = [...workingEntries.values()].map((entry) => ({
254
+ path: entry.path,
255
+ evidence: entry.evidence.map(cloneEvidence),
256
+ }));
257
+ const writeBoundaries = [...writeEntries.values()].map((entry) => ({
258
+ path: entry.path,
259
+ evidence: entry.evidence.map(cloneEvidence),
260
+ }));
261
+ return {
262
+ source: "inferred",
263
+ workingContextPaths: workingContext.map((entry) => entry.path),
264
+ writeBoundaries: writeBoundaries.map((entry) => entry.path),
265
+ inferredOnlyPaths: [],
266
+ provenance: {
267
+ version: 1,
268
+ mode: "legacy-inferred",
269
+ workingContextPaths: workingContext,
270
+ writeBoundaries,
271
+ },
272
+ };
273
+ }
274
+
275
+ /** Resolve every policy-bearing path projection for one dispatch request. */
276
+ export function resolveDispatchPathScope(req: DispatchRequest): DispatchPathScope {
277
+ const cwd = req.cwd ?? process.cwd();
278
+ if (req.intent === undefined) return legacyPathScope(req, cwd);
279
+
280
+ const declaredProvenance = declaredIntentPathProvenance(req.intent);
281
+ const declaredPaths = declaredProvenance.map((entry) => entry.path);
282
+ const inferredOnlyPaths = inferredPaths(req).filter((candidate) => !pathBoundaryCovers(declaredPaths, candidate));
283
+ const writeEvidence = DECLARED_EVIDENCE.writeRoots;
284
+ const writeBoundaries = req.intent.writeRoots.map((root) => ({
285
+ path: resolvePathBoundary(cwd, root),
286
+ evidence: [cloneEvidence(writeEvidence)],
287
+ }));
288
+ return {
289
+ source: "declared",
290
+ workingContextPaths: declaredPaths,
291
+ writeBoundaries: writeBoundaries.map((entry) => entry.path),
292
+ inferredOnlyPaths,
293
+ provenance: {
294
+ version: 1,
295
+ mode: "declared",
296
+ workingContextPaths: declaredProvenance,
297
+ writeBoundaries,
298
+ },
299
+ };
300
+ }
301
+
302
+ /** Render the named diagnostic for the declared-scope replacement tradeoff. */
303
+ export function declaredScopeReplacementDiagnostic(scope: DispatchPathScope): string | null {
304
+ if (scope.source !== "declared" || scope.inferredOnlyPaths.length === 0) return null;
305
+ return `typed_scope_replaced_inferred_paths: typed intent omitted prose-inferred paths ${scope.inferredOnlyPaths.join(
306
+ ", ",
307
+ )}; those paths did not select project rules or expand worker authority`;
308
+ }
309
+
310
+ /** Render the interactive warning for the declared-scope replacement tradeoff. */
311
+ export function declaredScopeReplacementNotice(scope: DispatchPathScope): DeclaredScopeReplacementNotice | null {
312
+ if (scope.source !== "declared" || scope.inferredOnlyPaths.length === 0) return null;
313
+ const omittedPaths = [...scope.inferredOnlyPaths];
314
+ return {
315
+ code: "typed_scope_replaced_inferred_paths",
316
+ level: "warning",
317
+ omittedPaths,
318
+ message: `[dispatch scope] typed intent replaced prose path inference; omitted paths: ${omittedPaths.join(
319
+ ", ",
320
+ )}. Those paths did not select project rules or expand worker authority.`,
321
+ };
322
+ }
323
+
324
+ function legacyNoticePaths(scope: DispatchPathScope): LegacyScopeInferenceNotice["paths"] {
325
+ const paths: Array<LegacyScopeInferenceNotice["paths"][number]> = [];
326
+ for (const [policy, entries] of [
327
+ ["working-context", scope.provenance.workingContextPaths],
328
+ ["write-boundary", scope.provenance.writeBoundaries],
329
+ ] as const) {
330
+ for (const entry of entries) {
331
+ for (const evidence of entry.evidence) {
332
+ paths.push({
333
+ path: entry.path,
334
+ policy,
335
+ provenance: evidence.provenance,
336
+ source: evidence.source,
337
+ confidence: evidence.confidence,
338
+ });
339
+ }
340
+ }
341
+ }
342
+ return paths;
343
+ }
344
+
345
+ /** Render the operator-visible warning for a legacy scope resolution. */
346
+ export function legacyScopeInferenceNotice(scope: DispatchPathScope): LegacyScopeInferenceNotice | null {
347
+ if (scope.source !== "inferred") return null;
348
+ const paths = legacyNoticePaths(scope);
349
+ if (paths.length === 0) {
350
+ return {
351
+ code: "legacy_scope_empty",
352
+ level: "warning",
353
+ paths,
354
+ message:
355
+ "[dispatch scope] legacy dispatch has no declared intent and inferred no policy-bearing paths. Path-scoped rules received no inferred match input, and worker authority did not expand.",
356
+ };
357
+ }
358
+ const rendered = paths
359
+ .map(
360
+ (entry) =>
361
+ `${entry.policy} ${entry.path} (provenance=${entry.provenance} source=${entry.source} confidence=${entry.confidence})`,
362
+ )
363
+ .join("; ");
364
+ return {
365
+ code: "legacy_scope_inferred",
366
+ level: "warning",
367
+ paths,
368
+ message: `[dispatch scope] legacy dispatch resolved policy-bearing scope without declared intent: ${rendered}. Review this scope before execution.`,
369
+ };
370
+ }
@@ -10,7 +10,7 @@ import type { RunEnvelope, RunReceipt, RunReceiptDraft, RunReceiptIntegrity, Run
10
10
  * base, so there are no historical receipts to keep verifying: a receipt is
11
11
  * either this version or it is not a receipt.
12
12
  */
13
- export const RUN_RECEIPT_INTEGRITY_VERSION: RunReceiptIntegrity["version"] = 19;
13
+ export const RUN_RECEIPT_INTEGRITY_VERSION: RunReceiptIntegrity["version"] = 20;
14
14
  export type ReceiptIntegrityVersion = RunReceiptIntegrity["version"];
15
15
  export type ReceiptIntegrityField = keyof RunReceiptDraft;
16
16
  export const RUN_RECEIPT_INTEGRITY_ALGORITHM = "sha256";
@@ -77,6 +77,7 @@ export const RECEIPT_INTEGRITY_FIELD_COVERAGE = {
77
77
  requestOrigin: true,
78
78
  task: true,
79
79
  intent: true,
80
+ pathScope: true,
80
81
  // Historical receipts omit this field, so their receipt and ledger payloads remain unchanged.
81
82
  budget: true,
82
83
  targetId: true,
@@ -1,5 +1,6 @@
1
1
  import { randomBytes } from "node:crypto";
2
2
  import { withStateFileLockSync } from "../../core/state-file-lock.js";
3
+ import { endpointLabel } from "../providers/endpoint-capacity.js";
3
4
  import {
4
5
  acquireCapacityLease,
5
6
  activeCapacityDrainUnsafe,
@@ -7,6 +8,7 @@ import {
7
8
  type CapacityLimits,
8
9
  type CapacityStateFile,
9
10
  capacityStateLockPath,
11
+ describeEndpointCapacityHolders,
10
12
  readCapacityStateUnsafe,
11
13
  writeCapacityStateUnsafe,
12
14
  } from "./capacity-lease.js";
@@ -19,9 +21,10 @@ export type ReservationMemberStatus = "held" | "consumed" | "released";
19
21
  export type ReservationStatus = "active" | "released" | "rolled_back" | "expired";
20
22
 
21
23
  /**
22
- * A reservation holds three scarce things and nothing else: a global
23
- * concurrency slot, a per-node slot, and a budget upper bound. Node identity
24
- * matters only because slots are per-node. Agent, target, model, and runtime
24
+ * A reservation holds four scarce things and nothing else: a global
25
+ * concurrency slot, a per-node slot, an inference-endpoint slot, and a budget
26
+ * upper bound. Node and endpoint identity matter only because slots bind on
27
+ * those dimensions. Agent, target, model, and runtime
25
28
  * identity are route identity, which the failover envelope owns
26
29
  * (`assertRouteWithinApprovedEnvelope`); pinning them here made a
27
30
  * plan-approved dispatch unable to fail over at all.
@@ -30,6 +33,7 @@ export interface ReservationPlanTask {
30
33
  memberId: string;
31
34
  wave: number;
32
35
  nodeId: string;
36
+ endpointKey?: string;
33
37
  costUpperBoundUsd: number;
34
38
  }
35
39
 
@@ -54,17 +58,31 @@ export interface DispatchReservationRecord {
54
58
  export interface ReservationCapacitySnapshot {
55
59
  global: { active: number; limit: number };
56
60
  nodes: Readonly<Record<string, { active: number; limit: number }>>;
61
+ endpoints: Readonly<Record<string, { active: number; limit: number; leases?: number; foregroundStreams?: number }>>;
57
62
  budget: { currentUsd: number; ceilingUsd: number };
58
63
  }
59
64
 
60
65
  export interface ReservationAllocation {
61
66
  globalSlots: number;
62
67
  nodeSlots: Readonly<Record<string, number>>;
68
+ endpointSlots: Readonly<Record<string, number>>;
63
69
  budgetUsd: number;
64
70
  }
65
71
 
66
72
  export type ReservationPlanResult = { ok: true; allocation: ReservationAllocation } | { ok: false; reason: string };
67
73
 
74
+ function endpointCapacityDenial(input: {
75
+ endpointKey: string;
76
+ limit: number;
77
+ used: number;
78
+ requested: number;
79
+ leases: number;
80
+ reservations: number;
81
+ foregroundStreams: number;
82
+ }): string {
83
+ return `dispatch: admission denied: endpoint '${endpointLabel(input.endpointKey)}' capacity exceeded (${input.used + input.requested}/${input.limit} slots): ${describeEndpointCapacityHolders(input)}; reduce the same-wave worker count, collect in-flight runs, or point workers at a second server`;
84
+ }
85
+
68
86
  function copyRecord(record: DispatchReservationRecord): DispatchReservationRecord {
69
87
  return { ...record, members: record.members.map((member) => ({ ...member })) };
70
88
  }
@@ -96,6 +114,7 @@ function validRecord(value: unknown): value is DispatchReservationRecord {
96
114
  typeof member.memberId === "string" &&
97
115
  Number.isInteger(member.wave) &&
98
116
  typeof member.nodeId === "string" &&
117
+ (member.endpointKey === undefined || typeof member.endpointKey === "string") &&
99
118
  typeof member.costUpperBoundUsd === "number" &&
100
119
  Number.isFinite(member.costUpperBoundUsd) &&
101
120
  member.costUpperBoundUsd >= 0 &&
@@ -145,9 +164,13 @@ function reservationAllocation(tasks: ReadonlyArray<ReservationPlanTask>): Reser
145
164
  ),
146
165
  );
147
166
  const nodeSlots = peakBy(tasks.map((task) => ({ wave: task.wave, key: task.nodeId })));
167
+ const endpointSlots = peakBy(
168
+ tasks.flatMap((task) => (task.endpointKey === undefined ? [] : [{ wave: task.wave, key: task.endpointKey }])),
169
+ );
148
170
  return {
149
171
  globalSlots,
150
172
  nodeSlots,
173
+ endpointSlots,
151
174
  budgetUsd: tasks.reduce((sum, task) => sum + task.costUpperBoundUsd, 0),
152
175
  };
153
176
  }
@@ -191,6 +214,26 @@ function allocateReservation(
191
214
  return { ok: false, reason: `node '${nodeId}' capacity exceeded (${used + slots}/${node.limit})` };
192
215
  }
193
216
  }
217
+ for (const [endpointKey, slots] of Object.entries(requested.endpointSlots)) {
218
+ const endpoint = capacity.endpoints[endpointKey];
219
+ if (!endpoint || !Number.isFinite(endpoint.limit)) continue;
220
+ const heldSlots = held.reduce((sum, allocation) => sum + (allocation.endpointSlots[endpointKey] ?? 0), 0);
221
+ const used = endpoint.active + heldSlots;
222
+ if (used + slots > endpoint.limit) {
223
+ return {
224
+ ok: false,
225
+ reason: endpointCapacityDenial({
226
+ endpointKey,
227
+ limit: endpoint.limit,
228
+ used,
229
+ requested: slots,
230
+ leases: endpoint.leases ?? endpoint.active - (endpoint.foregroundStreams ?? 0),
231
+ reservations: heldSlots,
232
+ foregroundStreams: endpoint.foregroundStreams ?? 0,
233
+ }),
234
+ };
235
+ }
236
+ }
194
237
  const existingBudget = activeRecords.reduce((sum, record) => sum + outstandingBudget(record), 0);
195
238
  const projected = capacity.budget.currentUsd + existingBudget + requested.budgetUsd;
196
239
  if (projected >= capacity.budget.ceilingUsd) {
@@ -225,7 +268,9 @@ export function createDispatchReservation(input: {
225
268
  if (!planned.ok) {
226
269
  replaceReservations(state, records);
227
270
  writeCapacityStateUnsafe(state);
228
- throw new Error(`dispatch: reservation denied: ${planned.reason}`);
271
+ throw new Error(
272
+ planned.reason.startsWith("dispatch:") ? planned.reason : `dispatch: reservation denied: ${planned.reason}`,
273
+ );
229
274
  }
230
275
  const createdAt = new Date(nowMs).toISOString();
231
276
  const record: DispatchReservationRecord = {
@@ -250,6 +295,7 @@ export function transferDispatchReservationToLease(input: {
250
295
  memberId: string;
251
296
  assignmentId: string;
252
297
  nodeId: string;
298
+ endpointKey?: string;
253
299
  limits: CapacityLimits;
254
300
  nowMs?: number;
255
301
  ttlMs?: number;
@@ -258,6 +304,7 @@ export function transferDispatchReservationToLease(input: {
258
304
  return acquireCapacityLease({
259
305
  assignmentId: input.assignmentId,
260
306
  nodeId: input.nodeId,
307
+ ...(input.endpointKey !== undefined ? { endpointKey: input.endpointKey } : {}),
261
308
  limits: input.limits,
262
309
  nowMs,
263
310
  ...(input.ttlMs !== undefined ? { ttlMs: input.ttlMs } : {}),
@@ -290,6 +337,7 @@ export function rebindDispatchReservationMember(input: {
290
337
  ownerId: string;
291
338
  memberId: string;
292
339
  nodeId: string;
340
+ endpointKey?: string;
293
341
  costUpperBoundUsd: number;
294
342
  capacity: ReservationCapacitySnapshot;
295
343
  nowMs?: number;
@@ -304,11 +352,42 @@ export function rebindDispatchReservationMember(input: {
304
352
  if (member.status === "released") {
305
353
  throw new Error(`dispatch: reservation member '${input.memberId}' was already released`);
306
354
  }
307
- if (member.nodeId === input.nodeId && member.costUpperBoundUsd === input.costUpperBoundUsd) {
355
+ if (
356
+ member.nodeId === input.nodeId &&
357
+ member.endpointKey === input.endpointKey &&
358
+ member.costUpperBoundUsd === input.costUpperBoundUsd
359
+ ) {
308
360
  return copyRecord(record);
309
361
  }
310
-
311
362
  const others = records.filter((entry) => entry.status === "active" && entry.ownerId !== input.ownerId);
363
+ if (member.endpointKey !== input.endpointKey && input.endpointKey !== undefined) {
364
+ const endpointKey = input.endpointKey;
365
+ const endpoint = input.capacity.endpoints[endpointKey];
366
+ if (endpoint !== undefined && Number.isFinite(endpoint.limit)) {
367
+ const siblingSlots = record.members.filter(
368
+ (entry) => entry !== member && entry.status !== "released" && entry.endpointKey === endpointKey,
369
+ ).length;
370
+ const otherHeld = others.reduce(
371
+ (sum, entry) => sum + (reservationAllocation(outstandingTasks(entry)).endpointSlots[endpointKey] ?? 0),
372
+ 0,
373
+ );
374
+ const used = endpoint.active + otherHeld + siblingSlots;
375
+ if (used + 1 > endpoint.limit) {
376
+ throw new Error(
377
+ endpointCapacityDenial({
378
+ endpointKey,
379
+ limit: endpoint.limit,
380
+ used,
381
+ requested: 1,
382
+ leases: endpoint.leases ?? endpoint.active - (endpoint.foregroundStreams ?? 0),
383
+ reservations: otherHeld + siblingSlots,
384
+ foregroundStreams: endpoint.foregroundStreams ?? 0,
385
+ }),
386
+ );
387
+ }
388
+ }
389
+ }
390
+
312
391
  if (member.nodeId !== input.nodeId) {
313
392
  const node = input.capacity.nodes[input.nodeId];
314
393
  if (node !== undefined && Number.isFinite(node.limit)) {
@@ -342,6 +421,8 @@ export function rebindDispatchReservationMember(input: {
342
421
  }
343
422
 
344
423
  member.nodeId = input.nodeId;
424
+ if (input.endpointKey === undefined) delete member.endpointKey;
425
+ else member.endpointKey = input.endpointKey;
345
426
  member.costUpperBoundUsd = Math.max(0, input.costUpperBoundUsd);
346
427
  writeStore(records);
347
428
  return copyRecord(record);
@@ -370,6 +451,26 @@ export function releaseDispatchReservationMember(
370
451
  });
371
452
  }
372
453
 
454
+ /** Settle every member after the scheduler has finished the reserved plan. */
455
+ export function releaseDispatchReservation(ownerId: string, nowMs = Date.now()): DispatchReservationRecord | null {
456
+ return withStateFileLockSync(capacityStateLockPath(), () => {
457
+ const records = readStore();
458
+ const record = records.find((entry) => entry.ownerId === ownerId);
459
+ if (!record) return null;
460
+ if (record.status !== "active") return copyRecord(record);
461
+ const settledAt = new Date(nowMs).toISOString();
462
+ for (const member of record.members) {
463
+ if (member.status === "released") continue;
464
+ member.status = "released";
465
+ member.releasedAt = settledAt;
466
+ }
467
+ record.status = "released";
468
+ record.settledAt = settledAt;
469
+ writeStore(records);
470
+ return copyRecord(record);
471
+ });
472
+ }
473
+
373
474
  export function rollbackDispatchReservation(ownerId: string, nowMs = Date.now()): DispatchReservationRecord | null {
374
475
  return withStateFileLockSync(capacityStateLockPath(), () => {
375
476
  const records = readStore();
@@ -488,14 +589,21 @@ export function reservedBudgetUsd(): number {
488
589
  .reduce((sum, record) => sum + outstandingBudget(record), 0);
489
590
  }
490
591
 
491
- export function reservedCapacity(): { globalSlots: number; nodeSlots: Readonly<Record<string, number>> } {
592
+ export function reservedCapacity(): {
593
+ globalSlots: number;
594
+ nodeSlots: Readonly<Record<string, number>>;
595
+ endpointSlots: Readonly<Record<string, number>>;
596
+ } {
492
597
  let globalSlots = 0;
493
598
  const nodeSlots: Record<string, number> = {};
599
+ const endpointSlots: Record<string, number> = {};
494
600
  for (const record of readStore().filter((entry) => entry.status === "active")) {
495
601
  const allocation = reservationAllocation(outstandingTasks(record));
496
602
  globalSlots += allocation.globalSlots;
497
603
  for (const [nodeId, count] of Object.entries(allocation.nodeSlots))
498
604
  nodeSlots[nodeId] = (nodeSlots[nodeId] ?? 0) + count;
605
+ for (const [endpointKey, count] of Object.entries(allocation.endpointSlots))
606
+ endpointSlots[endpointKey] = (endpointSlots[endpointKey] ?? 0) + count;
499
607
  }
500
- return { globalSlots, nodeSlots };
608
+ return { globalSlots, nodeSlots, endpointSlots };
501
609
  }
@@ -107,6 +107,10 @@ export async function writeFleetRun(record: FleetRunRecord): Promise<void> {
107
107
  await atomicWrite(fleetRunPath(record.id), `${JSON.stringify(record, null, 2)}\n`);
108
108
  }
109
109
 
110
+ // `RunEnvelope.heartbeatAt` deliberately persists only the absolute wall-clock
111
+ // anchor used for operator display and crash recovery. The monotonic heartbeat
112
+ // stamp belongs to the live transport in this process; serializing it would
113
+ // give a restarted process a number from an origin it cannot share.
110
114
  function readRuns(): RunEnvelope[] {
111
115
  const path = runsPath();
112
116
  if (!existsSync(path)) return [];