@iowarp/clio-coder 0.3.8 → 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 (333) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +7 -3
  3. package/dist/{acp-U67UHUK2.js → acp-7LOELQFP.js} +6 -6
  4. package/dist/{agents-YU6SGALZ.js → agents-FIBG2SHA.js} +27 -25
  5. package/dist/assets/codewiki.json +1 -1
  6. package/dist/{auth-5ZPJOIVG.js → auth-OI4LIH2I.js} +11 -12
  7. package/dist/{builtins-C6JMZVV6.js → builtins-AD25UL3C.js} +5 -5
  8. package/dist/{chunk-5FR74PWO.js → chunk-2JDWVJND.js} +2 -2
  9. package/dist/chunk-3DPEIQKN.js +113 -0
  10. package/dist/{chunk-4SPRNWDE.js → chunk-3DUR4WUA.js} +15 -15
  11. package/dist/{chunk-VHN4MY6O.js → chunk-3MRC2YSQ.js} +2 -2
  12. package/dist/{chunk-5DHKRSMQ.js → chunk-3UUY7R3Z.js} +11 -7
  13. package/dist/{chunk-IGWKHNIQ.js → chunk-3V5AYSEQ.js} +8 -8
  14. package/dist/{chunk-FHJEP5SW.js → chunk-465CC7FK.js} +8 -5
  15. package/dist/{chunk-5WIGXA4T.js → chunk-47CMYGET.js} +111 -4
  16. package/dist/{chunk-A3WNZD3P.js → chunk-4H6ULJ3H.js} +67 -21
  17. package/dist/{chunk-TB5666IT.js → chunk-4LJX2PUC.js} +3 -3
  18. package/dist/{chunk-XWSF374K.js → chunk-56KB5IJP.js} +2 -2
  19. package/dist/{chunk-SPULKLCF.js → chunk-5DQRIYDZ.js} +2 -2
  20. package/dist/{chunk-2HEJ2F35.js → chunk-5HFBWUMU.js} +20 -8
  21. package/dist/{chunk-WNIJTQQK.js → chunk-5PVQ4SRS.js} +78 -6
  22. package/dist/{chunk-DYIM5TJT.js → chunk-5QKCQQ3E.js} +262 -6
  23. package/dist/{chunk-TYPGUK6W.js → chunk-5T7RBWN2.js} +111 -5
  24. package/dist/{chunk-IIZWH4XA.js → chunk-774ILSRL.js} +2 -2
  25. package/dist/chunk-7C6RYZGQ.js +391 -0
  26. package/dist/{chunk-TANS5ZJS.js → chunk-AD7Y7STJ.js} +3 -3
  27. package/dist/{chunk-RWSI4YD7.js → chunk-AEYBF3TB.js} +33 -12
  28. package/dist/{chunk-DGSYXYMX.js → chunk-AMKHQW3C.js} +2 -2
  29. package/dist/{chunk-VWZOAB7K.js → chunk-B5XRQOLB.js} +7 -7
  30. package/dist/{chunk-WXY7KU3G.js → chunk-BVDVID7E.js} +2 -2
  31. package/dist/{chunk-PMDBGQSJ.js → chunk-CA42X6KT.js} +2 -2
  32. package/dist/{chunk-HLE42MG7.js → chunk-D73KXYPF.js} +3 -3
  33. package/dist/{chunk-5Q2VVUKB.js → chunk-DG4M6ZUE.js} +3 -3
  34. package/dist/{chunk-ME6CCNFO.js → chunk-EBOC7MT3.js} +6 -6
  35. package/dist/{chunk-MXKJU4JB.js → chunk-ECUO3KDP.js} +48 -7
  36. package/dist/{chunk-26LEYJZH.js → chunk-FALJGAWU.js} +2 -2
  37. package/dist/{chunk-GWS3VEIW.js → chunk-FWDFM5ZU.js} +24 -3
  38. package/dist/{chunk-7RGZWPB6.js → chunk-GAYUJ7LE.js} +67 -13
  39. package/dist/{chunk-VCBR6CU7.js → chunk-HAY4ZE2P.js} +2 -2
  40. package/dist/{chunk-FBVTI2TJ.js → chunk-HCBCAYZU.js} +11 -130
  41. package/dist/{chunk-WSB3FPX7.js → chunk-HJB5IUKP.js} +32 -136
  42. package/dist/{chunk-J3YUBZWY.js → chunk-HKO36JWF.js} +33 -5
  43. package/dist/{chunk-E77JEWSD.js → chunk-HPCTNZM2.js} +6 -36
  44. package/dist/{chunk-FJ3H4MN5.js → chunk-IHKBWSXF.js} +2 -2
  45. package/dist/{chunk-NMPKI6XL.js → chunk-JEQQR47K.js} +37 -18
  46. package/dist/{chunk-3BINW3FP.js → chunk-KV2AOLDF.js} +24 -4
  47. package/dist/{chunk-YS5VLNH5.js → chunk-LXPJXFM5.js} +7 -7
  48. package/dist/{chunk-7RFXX52T.js → chunk-MIX5N5AC.js} +271 -41
  49. package/dist/{chunk-K4XHGFR5.js → chunk-MLOK6ZOS.js} +1297 -218
  50. package/dist/{chunk-ZNLWCMVZ.js → chunk-MV2VUEJC.js} +2 -2
  51. package/dist/{chunk-MVVUPGPW.js → chunk-MXHC5QYU.js} +6 -6
  52. package/dist/{chunk-5H3GB5BO.js → chunk-N3PBVRTZ.js} +4 -382
  53. package/dist/{chunk-2HFZQUHL.js → chunk-N5XKWMDW.js} +17 -7
  54. package/dist/{chunk-5C3AQNDW.js → chunk-NNNWO6F2.js} +124 -36
  55. package/dist/{chunk-GU2UIAFZ.js → chunk-NQ6UCCOD.js} +3 -3
  56. package/dist/{chunk-N22QMJKY.js → chunk-NZU6YDNV.js} +4 -4
  57. package/dist/{chunk-ZVJ5BLO2.js → chunk-O6I4CIEU.js} +151 -13
  58. package/dist/{chunk-XN3L4EYL.js → chunk-OEDBCISO.js} +2 -2
  59. package/dist/{chunk-VAWNZU7Z.js → chunk-P3JGPQFL.js} +2 -2
  60. package/dist/{chunk-IJ7RPIYJ.js → chunk-PNY46YEY.js} +20 -3
  61. package/dist/{chunk-U6MBIEMB.js → chunk-PZ4I4JE2.js} +56 -35
  62. package/dist/{chunk-GPIEI3LY.js → chunk-QQ7EKM72.js} +2 -2
  63. package/dist/{chunk-WLFILSD5.js → chunk-R7LNVMCS.js} +66 -28
  64. package/dist/{chunk-GOXNB3AO.js → chunk-RAPCMZL4.js} +75 -4
  65. package/dist/chunk-RKKLTLYB.js +45 -0
  66. package/dist/{chunk-TT36MB5S.js → chunk-RKRLDWD3.js} +3 -1
  67. package/dist/{chunk-TTHACPOM.js → chunk-S4COXYBG.js} +456 -18
  68. package/dist/{chunk-WWCZ5F23.js → chunk-T3Z6VAAF.js} +69 -10
  69. package/dist/{chunk-GN57SG4G.js → chunk-TD7UE2L5.js} +9 -7
  70. package/dist/{chunk-TLQJPP24.js → chunk-TEO2TLVN.js} +523 -322
  71. package/dist/{chunk-FCSXB6T2.js → chunk-UOSL25KY.js} +14 -2
  72. package/dist/{chunk-KTYTFRMB.js → chunk-VKBMFOYV.js} +17 -15
  73. package/dist/{chunk-PT7HYKEM.js → chunk-VO2LKSTM.js} +2 -2
  74. package/dist/{chunk-P43ETTHK.js → chunk-VPTUJU4P.js} +2 -2
  75. package/dist/{chunk-WEH5XRJQ.js → chunk-WIE7ZOSW.js} +2 -2
  76. package/dist/{chunk-EMYUUSFG.js → chunk-WXCJ7VME.js} +5 -5
  77. package/dist/{chunk-4DGYLA73.js → chunk-XDOQXGFO.js} +22 -7
  78. package/dist/{chunk-JOZYP4GM.js → chunk-YKOFT37S.js} +5 -5
  79. package/dist/chunk-YSEHGPCT.js +127 -0
  80. package/dist/{chunk-HFSBBKSQ.js → chunk-YW7UVM5V.js} +138 -3
  81. package/dist/cli/index.js +27 -27
  82. package/dist/{clio-QVTYJ57A.js → clio-LT5V7SSZ.js} +6 -6
  83. package/dist/{code-nav-FGGFIE7L.js → code-nav-LMW275PA.js} +4 -4
  84. package/dist/{config-LW5IJFQN.js → config-RXS5T3JT.js} +71 -43
  85. package/dist/{configure-7XIZCOU4.js → configure-2WYWSCSD.js} +14 -15
  86. package/dist/{context-Y6Y7QPR6.js → context-I3BTOTCS.js} +12 -12
  87. package/dist/{context-L3WL3X7K.js → context-MVOORGMF.js} +34 -33
  88. package/dist/{context-N52ZA626.js → context-PALKKQYL.js} +20 -20
  89. package/dist/{context-clear-MBQRLSDQ.js → context-clear-N2WOYZ2K.js} +34 -33
  90. package/dist/{context-index-HVMFQHK3.js → context-index-HNG3MOME.js} +2 -2
  91. package/dist/{context-working-set-GS6DSO7F.js → context-working-set-MIEVECVZ.js} +10 -11
  92. package/dist/{dispatch-runner-22ZCNOM3.js → dispatch-runner-VVA4SRRH.js} +34 -33
  93. package/dist/doctor-TWBWFK5V.js +165 -0
  94. package/dist/{eval-BEC2WHDA.js → eval-IJ5VEZDJ.js} +2016 -142
  95. package/dist/{evidence-REJUMSKM.js → evidence-L5APPXNV.js} +29 -28
  96. package/dist/{evolve-PY5ZBA5K.js → evolve-RGNKFJ52.js} +29 -28
  97. package/dist/{extensions-HVKU65YU.js → extensions-7WYWUX5A.js} +9 -3
  98. package/dist/{fleet-7WZEWRFA.js → fleet-6CNVBZZP.js} +87 -55
  99. package/dist/{fleet-commands-UVHWM76J.js → fleet-commands-L2SXSYEI.js} +6 -6
  100. package/dist/{fleet-graph-6ULH7PES.js → fleet-graph-2J3OOIPO.js} +14 -12
  101. package/dist/{fleet-preflight-J53T6CCE.js → fleet-preflight-CZRJ4JP5.js} +3 -4
  102. package/dist/{fleet-validate-72PC4SLA.js → fleet-validate-C5RI6DP7.js} +16 -15
  103. package/dist/{init-OG3TPGQG.js → init-VBN2ACVA.js} +50 -48
  104. package/dist/{library-CNTMPLRF.js → library-JHGUMLY2.js} +13 -11
  105. package/dist/{memory-6IS7F275.js → memory-K4OQIYWG.js} +31 -30
  106. package/dist/{models-ENRJDA5W.js → models-2NCZUWDD.js} +23 -22
  107. package/dist/{monitor-XLDVO7TN.js → monitor-MMVTJABD.js} +35 -34
  108. package/dist/{orchestrator-6KSPYRHA.js → orchestrator-ZKBPCHW6.js} +1627 -294
  109. package/dist/{reset-RZ4ER727.js → reset-DD5JGOY3.js} +3 -3
  110. package/dist/{run-Y2CNK5RU.js → run-QEGNX7FL.js} +56 -55
  111. package/dist/{share-A55GYP6Z.js → share-JKD3BQMW.js} +13 -11
  112. package/dist/{skills-ALC5J6AT.js → skills-LMQIKDOZ.js} +14 -12
  113. package/dist/{skills-eval-JPBEBYQU.js → skills-eval-I7X2774U.js} +34 -32
  114. package/dist/{steer-GGWFUJUD.js → steer-CF5TDANS.js} +3 -3
  115. package/dist/{support-MIETYA5E.js → support-I7LOJLIF.js} +4 -4
  116. package/dist/{targets-VGNXIR3S.js → targets-RUSR6B5Z.js} +58 -32
  117. package/dist/{terminal-lease-WOBR64YA.js → terminal-lease-QYVORFR4.js} +6 -4
  118. package/dist/{trace-PNCASAXC.js → trace-ODOQIVIW.js} +61 -6
  119. package/dist/{upgrade-FUSUAGHR.js → upgrade-XANW3FXB.js} +17 -16
  120. package/dist/{usage-N4MKVHKD.js → usage-4H7ZRXQT.js} +88 -44
  121. package/dist/{verifiers-YAWOJ3H2.js → verifiers-UZXNBZEB.js} +6 -6
  122. package/dist/{verify-LTDHYBGY.js → verify-BVKWTNDL.js} +5 -5
  123. package/dist/{wiki-generate-6M7GHTBJ.js → wiki-generate-MY7WV2QI.js} +49 -47
  124. package/dist/worker/entry.js +29 -33
  125. package/docs/alcf-provider.md +1 -1
  126. package/docs/architecture.md +1 -1
  127. package/docs/artifact-versions.md +6 -1
  128. package/docs/built-in-agents.md +1 -1
  129. package/docs/capacity-and-scheduling.md +23 -2
  130. package/docs/commands-and-modes.md +1 -1
  131. package/docs/configuration-and-targets.md +30 -3
  132. package/docs/context-engine.md +63 -4
  133. package/docs/documentation-coverage.md +3 -3
  134. package/docs/documentation-guide.md +1 -1
  135. package/docs/environment-variables.md +2 -0
  136. package/docs/eval-runner.md +262 -11
  137. package/docs/evals-internal.md +72 -2
  138. package/docs/evidence-and-memory.md +11 -10
  139. package/docs/evolution.md +1 -1
  140. package/docs/extensions-and-sharing.md +3 -1
  141. package/docs/fleet-dispatch.md +4 -4
  142. package/docs/installation-and-lifecycle.md +1 -1
  143. package/docs/middleware-and-components.md +1 -1
  144. package/docs/model-catalog.md +1 -1
  145. package/docs/observability.md +53 -2
  146. package/docs/proactive-memory.md +127 -14
  147. package/docs/prompt-envelope-and-tools.md +19 -1
  148. package/docs/provider-adapter-cookbook.md +1 -1
  149. package/docs/release-cut-checklist.md +19 -3
  150. package/docs/safety-model.md +1 -1
  151. package/docs/scientific-validation.md +1 -1
  152. package/docs/skills-marketplace.md +1 -1
  153. package/docs/tool-usage.md +1 -1
  154. package/docs/trace-store.md +1 -1
  155. package/docs/troubleshooting.md +87 -0
  156. package/docs/tui-design.md +1 -1
  157. package/docs/worker-dispatch-mechanics.md +1 -1
  158. package/package.json +2 -1
  159. package/src/cli/agents.ts +1 -1
  160. package/src/cli/config-inspect.ts +33 -6
  161. package/src/cli/config.ts +1 -1
  162. package/src/cli/doctor-state-size.ts +82 -0
  163. package/src/cli/doctor.ts +3 -1
  164. package/src/cli/eval.ts +80 -16
  165. package/src/cli/extensions.ts +5 -1
  166. package/src/cli/fleet.ts +32 -3
  167. package/src/cli/targets.ts +44 -13
  168. package/src/cli/trace.ts +63 -4
  169. package/src/cli/usage.ts +63 -14
  170. package/src/core/bus-events.ts +29 -1
  171. package/src/core/cache-telemetry.ts +42 -0
  172. package/src/core/config.ts +18 -0
  173. package/src/core/defaults.ts +36 -6
  174. package/src/core/endpoint-key.ts +27 -0
  175. package/src/core/residency-target-key.ts +25 -0
  176. package/src/core/response-schema.ts +36 -2
  177. package/src/domains/config/classify.ts +3 -0
  178. package/src/domains/context/codewiki/coordinator.ts +12 -4
  179. package/src/domains/dispatch/admission.ts +40 -3
  180. package/src/domains/dispatch/capacity-lease.ts +98 -9
  181. package/src/domains/dispatch/contract.ts +11 -0
  182. package/src/domains/dispatch/execution-plan.ts +44 -4
  183. package/src/domains/dispatch/extension.ts +166 -42
  184. package/src/domains/dispatch/fleet-run.ts +23 -3
  185. package/src/domains/dispatch/heartbeat.ts +32 -8
  186. package/src/domains/dispatch/index.ts +3 -0
  187. package/src/domains/dispatch/orphan-recovery.ts +5 -0
  188. package/src/domains/dispatch/reservation-store.ts +116 -8
  189. package/src/domains/dispatch/state.ts +4 -0
  190. package/src/domains/dispatch/worker-spawn.ts +25 -11
  191. package/src/domains/dispatch/write-boundary-enforcer.ts +20 -3
  192. package/src/domains/dispatch/write-boundary.ts +62 -1
  193. package/src/domains/eval/artifacts/store.ts +62 -0
  194. package/src/domains/eval/compare/behavioral.ts +224 -0
  195. package/src/domains/eval/compare/compare.ts +355 -2
  196. package/src/domains/eval/compare/envelope.ts +128 -0
  197. package/src/domains/eval/compare/gates.ts +24 -6
  198. package/src/domains/eval/compare/thresholds.ts +30 -3
  199. package/src/domains/eval/execution-provenance.ts +240 -0
  200. package/src/domains/eval/metrics/aggregate.ts +136 -0
  201. package/src/domains/eval/metrics/call-ledger-stream.ts +112 -0
  202. package/src/domains/eval/metrics/tracked.ts +413 -0
  203. package/src/domains/eval/provenance.ts +117 -0
  204. package/src/domains/eval/reports/comparison.ts +128 -0
  205. package/src/domains/eval/reports/junit.ts +17 -3
  206. package/src/domains/eval/reports/markdown.ts +3 -3
  207. package/src/domains/eval/reports/text.ts +14 -0
  208. package/src/domains/eval/run-compare.ts +20 -0
  209. package/src/domains/eval/runners/clio-run.ts +127 -0
  210. package/src/domains/eval/runners/external-command.ts +28 -3
  211. package/src/domains/eval/schema/adapter.ts +111 -0
  212. package/src/domains/eval/schema/artifact.ts +20 -0
  213. package/src/domains/eval/schema/behavioral-metrics.ts +204 -0
  214. package/src/domains/eval/schema/behavioral.ts +520 -0
  215. package/src/domains/eval/schema/execution-envelope.ts +194 -0
  216. package/src/domains/eval/schema/serving.ts +74 -0
  217. package/src/domains/eval/schema/suite.ts +38 -8
  218. package/src/domains/eval/schema/validate.ts +58 -3
  219. package/src/domains/eval/schema/verdict.ts +237 -0
  220. package/src/domains/eval/suites/resolve.ts +2 -0
  221. package/src/domains/eval/suites/run.ts +264 -33
  222. package/src/domains/eval/verifiers/command.ts +2 -1
  223. package/src/domains/eval/workspaces/temp-copy.ts +145 -13
  224. package/src/domains/evidence/build.ts +2 -13
  225. package/src/domains/evidence/eval.ts +2 -12
  226. package/src/domains/evidence/findings-markdown.ts +33 -0
  227. package/src/domains/evidence/run-trust.ts +7 -113
  228. package/src/domains/evidence/trust-projection.ts +2 -2
  229. package/src/domains/extensions/compatibility.ts +285 -0
  230. package/src/domains/extensions/discovery.ts +38 -3
  231. package/src/domains/extensions/resources.ts +1 -1
  232. package/src/domains/extensions/state.ts +12 -3
  233. package/src/domains/extensions/types.ts +2 -0
  234. package/src/domains/lifecycle/doctor.ts +69 -1
  235. package/src/domains/memory/index.ts +14 -0
  236. package/src/domains/memory/task-bank-promotion.ts +64 -0
  237. package/src/domains/memory/task-memory-policy.ts +77 -8
  238. package/src/domains/memory/task-memory-spend.ts +131 -0
  239. package/src/domains/memory/task-memory-status.ts +7 -0
  240. package/src/domains/memory/task-memory-telemetry.ts +2 -0
  241. package/src/domains/middleware/index.ts +1 -0
  242. package/src/domains/middleware/memory-intervention.ts +69 -5
  243. package/src/domains/middleware/memory-step-endpoint.ts +71 -0
  244. package/src/domains/observability/background-memory-usage.ts +140 -0
  245. package/src/domains/observability/cost.ts +1 -1
  246. package/src/domains/observability/index.ts +7 -0
  247. package/src/domains/observability/out-of-turn-usage.ts +51 -2
  248. package/src/domains/observability/trace-store.ts +192 -2
  249. package/src/domains/prompts/compiler.ts +100 -13
  250. package/src/domains/providers/endpoint-capacity.ts +96 -0
  251. package/src/domains/providers/index.ts +10 -0
  252. package/src/domains/providers/models/local-models/clio-local-coding-targets.yaml +243 -1
  253. package/src/domains/providers/runtime-resolution.ts +8 -1
  254. package/src/domains/providers/runtimes/common/probe-helpers.ts +31 -9
  255. package/src/domains/providers/runtimes/local-native/llamacpp-anthropic.ts +1 -1
  256. package/src/domains/providers/runtimes/local-native/llamacpp-completion.ts +1 -1
  257. package/src/domains/providers/runtimes/local-native/llamacpp-embed.ts +1 -1
  258. package/src/domains/providers/runtimes/local-native/llamacpp-rerank.ts +1 -1
  259. package/src/domains/providers/runtimes/local-native/llamacpp.ts +4 -1
  260. package/src/domains/providers/runtimes/local-native/lmstudio.ts +4 -1
  261. package/src/domains/providers/runtimes/local-native/ollama-native.ts +6 -1
  262. package/src/domains/providers/types/capability-flags.ts +2 -0
  263. package/src/domains/providers/types/target-descriptor.ts +2 -0
  264. package/src/domains/resources/prompts/loader.ts +95 -33
  265. package/src/domains/safety/call-target.ts +52 -0
  266. package/src/domains/safety/run-effects.ts +35 -4
  267. package/src/domains/session/context-accounting.ts +52 -1
  268. package/src/domains/session/context-ledger.ts +37 -13
  269. package/src/domains/session/index.ts +6 -0
  270. package/src/domains/session/prompt-cache.ts +140 -0
  271. package/src/domains/session/prompt-manifest.ts +42 -0
  272. package/src/engine/acp/adapter.ts +18 -3
  273. package/src/engine/ai.ts +35 -0
  274. package/src/engine/apis/llamacpp-residency.ts +55 -3
  275. package/src/engine/apis/lmstudio.ts +25 -5
  276. package/src/engine/apis/ollama-native.ts +2 -1
  277. package/src/engine/apis/openai-completions.ts +80 -17
  278. package/src/engine/apis/residency-lock.ts +3 -1
  279. package/src/engine/apis/residency.ts +34 -1
  280. package/src/engine/provider-payload.ts +29 -1
  281. package/src/entry/orchestrator.ts +176 -30
  282. package/src/interactive/chat-loop-messages.ts +26 -7
  283. package/src/interactive/chat-loop.ts +318 -41
  284. package/src/interactive/chat-panel.ts +62 -8
  285. package/src/interactive/clio-editor.ts +45 -8
  286. package/src/interactive/context-activity.ts +5 -1
  287. package/src/interactive/context-meter.ts +1 -1
  288. package/src/interactive/context-overlay.ts +40 -10
  289. package/src/interactive/cost-overlay.ts +64 -6
  290. package/src/interactive/dispatch-board.ts +84 -12
  291. package/src/interactive/fleet-run-preview.ts +41 -15
  292. package/src/interactive/handoff-round.ts +41 -2
  293. package/src/interactive/interactive-application.ts +24 -1
  294. package/src/interactive/interactive-input-runtime.ts +8 -0
  295. package/src/interactive/interactive-presentation.ts +4 -0
  296. package/src/interactive/interactive-shell.ts +20 -17
  297. package/src/interactive/interactive-slash-runtime.ts +27 -4
  298. package/src/interactive/memory-overlay.ts +8 -0
  299. package/src/interactive/mutation-preview.ts +295 -0
  300. package/src/interactive/overlay-general-openers.ts +16 -0
  301. package/src/interactive/overlay-key-routing.ts +38 -0
  302. package/src/interactive/overlay-lifecycle.ts +38 -5
  303. package/src/interactive/overlay-permission-lifecycle.ts +22 -2
  304. package/src/interactive/overlay-session-lifecycle.ts +73 -9
  305. package/src/interactive/overlays/ask-user.ts +91 -19
  306. package/src/interactive/overlays/help-reference.ts +4 -0
  307. package/src/interactive/overlays/prompts.ts +11 -1
  308. package/src/interactive/overlays/settings.ts +35 -1
  309. package/src/interactive/permission-hint.ts +34 -2
  310. package/src/interactive/permission-overlay.ts +159 -9
  311. package/src/interactive/prewarm.ts +197 -0
  312. package/src/interactive/render-trace.ts +162 -15
  313. package/src/interactive/renderers/tool-execution.ts +4 -0
  314. package/src/interactive/side-question.ts +58 -1
  315. package/src/interactive/status/controller.ts +11 -0
  316. package/src/interactive/status/state-machine.ts +54 -2
  317. package/src/interactive/status/types.ts +7 -0
  318. package/src/interactive/terminal-lease.ts +2 -0
  319. package/src/interactive/turn-context.ts +299 -31
  320. package/src/interactive/turn-persistence.ts +14 -4
  321. package/src/interactive/turn-prewarm.ts +364 -0
  322. package/src/interactive/turn-queues.ts +7 -4
  323. package/src/interactive/turn-runtime.ts +8 -1
  324. package/src/interactive/turn-state.ts +23 -0
  325. package/src/interactive/view/view-overlay.ts +28 -3
  326. package/src/tools/ask-user.ts +43 -2
  327. package/src/tools/dispatch-plan.ts +17 -9
  328. package/src/tools/dispatch-scout.ts +1 -1
  329. package/src/tools/registry.ts +16 -0
  330. package/dist/chunk-AOCYTWAV.js +0 -449
  331. package/dist/chunk-HWUFFB6L.js +0 -83
  332. package/dist/chunk-R346GLFC.js +0 -31
  333. package/dist/doctor-M7YEDGAE.js +0 -91
@@ -140,8 +140,16 @@ export function coordinateCodewikiWrite(
140
140
  options: CodewikiCoordinateOptions = {},
141
141
  ): Promise<CodewikiCoordinatedResult | null> {
142
142
  const workspace = resolve(cwd);
143
- return enqueueWorkspace(workspace, () =>
144
- withStateFileLock(codewikiPath(workspace), async () => {
143
+ return enqueueWorkspace(workspace, () => {
144
+ // Taking the lease creates the lock's parent, so a project that was never
145
+ // indexed gained an empty `.clio-coder/` from every successful write: the
146
+ // incremental refresh is contractually a no-op there, and it was
147
+ // materializing state before it could say so (issue #248). The check runs
148
+ // inside the queue, so a writer that built the codewiki ahead of this task
149
+ // has already finished and this one sees it. The recheck under the lock
150
+ // stays, and is what still decides the race.
151
+ if (options.requireExisting && !existsSync(codewikiPath(workspace))) return Promise.resolve(null);
152
+ return withStateFileLock(codewikiPath(workspace), async () => {
145
153
  if (options.requireExisting && !existsSync(codewikiPath(workspace))) return null;
146
154
  const current = options.readCurrent?.(workspace) ?? readCodewiki(workspace);
147
155
  const request = await select(current, workspace);
@@ -152,8 +160,8 @@ export function coordinateCodewikiWrite(
152
160
  if (wrote) writeCodewiki(workspace, worker.codewiki);
153
161
  await options.afterCommit?.(worker, workspace);
154
162
  return { codewiki: !worker.changed && current ? current : worker.codewiki, worker, wrote };
155
- }),
156
- );
163
+ });
164
+ });
157
165
  }
158
166
 
159
167
  /** Serialize a non-build artifact transaction such as reset with every writer. */
@@ -1,4 +1,5 @@
1
1
  import { randomBytes } from "node:crypto";
2
+ import { endpointLabel } from "../providers/endpoint-capacity.js";
2
3
  import type { ActionClass } from "../safety/action-classifier.js";
3
4
  import type { ScopeSpec } from "../safety/scope.js";
4
5
  import { createAdmissionQueue } from "./admission-queue.js";
@@ -7,6 +8,8 @@ import {
7
8
  type CapacityLease,
8
9
  type CapacityLimits,
9
10
  capacityLeaseUsage,
11
+ describeEndpointCapacityHolders,
12
+ type EndpointCapacityHolders,
10
13
  heartbeatCapacityLease,
11
14
  releaseCapacityLease,
12
15
  renameCapacityLeaseAssignment,
@@ -41,6 +44,7 @@ export interface CapacityAdmissionController {
41
44
  admit(input: {
42
45
  assignmentId: string;
43
46
  nodeId: string;
47
+ endpointKey?: string;
44
48
  deadlineAt: number;
45
49
  priority?: number;
46
50
  planId?: string;
@@ -105,13 +109,31 @@ const PUMP_MAX_MS = 500;
105
109
  function describeAdmissionFailure(input: {
106
110
  state: "canceled" | "timed_out";
107
111
  nodeId: string;
112
+ endpointKey?: string;
108
113
  waitedMs: number;
109
114
  overdueAtQueueMs: number;
110
115
  queueDepth: number;
111
116
  limits: CapacityLimits;
112
- usage: { global: number; nodes: Readonly<Record<string, number>> };
117
+ usage: {
118
+ global: number;
119
+ nodes: Readonly<Record<string, number>>;
120
+ endpoints: Readonly<Record<string, number>>;
121
+ endpointHolders?: Readonly<Record<string, EndpointCapacityHolders>>;
122
+ };
113
123
  }): string {
114
124
  if (input.state === "canceled") return "dispatch: admission canceled before a capacity slot opened";
125
+ if (input.endpointKey !== undefined) {
126
+ const endpointLimit = input.limits.endpoints[input.endpointKey];
127
+ const endpointActive = input.usage.endpoints[input.endpointKey] ?? 0;
128
+ if (endpointLimit !== undefined && endpointActive >= endpointLimit) {
129
+ const holders = input.usage.endpointHolders?.[input.endpointKey];
130
+ const occupancy =
131
+ holders === undefined
132
+ ? "the supplied capacity snapshot does not attribute the occupied slots"
133
+ : describeEndpointCapacityHolders(holders);
134
+ return `dispatch: admission denied: endpoint '${endpointLabel(input.endpointKey)}' capacity reached (${endpointActive}/${endpointLimit} slots): ${occupancy}; collect in-flight runs or point workers at a second server`;
135
+ }
136
+ }
115
137
  const nodeLimit = input.limits.nodes[input.nodeId];
116
138
  const nodeActive = input.usage.nodes[input.nodeId] ?? 0;
117
139
  const where =
@@ -142,7 +164,12 @@ function describeAdmissionFailure(input: {
142
164
  export function createCapacityAdmissionController(options: {
143
165
  limits: () => CapacityLimits;
144
166
  /** Live lease counts for the timeout message; defaults to the durable state file. */
145
- usage?: () => { global: number; nodes: Readonly<Record<string, number>> };
167
+ usage?: () => {
168
+ global: number;
169
+ nodes: Readonly<Record<string, number>>;
170
+ endpoints: Readonly<Record<string, number>>;
171
+ endpointHolders?: Readonly<Record<string, EndpointCapacityHolders>>;
172
+ };
146
173
  now?: () => number;
147
174
  maxQueueSize?: number;
148
175
  queueCeilingMs?: number;
@@ -175,7 +202,14 @@ export function createCapacityAdmissionController(options: {
175
202
  admittedLease = request.value();
176
203
  return true;
177
204
  } catch (error) {
178
- // Capacity is a wait, not a failure. Anything else fails the request.
205
+ // Endpoint saturation is a named refusal because waiting would keep a
206
+ // model request queued behind the same inference scheduler. Existing
207
+ // node and global admission retain their bounded wait behavior.
208
+ if (error instanceof Error && /endpoint '.*' capacity reached/u.test(error.message)) {
209
+ queue.fail(request.requestId, error);
210
+ return false;
211
+ }
212
+ // Node and global capacity can wait. Anything else fails the request.
179
213
  if (error instanceof Error && /capacity reached/.test(error.message)) return false;
180
214
  queue.fail(request.requestId, error instanceof Error ? error : new Error(String(error)));
181
215
  return false;
@@ -229,12 +263,14 @@ export function createCapacityAdmissionController(options: {
229
263
  memberId: input.reservation.memberId,
230
264
  assignmentId: input.assignmentId,
231
265
  nodeId: input.nodeId,
266
+ ...(input.endpointKey !== undefined ? { endpointKey: input.endpointKey } : {}),
232
267
  limits: options.limits(),
233
268
  nowMs: now(),
234
269
  })
235
270
  : acquireCapacityLease({
236
271
  assignmentId: input.assignmentId,
237
272
  nodeId: input.nodeId,
273
+ ...(input.endpointKey !== undefined ? { endpointKey: input.endpointKey } : {}),
238
274
  limits: options.limits(),
239
275
  nowMs: now(),
240
276
  }),
@@ -246,6 +282,7 @@ export function createCapacityAdmissionController(options: {
246
282
  describeAdmissionFailure({
247
283
  state: outcome.state,
248
284
  nodeId: input.nodeId,
285
+ ...(input.endpointKey !== undefined ? { endpointKey: input.endpointKey } : {}),
249
286
  waitedMs: now() - queuedAt,
250
287
  overdueAtQueueMs: queuedAt - input.deadlineAt,
251
288
  queueDepth: queue.size(),
@@ -6,6 +6,7 @@ import { BIRTH_TOKEN_SOURCE_AVAILABLE, processAlive, processBirthToken } from ".
6
6
  import { FILE_LOCK_ACQUIRE_TIMEOUT_MS, withStateFileLockSync } from "../../core/state-file-lock.js";
7
7
  import { clioStateDir } from "../../core/xdg.js";
8
8
  import { atomicWrite } from "../../engine/session.js";
9
+ import { endpointLabel, foregroundStreamUsage } from "../providers/endpoint-capacity.js";
9
10
 
10
11
  export { processBirthToken } from "../../core/process-identity.js";
11
12
 
@@ -30,6 +31,8 @@ export interface CapacityLease {
30
31
  leaseId: string;
31
32
  assignmentId: string;
32
33
  nodeId: string;
34
+ /** Canonical inference endpoint. Absent only on leases written before endpoint capacity existed. */
35
+ endpointKey?: string;
33
36
  /** Owner host. Absent only on records written before the field existed; those are read as this host. */
34
37
  host?: string;
35
38
  ownerPid: number;
@@ -43,6 +46,7 @@ export interface CapacityLease {
43
46
  export interface CapacityLimits {
44
47
  global: number;
45
48
  nodes: Readonly<Record<string, number>>;
49
+ endpoints: Readonly<Record<string, number>>;
46
50
  }
47
51
  export interface CapacityDrain {
48
52
  requestedByPid: number;
@@ -81,6 +85,7 @@ function valid(value: unknown): value is CapacityLease {
81
85
  typeof lease.leaseId === "string" &&
82
86
  typeof lease.assignmentId === "string" &&
83
87
  typeof lease.nodeId === "string" &&
88
+ (lease.endpointKey === undefined || typeof lease.endpointKey === "string") &&
84
89
  (lease.host === undefined || typeof lease.host === "string") &&
85
90
  Number.isInteger(lease.ownerPid) &&
86
91
  typeof lease.processBirthToken === "string" &&
@@ -182,9 +187,14 @@ const MEMBER_STATUSES = new Set(["held", "consumed", "released"]);
182
187
  * rejected here as well: silently skipping an unreadable record would under-count
183
188
  * capacity and admit past the cap.
184
189
  */
185
- function heldReservationUsage(values: ReadonlyArray<unknown>): { global: number; nodes: Record<string, number> } {
190
+ function heldReservationUsage(values: ReadonlyArray<unknown>): {
191
+ global: number;
192
+ nodes: Record<string, number>;
193
+ endpoints: Record<string, number>;
194
+ } {
186
195
  let global = 0;
187
196
  const nodes: Record<string, number> = {};
197
+ const endpoints: Record<string, number> = {};
188
198
  for (const value of values) {
189
199
  if (typeof value !== "object" || value === null)
190
200
  throw new Error("dispatch capacity store has invalid reservation records");
@@ -194,10 +204,11 @@ function heldReservationUsage(values: ReadonlyArray<unknown>): { global: number;
194
204
  if (record.status !== "active") continue;
195
205
  const byWave = new Map<number, number>();
196
206
  const byNodeWave = new Map<string, Map<number, number>>();
207
+ const byEndpointWave = new Map<string, Map<number, number>>();
197
208
  for (const raw of record.members) {
198
209
  if (typeof raw !== "object" || raw === null)
199
210
  throw new Error("dispatch capacity store has invalid reservation records");
200
- const member = raw as { status?: unknown; wave?: unknown; nodeId?: unknown };
211
+ const member = raw as { status?: unknown; wave?: unknown; nodeId?: unknown; endpointKey?: unknown };
201
212
  if (
202
213
  typeof member.status !== "string" ||
203
214
  !MEMBER_STATUSES.has(member.status) ||
@@ -211,13 +222,53 @@ function heldReservationUsage(values: ReadonlyArray<unknown>): { global: number;
211
222
  const nodeWaves = byNodeWave.get(member.nodeId) ?? new Map<number, number>();
212
223
  nodeWaves.set(wave, (nodeWaves.get(wave) ?? 0) + 1);
213
224
  byNodeWave.set(member.nodeId, nodeWaves);
225
+ if (member.endpointKey !== undefined && typeof member.endpointKey !== "string")
226
+ throw new Error("dispatch capacity store has invalid reservation records");
227
+ if (typeof member.endpointKey === "string") {
228
+ const endpointWaves = byEndpointWave.get(member.endpointKey) ?? new Map<number, number>();
229
+ endpointWaves.set(wave, (endpointWaves.get(wave) ?? 0) + 1);
230
+ byEndpointWave.set(member.endpointKey, endpointWaves);
231
+ }
214
232
  }
215
233
  global += Math.max(0, ...byWave.values());
216
234
  for (const [id, waves] of byNodeWave) nodes[id] = (nodes[id] ?? 0) + Math.max(0, ...waves.values());
235
+ for (const [key, waves] of byEndpointWave) endpoints[key] = (endpoints[key] ?? 0) + Math.max(0, ...waves.values());
217
236
  }
218
- return { global, nodes };
237
+ return { global, nodes, endpoints };
219
238
  }
220
- function assertCapacity(file: CapacityStateFile, nodeId: string, limits: CapacityLimits): void {
239
+
240
+ export interface EndpointCapacityHolders {
241
+ leases: number;
242
+ reservations: number;
243
+ foregroundStreams: number;
244
+ }
245
+
246
+ /** Name the endpoint-capacity addends that are actually nonzero. */
247
+ export function describeEndpointCapacityHolders(holders: EndpointCapacityHolders): string {
248
+ const parts: string[] = [];
249
+ const add = (count: number, singular: string, plural: string): void => {
250
+ if (count > 0) parts.push(`${count} ${count === 1 ? singular : plural}`);
251
+ };
252
+ add(holders.leases, "active lease", "active leases");
253
+ add(holders.reservations, "held reservation", "held reservations");
254
+ add(holders.foregroundStreams, "foreground stream", "foreground streams");
255
+ if (parts.length === 0) return "no active lease, held reservation, or foreground stream currently holds a slot";
256
+ const named =
257
+ parts.length === 1
258
+ ? parts[0]
259
+ : parts.length === 2
260
+ ? `${parts[0]} and ${parts[1]}`
261
+ : `${parts.slice(0, -1).join(", ")}, and ${parts.at(-1)}`;
262
+ const total = holders.leases + holders.reservations + holders.foregroundStreams;
263
+ return `${named} ${total === 1 ? "holds" : "hold"} ${total === 1 ? "the slot" : "the slots"}`;
264
+ }
265
+
266
+ function assertCapacity(
267
+ file: CapacityStateFile,
268
+ nodeId: string,
269
+ endpointKey: string | undefined,
270
+ limits: CapacityLimits,
271
+ ): void {
221
272
  const held = heldReservationUsage(file.reservations);
222
273
  const globalUsed = file.leases.length + held.global;
223
274
  // The denial names the compliant next move, not just the gate. A model told
@@ -235,10 +286,28 @@ function assertCapacity(file: CapacityStateFile, nodeId: string, limits: Capacit
235
286
  if (used >= nodeLimit)
236
287
  throw new Error(`dispatch: admission denied: node '${nodeId}' capacity reached (${used}/${nodeLimit})`);
237
288
  }
289
+ if (endpointKey !== undefined) {
290
+ const endpointLimit = limits.endpoints[endpointKey];
291
+ if (endpointLimit !== undefined) {
292
+ if (endpointLimit < 1)
293
+ throw new Error(`dispatch: admission denied: endpoint '${endpointLabel(endpointKey)}' capacity unavailable`);
294
+ const holders: EndpointCapacityHolders = {
295
+ leases: file.leases.filter((lease) => lease.endpointKey === endpointKey).length,
296
+ reservations: held.endpoints[endpointKey] ?? 0,
297
+ foregroundStreams: foregroundStreamUsage()[endpointKey] ?? 0,
298
+ };
299
+ const used = holders.leases + holders.reservations + holders.foregroundStreams;
300
+ if (used >= endpointLimit)
301
+ throw new Error(
302
+ `dispatch: admission denied: endpoint '${endpointLabel(endpointKey)}' capacity reached (${used}/${endpointLimit} slots): ${describeEndpointCapacityHolders(holders)}; collect in-flight runs or point workers at a second server`,
303
+ );
304
+ }
305
+ }
238
306
  }
239
307
  export function acquireCapacityLease(input: {
240
308
  assignmentId: string;
241
309
  nodeId: string;
310
+ endpointKey?: string;
242
311
  limits: CapacityLimits;
243
312
  nowMs?: number;
244
313
  ttlMs?: number;
@@ -259,10 +328,12 @@ export function acquireCapacityLease(input: {
259
328
  );
260
329
  const existing = file.leases.find((lease) => lease.assignmentId === input.assignmentId);
261
330
  if (existing) {
262
- if (existing.nodeId !== input.nodeId) {
331
+ if (existing.nodeId !== input.nodeId || existing.endpointKey !== input.endpointKey) {
263
332
  const without = { ...file, leases: file.leases.filter((lease) => lease !== existing) };
264
- assertCapacity(without, input.nodeId, input.limits);
333
+ assertCapacity(without, input.nodeId, input.endpointKey, input.limits);
265
334
  existing.nodeId = input.nodeId;
335
+ if (input.endpointKey === undefined) delete existing.endpointKey;
336
+ else existing.endpointKey = input.endpointKey;
266
337
  }
267
338
  existing.heartbeatAt = new Date(nowMs).toISOString();
268
339
  existing.expiresAt = new Date(nowMs + (input.ttlMs ?? DEFAULT_CAPACITY_LEASE_TTL_MS)).toISOString();
@@ -270,7 +341,7 @@ export function acquireCapacityLease(input: {
270
341
  return clone(existing);
271
342
  }
272
343
  input.onAcquiredUnderLock?.(file);
273
- assertCapacity(file, input.nodeId, input.limits);
344
+ assertCapacity(file, input.nodeId, input.endpointKey, input.limits);
274
345
  const token = input.processBirthToken ?? processBirthToken(input.ownerPid ?? process.pid);
275
346
  if (!token) throw new Error("dispatch: cannot establish process birth token");
276
347
  const at = new Date(nowMs).toISOString();
@@ -278,6 +349,7 @@ export function acquireCapacityLease(input: {
278
349
  leaseId: `lease-${nowMs.toString(36)}-${randomBytes(6).toString("hex")}`,
279
350
  assignmentId: input.assignmentId,
280
351
  nodeId: input.nodeId,
352
+ ...(input.endpointKey !== undefined ? { endpointKey: input.endpointKey } : {}),
281
353
  host: hostname(),
282
354
  ownerPid: input.ownerPid ?? process.pid,
283
355
  processBirthToken: token,
@@ -365,11 +437,28 @@ export function capacityDrain(nowMs = Date.now()): CapacityDrain | null {
365
437
  export function capacityLeaseUsage(options?: { nowMs?: number; probe?: LeaseOwnerProbe }): {
366
438
  global: number;
367
439
  nodes: Readonly<Record<string, number>>;
440
+ endpoints: Readonly<Record<string, number>>;
441
+ endpointHolders: Readonly<Record<string, EndpointCapacityHolders>>;
368
442
  } {
369
443
  const leases = listCapacityLeases(options);
370
444
  const nodes: Record<string, number> = {};
371
- for (const lease of leases) nodes[lease.nodeId] = (nodes[lease.nodeId] ?? 0) + 1;
372
- return { global: leases.length, nodes };
445
+ const foreground = foregroundStreamUsage();
446
+ const endpoints: Record<string, number> = { ...foreground };
447
+ const endpointHolders: Record<string, EndpointCapacityHolders> = Object.fromEntries(
448
+ Object.entries(foreground).map(([endpointKey, foregroundStreams]) => [
449
+ endpointKey,
450
+ { leases: 0, reservations: 0, foregroundStreams },
451
+ ]),
452
+ );
453
+ for (const lease of leases) {
454
+ nodes[lease.nodeId] = (nodes[lease.nodeId] ?? 0) + 1;
455
+ if (lease.endpointKey !== undefined) {
456
+ endpoints[lease.endpointKey] = (endpoints[lease.endpointKey] ?? 0) + 1;
457
+ const holders = endpointHolders[lease.endpointKey] ?? { leases: 0, reservations: 0, foregroundStreams: 0 };
458
+ endpointHolders[lease.endpointKey] = { ...holders, leases: holders.leases + 1 };
459
+ }
460
+ }
461
+ return { global: leases.length, nodes, endpoints, endpointHolders };
373
462
  }
374
463
 
375
464
  /**
@@ -13,6 +13,7 @@ import type { ApprovedAssignmentRoute } from "./route-approval.js";
13
13
  import type { RouteDecisionV1 } from "./route-decision.js";
14
14
  import type { RunEnvelope, RunLineage, RunNodeIdentity, RunPhaseDurations, RunReceipt, RunStatus } from "./types.js";
15
15
  import type { DispatchFailoverCandidate, JobSpec } from "./validation.js";
16
+ import type { WriteBoundaryAttribution } from "./write-boundary.js";
16
17
 
17
18
  export interface DispatchRequest extends JobSpec {
18
19
  /** Coordinator-owned result contract override for a fleet plan step. */
@@ -93,6 +94,8 @@ export interface DispatchPlanTaskResolution {
93
94
  targetId: string;
94
95
  wireModelId: string;
95
96
  runtimeId: string;
97
+ /** Canonical inference scheduler identity and its independent request-slot bound. */
98
+ endpoint?: { key: string; label: string; limit: number };
96
99
  node: RunNodeIdentity;
97
100
  /** Effective thinking control for this route; null when the runtime has none. */
98
101
  thinkingLevel: string | null;
@@ -209,6 +212,7 @@ export interface DispatchContract {
209
212
  topology: ReservationTopology;
210
213
  tasks: ReadonlyArray<{ memberId: string; wave: number; resolution: DispatchPlanTaskResolution }>;
211
214
  }): DispatchReservationRecord;
215
+ release(ownerId: string): DispatchReservationRecord | null;
212
216
  rollback(ownerId: string): DispatchReservationRecord | null;
213
217
  rollbackUnconsumed(ownerId: string): DispatchReservationRecord | null;
214
218
  get(ownerId: string): DispatchReservationRecord | null;
@@ -266,6 +270,13 @@ export interface DispatchContract {
266
270
  */
267
271
  observedRunWrites?(runId: string): ReadonlyArray<string> | null;
268
272
 
273
+ /**
274
+ * Full write-record witness, including durable downgrade causes. New callers
275
+ * use this surface; observedRunWrites remains as the closed-list compatibility
276
+ * view for integrations that only know paths or null.
277
+ */
278
+ observedRunWriteAttribution?(runId: string): WriteBoundaryAttribution | null;
279
+
269
280
  /** In-memory logical assignment state and complete finalized-attempt history. */
270
281
  readonly assignments?: {
271
282
  /** Live assignment, including its terminal promise and detailed attempt refs. */
@@ -59,6 +59,8 @@ export interface ExecutionPlanAgentStep {
59
59
  /** Exact per-step route defaults from a version 5 fleet contract. */
60
60
  target?: string;
61
61
  profile?: string;
62
+ /** Resolved inference scheduler bound used when waves are packed. */
63
+ endpoint?: { key: string; limit: number; foregroundHeld?: number };
62
64
  /** Version 5 gate authoring and baseline execution facts. */
63
65
  gate?: { path: string; commandId: string };
64
66
  /** Version 5 dynamic planning facts. */
@@ -163,7 +165,12 @@ function canonicalSteps(steps: ReadonlyArray<ExecutionPlanStepInput>): Execution
163
165
  }
164
166
 
165
167
  export function executionPlanWaves(
166
- steps: ReadonlyArray<{ id: string; dependencies: ReadonlyArray<string>; kind?: "agent" | "code" }>,
168
+ steps: ReadonlyArray<{
169
+ id: string;
170
+ dependencies: ReadonlyArray<string>;
171
+ kind?: "agent" | "code";
172
+ endpoint?: { key: string; limit: number; foregroundHeld?: number };
173
+ }>,
167
174
  maxWorkers: number,
168
175
  ): string[][] {
169
176
  if (!Number.isInteger(maxWorkers) || maxWorkers < 1)
@@ -191,11 +198,24 @@ export function executionPlanWaves(
191
198
  // capacity lease, so they ride the first chunk of their ready set without
192
199
  // displacing an agent step or forcing an extra wave.
193
200
  const codeReady = ready.filter((step) => step.kind === "code").map((step) => step.id);
194
- const agentReady = ready.filter((step) => step.kind !== "code").map((step) => step.id);
201
+ const agentReady = ready.filter((step) => step.kind !== "code");
195
202
  const chunks: string[][] = [];
196
- for (let offset = 0; offset < agentReady.length; offset += maxWorkers) {
197
- chunks.push(agentReady.slice(offset, offset + maxWorkers));
203
+ let chunk: string[] = [];
204
+ let endpointCounts = new Map<string, number>();
205
+ for (const step of agentReady) {
206
+ const endpoint = step.endpoint;
207
+ const endpointLimit =
208
+ endpoint === undefined ? Number.POSITIVE_INFINITY : Math.max(1, endpoint.limit - (endpoint.foregroundHeld ?? 0));
209
+ const endpointFull = endpoint !== undefined && (endpointCounts.get(endpoint.key) ?? 0) >= endpointLimit;
210
+ if (chunk.length >= maxWorkers || endpointFull) {
211
+ chunks.push(chunk);
212
+ chunk = [];
213
+ endpointCounts = new Map<string, number>();
214
+ }
215
+ chunk.push(step.id);
216
+ if (endpoint !== undefined) endpointCounts.set(endpoint.key, (endpointCounts.get(endpoint.key) ?? 0) + 1);
198
217
  }
218
+ if (chunk.length > 0) chunks.push(chunk);
199
219
  if (chunks.length === 0) chunks.push([]);
200
220
  chunks[0] = [...codeReady, ...(chunks[0] ?? [])];
201
221
  for (const wave of chunks) {
@@ -317,6 +337,26 @@ export function compileExecutionPlan(input: ExecutionPlanInput): ExecutionPlan {
317
337
  return { ...canonical, hash: createHash("sha256").update(JSON.stringify(canonical)).digest("hex") };
318
338
  }
319
339
 
340
+ /** Recompile one approved graph after route preflight supplies endpoint bounds. */
341
+ export function bindExecutionPlanEndpoints(
342
+ plan: ExecutionPlan,
343
+ bindings: Readonly<Record<string, { key: string; limit: number; foregroundHeld?: number } | undefined>>,
344
+ ): ExecutionPlan {
345
+ return compileExecutionPlan({
346
+ topology: plan.topology,
347
+ rootTask: plan.rootTask,
348
+ maxWorkers: plan.maxWorkers,
349
+ ...(plan.writers === 1 ? { writers: 1 as const } : {}),
350
+ onFailure: plan.onFailure,
351
+ steps: plan.steps.map((step) => {
352
+ if (step.kind === "code") return step;
353
+ const endpoint = bindings[step.id];
354
+ return endpoint === undefined ? step : { ...step, endpoint: { ...endpoint } };
355
+ }),
356
+ loops: plan.loops,
357
+ });
358
+ }
359
+
320
360
  /** Transitive dependency closure of one plan step. */
321
361
  export function executionPlanAncestors(plan: ExecutionPlan, stepId: string): ReadonlySet<string> {
322
362
  const byId = new Map(plan.steps.map((step) => [step.id, step]));