@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
@@ -403,19 +403,83 @@ export function createOverlaySessionLifecycle(deps: OverlaySessionLifecycleDeps)
403
403
  void runHandoffExtraction(session, fromSessionId, verdict.goal);
404
404
  }
405
405
 
406
+ /** Characters of the second round's answer quoted in the terminal refusal. */
407
+ const HANDOFF_REFUSAL_QUOTE_CHARS = 200;
408
+
409
+ /**
410
+ * A refusal that says what was asked for and what came back, not only that no
411
+ * JSON object arrived. Both rounds are named, so an operator reading it knows
412
+ * two model calls were spent and on what.
413
+ */
414
+ function terminalHandoffRefusal(firstReason: string, secondReason: string, secondText: string): string {
415
+ const answered = secondText.replace(/\s+/g, " ").trim().slice(0, HANDOFF_REFUSAL_QUOTE_CHARS);
416
+ return [
417
+ "the extraction round could not produce a handoff record after one repair attempt.",
418
+ `Asked for: one JSON object with decisions, facts, files, commands, and openQuestions.`,
419
+ `Round 1: ${firstReason}.`,
420
+ `Round 2: ${secondReason}.`,
421
+ `Round 2 returned: ${answered.length > 0 ? answered : "(nothing)"}`,
422
+ ].join(" ");
423
+ }
424
+
425
+ /**
426
+ * Extract, then repair once.
427
+ *
428
+ * A local model that answers with prose around the object, or with nothing
429
+ * parseable, used to end `/handoff` outright: every downstream behavior (the
430
+ * dropped-path listing, the `e` editor, accept-mints-a-session) was
431
+ * unreachable and the operator had paid for the round either way (issue
432
+ * #223). Exactly one repair round follows, quoting what came back and what
433
+ * the parser objected to, and both rounds bill through the same out-of-turn
434
+ * usage store.
435
+ */
436
+ async function extractWithOneRepair(
437
+ goal: string,
438
+ ): Promise<
439
+ | { ok: true; parsed: Extract<ReturnType<typeof parseHandoffExtraction>, { ok: true }> }
440
+ | { ok: false; level: "error" | "warn"; reason: string }
441
+ > {
442
+ const first = await deps.chat.extractHandoff(goal);
443
+ if (first.status !== "answered") {
444
+ return {
445
+ ok: false,
446
+ level: first.status === "failed" ? "error" : "warn",
447
+ reason: first.status === "aborted" ? "the extraction round was cancelled" : first.reason,
448
+ };
449
+ }
450
+ const parsedFirst = parseHandoffExtraction(first.text);
451
+ if (parsedFirst.ok) return { ok: true, parsed: parsedFirst };
452
+
453
+ const second = await deps.chat.extractHandoff(goal, {
454
+ repair: { complaint: parsedFirst.reason, previous: first.text },
455
+ });
456
+ if (second.status !== "answered") {
457
+ return {
458
+ ok: false,
459
+ level: second.status === "failed" ? "error" : "warn",
460
+ reason:
461
+ second.status === "aborted"
462
+ ? "the repair round was cancelled"
463
+ : `round 1 could not be read (${parsedFirst.reason}); the repair round then failed: ${second.reason}`,
464
+ };
465
+ }
466
+ const parsedSecond = parseHandoffExtraction(second.text);
467
+ if (parsedSecond.ok) return { ok: true, parsed: parsedSecond };
468
+ return {
469
+ ok: false,
470
+ level: "error",
471
+ reason: terminalHandoffRefusal(parsedFirst.reason, parsedSecond.reason, second.text),
472
+ };
473
+ }
474
+
406
475
  async function runHandoffExtraction(session: SessionContract, fromSessionId: string, goal: string): Promise<void> {
407
476
  const notice = deps.getSlashNotice();
408
- const outcome = await deps.chat.extractHandoff(goal);
409
- if (outcome.status !== "answered") {
410
- const reason = outcome.status === "aborted" ? "the extraction round was cancelled" : outcome.reason;
411
- emitCommandNotice(notice, outcome.status === "failed" ? "error" : "warn", "handoff", reason);
412
- return;
413
- }
414
- const parsed = parseHandoffExtraction(outcome.text);
415
- if (!parsed.ok) {
416
- emitCommandNotice(notice, "error", "handoff", parsed.reason);
477
+ const extraction = await extractWithOneRepair(goal);
478
+ if (!extraction.ok) {
479
+ emitCommandNotice(notice, extraction.level, "handoff", extraction.reason);
417
480
  return;
418
481
  }
482
+ const parsed = extraction.parsed;
419
483
  const meta = session.current();
420
484
  const cwd = typeof meta?.cwd === "string" && meta.cwd.length > 0 ? meta.cwd : null;
421
485
  const entries = deps.readStructuredEntries(fromSessionId);
@@ -129,6 +129,14 @@ interface QuestionState {
129
129
  customAnswer: string;
130
130
  inputValue: string;
131
131
  answer: string;
132
+ /**
133
+ * The operator's typed text exactly as submitted, empty when they typed
134
+ * nothing. `answer` is the one-line rendering of the whole decision and gets
135
+ * trimmed and joined; this is the record of what they actually typed, and
136
+ * losing it is what made an interview re-ask for the same figures three times
137
+ * (issue #228).
138
+ */
139
+ rawValue: string;
132
140
  focusedValue?: string;
133
141
  }
134
142
 
@@ -154,6 +162,7 @@ function createQuestionState(question: AskUserQuestion): QuestionState {
154
162
  customAnswer: "",
155
163
  inputValue: "",
156
164
  answer: "",
165
+ rawValue: "",
157
166
  };
158
167
  }
159
168
 
@@ -205,12 +214,18 @@ function optionIndexFromValue(value: string): number | null {
205
214
  return Number.isInteger(index) && index >= 0 ? index : null;
206
215
  }
207
216
 
208
- function answerText(question: AskUserQuestion, selected: ReadonlySet<number>, customAnswer: string): string {
209
- const parts: string[] = [];
217
+ /** The labels the operator chose, in list order. */
218
+ function selectedOptionLabels(question: AskUserQuestion, selected: ReadonlySet<number>): string[] {
219
+ const labels: string[] = [];
210
220
  for (const index of [...selected].sort((a, b) => a - b)) {
211
221
  const label = question.options?.[index]?.label;
212
- if (label) parts.push(label);
222
+ if (label) labels.push(label);
213
223
  }
224
+ return labels;
225
+ }
226
+
227
+ function answerText(question: AskUserQuestion, selected: ReadonlySet<number>, customAnswer: string): string {
228
+ const parts = selectedOptionLabels(question, selected);
214
229
  const custom = customAnswer.trim();
215
230
  if (custom.length > 0) parts.push(custom);
216
231
  return parts.join("; ");
@@ -348,6 +363,10 @@ class AskUserOverlayView implements Component {
348
363
  this.commitMultiSelectOrOpenOther(state);
349
364
  return;
350
365
  }
366
+ if (this.isAddTextKey(data)) {
367
+ this.chooseFocusedAndOpenText(question, state);
368
+ return;
369
+ }
351
370
  this.list?.handleInput(data);
352
371
  }
353
372
 
@@ -502,24 +521,22 @@ class AskUserOverlayView implements Component {
502
521
  // The classified action label distinguishes recording an answer from
503
522
  // granting authority. Esc keeps the product's one word for the way out,
504
523
  // since it closes the prompt and the interview together.
524
+ // `t` is on every select footer because the operator cannot tell from a
525
+ // label whether the option needs a figure attached until they read it.
526
+ const addText: HintEntry = { key: "t", verb: "add text", short: "text" };
505
527
  if (question.multi_select === true) {
506
528
  return this.questions.length > 1
507
529
  ? this.withScrollHint([
508
530
  { key: "Left/Right", verb: "question" },
509
531
  { key: "Space", verb: "toggle" },
532
+ addText,
510
533
  { key: "Enter", verb: recordAnswer },
511
534
  ])
512
- : this.withScrollHint([
513
- { key: "Space", verb: "toggle" },
514
- { key: "Enter", verb: recordAnswer },
515
- ]);
535
+ : this.withScrollHint([{ key: "Space", verb: "toggle" }, addText, { key: "Enter", verb: recordAnswer }]);
516
536
  }
517
537
  return this.questions.length > 1
518
- ? this.withScrollHint([
519
- { key: "Left/Right", verb: "question" },
520
- { key: "Enter", verb: recordAnswer },
521
- ])
522
- : this.withScrollHint([{ key: "Enter", verb: recordAnswer }]);
538
+ ? this.withScrollHint([{ key: "Left/Right", verb: "question" }, addText, { key: "Enter", verb: recordAnswer }])
539
+ : this.withScrollHint([addText, { key: "Enter", verb: recordAnswer }]);
523
540
  }
524
541
 
525
542
  private renderDecisionContext(width: number): string[] {
@@ -644,6 +661,34 @@ class AskUserOverlayView implements Component {
644
661
  return this.questions.length > 1 && (matchesKey(data, "alt+right") || matchesKey(data, "ctrl+right"));
645
662
  }
646
663
 
664
+ /**
665
+ * The key that adds typed text to the option under the cursor.
666
+ *
667
+ * Choosing an option used to be the whole answer, so a model that offered
668
+ * "Exact number - I'll type it" gave the operator a label and nowhere to put
669
+ * the number; the interview then spent two of its four rounds asking for the
670
+ * figures again (issue #228). The list itself only reads the arrows, Enter,
671
+ * and Esc, so a letter is free here.
672
+ */
673
+ private isAddTextKey(data: string): boolean {
674
+ return matchesKey(data, "t");
675
+ }
676
+
677
+ /**
678
+ * Take the focused option and open the text field for it, without committing.
679
+ * Submitting the text records the label and the text together.
680
+ */
681
+ private chooseFocusedAndOpenText(question: AskUserQuestion, state: QuestionState): void {
682
+ const current = this.list?.getSelectedItem();
683
+ const optionIndex = current && current.value !== "other" ? optionIndexFromValue(current.value) : null;
684
+ if (optionIndex !== null) {
685
+ if (question.multi_select === true) state.selected.add(optionIndex);
686
+ else state.selected = new Set<number>([optionIndex]);
687
+ state.focusedValue = `option:${optionIndex}`;
688
+ }
689
+ this.openTextInput(state.selected.size > 0 ? "Add your answer to the chosen option" : "Other answer");
690
+ }
691
+
647
692
  /**
648
693
  * The row above the question, or nothing.
649
694
  *
@@ -829,14 +874,20 @@ class AskUserOverlayView implements Component {
829
874
  return;
830
875
  }
831
876
  state.inputValue = answer;
877
+ // Verbatim, before any joining or trimming the display line does. The
878
+ // typed text is the answer; the one-line `answer` is a rendering of it.
879
+ state.rawValue = value;
832
880
  if (questionHasOptions(question) && question.multi_select === true) {
833
881
  state.customAnswer = answer;
834
882
  this.commitCurrentAnswer();
835
- } else {
836
- state.customAnswer = questionHasOptions(question) ? answer : "";
837
- state.answer = answer;
838
- this.finishIfCompleteOrAdvance();
883
+ return;
839
884
  }
885
+ state.customAnswer = questionHasOptions(question) ? answer : "";
886
+ // The chosen labels and the typed text compose, so a question answered
887
+ // with an option that says "I will type it" keeps both. With nothing
888
+ // selected, which is the implicit Other path, this is the text alone.
889
+ state.answer = questionHasOptions(question) ? answerText(question, state.selected, state.customAnswer) : answer;
890
+ this.finishIfCompleteOrAdvance();
840
891
  };
841
892
  activeInput.onEscape = () => this.cancel();
842
893
  this.input = activeInput;
@@ -866,7 +917,14 @@ class AskUserOverlayView implements Component {
866
917
  return;
867
918
  }
868
919
  state.selected = new Set<number>([optionIndex]);
869
- state.answer = question.options?.[optionIndex]?.label ?? item.label;
920
+ // Choosing a plain option is a label-only answer, so the typed text
921
+ // clears with the choice: it was given for a label the operator has
922
+ // just moved off, and the composed answer runs through the same
923
+ // `answerText` every other path uses.
924
+ state.customAnswer = "";
925
+ state.rawValue = "";
926
+ state.inputValue = "";
927
+ state.answer = answerText(question, state.selected, state.customAnswer) || item.label;
870
928
  state.focusedValue = item.value;
871
929
  this.status = "";
872
930
  this.finishIfCompleteOrAdvance();
@@ -992,12 +1050,26 @@ class AskUserOverlayView implements Component {
992
1050
  return this.states.length > 0 && this.states.every((state) => state.answer.trim().length > 0);
993
1051
  }
994
1052
 
1053
+ /**
1054
+ * What the round captured, as three separable facts per question: the one-line
1055
+ * answer, the labels chosen, and the text typed. A reader tells a label-only
1056
+ * answer from a label-plus-value one by whether `value` is there, which the
1057
+ * joined string alone could never say.
1058
+ */
995
1059
  private answers(): AskUserResult["answers"] {
996
1060
  const answers: AskUserResult["answers"] = [];
997
1061
  for (let index = 0; index < this.questions.length; index += 1) {
998
1062
  const question = this.questions[index];
999
- const answer = this.states[index]?.answer.trim();
1000
- if (question && answer && answer.length > 0) answers.push({ question: question.question, answer });
1063
+ const state = this.states[index];
1064
+ const answer = state?.answer.trim();
1065
+ if (!question || !state || !answer || answer.length === 0) continue;
1066
+ const options = selectedOptionLabels(question, state.selected);
1067
+ answers.push({
1068
+ question: question.question,
1069
+ answer,
1070
+ ...(options.length > 0 ? { options } : {}),
1071
+ ...(state.rawValue.length > 0 ? { value: state.rawValue } : {}),
1072
+ });
1001
1073
  }
1002
1074
  return answers;
1003
1075
  }
@@ -129,6 +129,10 @@ export function openHelpOverlay(
129
129
  "auto-edit parks unrecognized commands, and full-auto runs them.",
130
130
  "**Approvals**: a parked call waits for a one-shot operator decision; approving resumes only that call. " +
131
131
  "Workers resolve asks per `workers.onPermission` (Approvals Routing); headless runs auto-deny.",
132
+ "**Inspecting a mutation**: a parked `write` or `edit` card carries the target, the byte count, and a `sha256` digest of the exact call arguments. " +
133
+ "Press `v` to read the complete proposed content, or the complete effective diff against the file on disk, and `v` again to put it away; ↑/↓ and PageUp/PageDown scroll it. " +
134
+ "Enter still allows, `s` still stops, and Esc still denies while it is open. The mutation text is shown locally and never enters the transcript, a notice, a desktop notification, or the render trace. " +
135
+ "A worker escalation has no preview because the call's arguments never leave the worker, and its card says so.",
132
136
  ],
133
137
  },
134
138
  ];
@@ -22,13 +22,23 @@ export function openPromptsOverlay(tui: TUI, ctx: SlashCommandContext, onClose:
22
22
  lines.push(`**Argument Hint:** \`${template.argumentHint}\``);
23
23
  }
24
24
  lines.push(`**Source:** ${template.sourceInfo.source ?? template.sourceInfo.scope}`);
25
+ if (template.unavailable !== undefined) {
26
+ lines.push(`**Unavailable:** ${template.unavailable}`);
27
+ }
25
28
  if (!template.trusted) {
26
29
  lines.push("**Untrusted:** set `skills.trustProjectCompatRoots` before this template will expand.");
27
30
  }
28
31
  return lines;
29
32
  },
30
33
  };
31
- const marker = template.trusted ? undefined : clioTheme().fg("warning", "untrusted");
34
+ // A template that loaded in a refusing state is listed, because the
35
+ // operator's command does exist; the marker is what says it will not run.
36
+ const marker =
37
+ template.unavailable !== undefined
38
+ ? clioTheme().fg("error", "unavailable")
39
+ : template.trusted
40
+ ? undefined
41
+ : clioTheme().fg("warning", "untrusted");
32
42
  if (marker) item.meta = template.argumentHint ? `${template.argumentHint} · ${marker}` : marker;
33
43
  else if (template.argumentHint) item.meta = template.argumentHint;
34
44
  return item;
@@ -9,6 +9,7 @@ import {
9
9
  import { DEFAULT_SETTINGS, THINKING_LEVELS } from "../../core/defaults.js";
10
10
  import { getAtPath, isRoutingPath } from "../../core/session-routing.js";
11
11
  import { MAX_TIMER_DELAY_MS } from "../../core/timers.js";
12
+ import { capacityLeaseUsage } from "../../domains/dispatch/capacity-lease.js";
12
13
  import {
13
14
  delegationEntryForKind,
14
15
  type InteropAgentId,
@@ -17,6 +18,8 @@ import {
17
18
  } from "../../domains/interop/index.js";
18
19
  import {
19
20
  type CapabilityFlags,
21
+ endpointCapacitiesForStatuses,
22
+ endpointCapacityForStatus,
20
23
  isDispatchEligibleRuntime,
21
24
  isOrchestratorEligibleRuntime,
22
25
  type ProvidersContract,
@@ -225,7 +228,8 @@ type EntrySettingId =
225
228
  | `workers.profiles.${string}`
226
229
  | `workers.agentBindings.${string}`
227
230
  | `targets.${string}`
228
- | `fleet.nodes.${string}`;
231
+ | `fleet.nodes.${string}`
232
+ | `fleet.endpoints.${string}`;
229
233
  export type EditableSettingId = keyof typeof SETTINGS_LABELS_BY_ID | EntrySettingId;
230
234
  type FleetGroupHeaderId = `fleet.group.${"defaults" | "profiles" | "agent-routes" | "placement"}`;
231
235
  type TargetsCtaId = "targets.add-cta";
@@ -593,6 +597,13 @@ function delegationAgentsAffordance(
593
597
  type SubmenuTitle = string | ((width: number) => string);
594
598
 
595
599
  export class SubmenuWrapper implements Component {
600
+ /**
601
+ * Why the last submission was refused, or null while nothing is wrong. The
602
+ * editor sets it instead of closing, so the operator corrects the value with
603
+ * the reason on screen rather than back at the row list with no reason.
604
+ */
605
+ private problem: string | null = null;
606
+
596
607
  constructor(
597
608
  private readonly title: SubmenuTitle,
598
609
  private readonly child: Component,
@@ -600,6 +611,10 @@ export class SubmenuWrapper implements Component {
600
611
  private readonly note?: string,
601
612
  ) {}
602
613
 
614
+ setProblem(problem: string | null): void {
615
+ this.problem = problem;
616
+ }
617
+
603
618
  render(width: number): string[] {
604
619
  const theme = clioTheme();
605
620
  const lines: string[] = [];
@@ -619,6 +634,11 @@ export class SubmenuWrapper implements Component {
619
634
  // two columns off each one and marked the cut, so every entry wore a
620
635
  // trailing … it had not earned.
621
636
  lines.push(...this.child.render(Math.max(1, width - 2)).map((line) => ` ${line}`));
637
+ if (this.problem !== null) {
638
+ for (const line of wrapTextWithAnsi(this.problem, Math.max(1, width - 2))) {
639
+ lines.push(theme.fg("error", ` ${line}`));
640
+ }
641
+ }
622
642
  lines.push("");
623
643
  lines.push(theme.fg("dim", ` ${this.hint}`));
624
644
  return lines;
@@ -693,33 +713,108 @@ function editTextSubmenu(title: string, note?: string): SettingSubmenuBuilder {
693
713
  return textInputSubmenu(title, note);
694
714
  }
695
715
 
696
- function editNumberSubmenu(title: string, options: { min?: number; allowBlank?: boolean } = {}): SettingSubmenuBuilder {
697
- const min = options.min ?? 0;
716
+ /**
717
+ * The bound a number row enforces, shared by the editor and the apply path so
718
+ * the two cannot drift: whatever the editor forwards, the apply path stores.
719
+ * The bounds mirror the config validator's, so a value the Settings Center
720
+ * refuses is one settings.yaml would have refused too, in the same words.
721
+ */
722
+ interface NumberSettingRule {
723
+ readonly min: number;
724
+ readonly max?: number;
725
+ readonly integer: boolean;
726
+ /** A blank submission clears the key instead of being a parse failure. */
727
+ readonly allowBlank?: boolean;
728
+ }
729
+
730
+ const NUMBER_SETTING_RULES = {
731
+ "budget.sessionCeilingUsd": { min: 0, integer: false },
732
+ "watchdog.cadenceToolCalls": { min: 1, integer: true, allowBlank: true },
733
+ "delegation.defaults.connectTimeoutMs": { min: 1, max: MAX_TIMER_DELAY_MS, integer: true },
734
+ "delegation.defaults.turnTimeoutMs": { min: 1, max: MAX_TIMER_DELAY_MS, integer: true },
735
+ "delegation.defaults.permissionTimeoutMs": { min: 1, max: MAX_TIMER_DELAY_MS, integer: true },
736
+ } as const satisfies Partial<Record<EditableSettingId, NumberSettingRule>>;
737
+
738
+ export type NumberSettingId = keyof typeof NUMBER_SETTING_RULES;
739
+
740
+ export const NUMBER_SETTING_IDS = Object.keys(NUMBER_SETTING_RULES) as readonly NumberSettingId[];
741
+
742
+ type NumberSettingOutcome = { readonly value: number | null } | { readonly refusal: string };
743
+
744
+ function describeSubmittedText(value: string): string {
745
+ const trimmed = value.trim();
746
+ return trimmed.length === 0 ? "an empty string" : JSON.stringify(trimmed);
747
+ }
748
+
749
+ /**
750
+ * Parse one submitted number-row value under its rule. A null value means the
751
+ * operator cleared an optional key. The refusal is worded like the config
752
+ * validator's issue for the same key ("expected an integer >= 1, got 0"), with
753
+ * a "Not applied:" prefix so it reads as the outcome of this submission.
754
+ */
755
+ function parseNumberSetting(value: string, rule: NumberSettingRule): NumberSettingOutcome {
756
+ const trimmed = value.trim();
757
+ const noun = rule.integer ? "an integer" : "a number";
758
+ if (trimmed.length === 0 && rule.allowBlank === true) return { value: null };
759
+ const parsed = trimmed.length === 0 ? Number.NaN : Number(trimmed);
760
+ if (!Number.isFinite(parsed) || (rule.integer && !Number.isInteger(parsed))) {
761
+ const shown = Number.isFinite(parsed) ? String(parsed) : describeSubmittedText(value);
762
+ return { refusal: `Not applied: expected ${noun}, got ${shown}.` };
763
+ }
764
+ if (parsed < rule.min) return { refusal: `Not applied: expected ${noun} >= ${rule.min}, got ${parsed}.` };
765
+ if (rule.max !== undefined && parsed > rule.max) {
766
+ return { refusal: `Not applied: expected ${noun} <= ${rule.max}, got ${parsed}.` };
767
+ }
768
+ return { value: parsed };
769
+ }
770
+
771
+ /**
772
+ * The reason a number row would refuse `value`, or null when it would apply.
773
+ * This is the same check the editor and `applySettingChange` run, exposed so a
774
+ * caller that bypasses the editor can still name the bound it hit.
775
+ */
776
+ export function describeNumberSettingRefusal(id: NumberSettingId, value: string): string | null {
777
+ const outcome = parseNumberSetting(value, NUMBER_SETTING_RULES[id]);
778
+ return "refusal" in outcome ? outcome.refusal : null;
779
+ }
780
+
781
+ function numberSettingNote(rule: NumberSettingRule): string {
782
+ const noun = rule.integer ? "a whole number" : "a number";
783
+ const clears = rule.allowBlank === true ? "; blank clears it" : "";
784
+ if (rule.min > 0 && rule.max !== undefined) return `Use ${noun} from ${rule.min} to ${rule.max}${clears}.`;
785
+ if (rule.min > 0) return `Use ${noun} of at least ${rule.min}${clears}.`;
786
+ return `Use a non-negative ${rule.integer ? "whole number" : "number"}${clears}.`;
787
+ }
788
+
789
+ /**
790
+ * A number editor that stays open on a refused submission. Closing with no
791
+ * value looked like a successful edit that changed nothing, so the reason is
792
+ * rendered under the input and the operator corrects the text in place; Esc
793
+ * still leaves without applying.
794
+ */
795
+ function editNumberSubmenu(title: string, id: NumberSettingId): SettingSubmenuBuilder {
796
+ const rule = NUMBER_SETTING_RULES[id];
698
797
  return (currentValue: string, done: (val?: string) => void) => {
699
798
  const input = new Input();
700
799
  input.setValue(currentValue);
701
800
  input.focused = true;
801
+ const wrapper = new SubmenuWrapper(
802
+ title,
803
+ input,
804
+ buildHint([{ key: "Enter", verb: "confirm" }], "back"),
805
+ numberSettingNote(rule),
806
+ );
702
807
  input.onSubmit = (val) => {
703
- if (options.allowBlank === true && val.trim().length === 0) {
704
- done("");
808
+ const outcome = parseNumberSetting(val, rule);
809
+ if ("refusal" in outcome) {
810
+ wrapper.setProblem(outcome.refusal);
705
811
  return;
706
812
  }
707
- const num = Number(val);
708
- if (Number.isFinite(num) && num >= min) {
709
- done(val);
710
- } else {
711
- done();
712
- }
813
+ wrapper.setProblem(null);
814
+ done(outcome.value === null ? "" : val.trim());
713
815
  };
714
816
  input.onEscape = () => done();
715
- return new SubmenuWrapper(
716
- title,
717
- input,
718
- buildHint([{ key: "Enter", verb: "confirm" }], "back"),
719
- min > 0
720
- ? `Use a whole number of at least ${min}${options.allowBlank === true ? "; blank clears it" : ""}.`
721
- : "Use a non-negative number.",
722
- );
817
+ return wrapper;
723
818
  };
724
819
  }
725
820
 
@@ -1391,6 +1486,7 @@ export function buildSettingItems(
1391
1486
  }),
1392
1487
  fleetGroupHeader("fleet.group.placement", "Placement"),
1393
1488
  ...fleetNodeRows(options?.getFleetNodes?.() ?? []),
1489
+ ...fleetEndpointRows(options?.providers),
1394
1490
  settingItem("targets", "", {
1395
1491
  description: "Live inference target inventory and routing roles.",
1396
1492
  affordance: "column heading",
@@ -1412,7 +1508,7 @@ export function buildSettingItems(
1412
1508
  readOnly: true,
1413
1509
  }),
1414
1510
  settingItem("budget.sessionCeilingUsd", String(settings.budget.sessionCeilingUsd), {
1415
- submenu: editNumberSubmenu("Edit session cost ceiling USD"),
1511
+ submenu: editNumberSubmenu("Edit session cost ceiling USD", "budget.sessionCeilingUsd"),
1416
1512
  affordance: "free text",
1417
1513
  }),
1418
1514
  settingItem("defaults.maxTokens", String(settings.defaults.maxTokens), {
@@ -1479,10 +1575,10 @@ export function buildSettingItems(
1479
1575
  "watchdog.cadenceToolCalls",
1480
1576
  watchdog.cadenceToolCalls === undefined ? "(turn end only)" : String(watchdog.cadenceToolCalls),
1481
1577
  {
1482
- submenu: editNumberSubmenu("Edit watchdog cadence in tool calls; blank fires at turn end only", {
1483
- min: 1,
1484
- allowBlank: true,
1485
- }),
1578
+ submenu: editNumberSubmenu(
1579
+ "Edit watchdog cadence in tool calls; blank fires at turn end only",
1580
+ "watchdog.cadenceToolCalls",
1581
+ ),
1486
1582
  affordance: "free text",
1487
1583
  editValue: watchdog.cadenceToolCalls === undefined ? "" : String(watchdog.cadenceToolCalls),
1488
1584
  },
@@ -1503,15 +1599,15 @@ export function buildSettingItems(
1503
1599
  affordance: "free text",
1504
1600
  }),
1505
1601
  settingItem("delegation.defaults.connectTimeoutMs", String(settings.delegation.defaults.connectTimeoutMs), {
1506
- submenu: editNumberSubmenu("Edit delegate connect timeout (ms)"),
1602
+ submenu: editNumberSubmenu("Edit delegate connect timeout (ms)", "delegation.defaults.connectTimeoutMs"),
1507
1603
  affordance: "free text",
1508
1604
  }),
1509
1605
  settingItem("delegation.defaults.turnTimeoutMs", String(settings.delegation.defaults.turnTimeoutMs), {
1510
- submenu: editNumberSubmenu("Edit delegate turn timeout (ms)"),
1606
+ submenu: editNumberSubmenu("Edit delegate turn timeout (ms)", "delegation.defaults.turnTimeoutMs"),
1511
1607
  affordance: "free text",
1512
1608
  }),
1513
1609
  settingItem("delegation.defaults.permissionTimeoutMs", String(settings.delegation.defaults.permissionTimeoutMs), {
1514
- submenu: editNumberSubmenu("Edit delegate permission timeout (ms)"),
1610
+ submenu: editNumberSubmenu("Edit delegate permission timeout (ms)", "delegation.defaults.permissionTimeoutMs"),
1515
1611
  affordance: "free text",
1516
1612
  }),
1517
1613
  settingItem("keybindings", keybindingCount > 0 ? `${keybindingCount} override(s)` : "(defaults)", {
@@ -1769,6 +1865,35 @@ function fleetNodeRows(nodes: ReadonlyArray<FleetNodeSnapshot>): SettingsCenterI
1769
1865
  });
1770
1866
  }
1771
1867
 
1868
+ /** Read-only endpoint rows show the independent inference scheduler dimension. */
1869
+ function fleetEndpointRows(providers: ProvidersContract | undefined): SettingsCenterItem[] {
1870
+ if (providers === undefined) return [];
1871
+ let usage: Readonly<Record<string, number>> = {};
1872
+ try {
1873
+ usage = capacityLeaseUsage().endpoints;
1874
+ } catch {
1875
+ // A damaged capacity file belongs to admission. Settings still shows the configured limits.
1876
+ }
1877
+ const capacities = endpointCapacitiesForStatuses(providers.list());
1878
+ const targetsByEndpoint = new Map<string, string[]>();
1879
+ for (const status of providers.list()) {
1880
+ const endpoint = endpointCapacityForStatus(status);
1881
+ if (endpoint === null) continue;
1882
+ targetsByEndpoint.set(endpoint.key, [...(targetsByEndpoint.get(endpoint.key) ?? []), status.target.id]);
1883
+ }
1884
+ return Object.entries(capacities).map(([key, endpoint]) => {
1885
+ const slots = `slots ${usage[key] ?? 0}/${endpoint.limit}`;
1886
+ return settingItem(`fleet.endpoints.${key}`, slots, {
1887
+ label: `endpoint ${endpoint.label}`,
1888
+ description: `Targets: ${(targetsByEndpoint.get(key) ?? []).join(", ")} · Limit source: ${endpoint.source}`,
1889
+ affordance: "read-only inference endpoint capacity",
1890
+ readOnly: true,
1891
+ presentationKind: "status",
1892
+ valueSegments: [{ text: slots, tone: "neutral" }],
1893
+ });
1894
+ });
1895
+ }
1896
+
1772
1897
  export function buildSettingsSections(items: readonly SettingsCenterItem[]): SettingsCenterSection[] {
1773
1898
  return SETTINGS_SECTIONS.map((section) => ({
1774
1899
  id: section.id,
@@ -1819,9 +1944,15 @@ function applyNonNegativeInteger(value: string, set: (next: number) => void): vo
1819
1944
  if (Number.isFinite(parsed) && parsed >= 0) set(Math.floor(parsed));
1820
1945
  }
1821
1946
 
1822
- function applyPositiveInteger(value: string, set: (next: number) => void): void {
1823
- const parsed = Number(value);
1824
- if (Number.isInteger(parsed) && parsed >= 1 && parsed <= MAX_TIMER_DELAY_MS) set(parsed);
1947
+ /**
1948
+ * Store a number-row submission under the row's shared rule. A refused value
1949
+ * leaves the setting alone; the editor has already shown the operator why, and
1950
+ * `describeNumberSettingRefusal` names the reason for any other caller.
1951
+ */
1952
+ function applyNumberSetting(id: NumberSettingId, value: string, set: (next: number | null) => void): void {
1953
+ const outcome = parseNumberSetting(value, NUMBER_SETTING_RULES[id]);
1954
+ if ("refusal" in outcome) return;
1955
+ set(outcome.value);
1825
1956
  }
1826
1957
 
1827
1958
  /**
@@ -1986,15 +2117,12 @@ export function applySettingChange(settings: ClioSettings, id: string, value: st
1986
2117
  else delete settings.watchdog.target;
1987
2118
  return;
1988
2119
  }
1989
- case "watchdog.cadenceToolCalls": {
1990
- if (value.trim().length === 0) {
1991
- delete settings.watchdog.cadenceToolCalls;
1992
- return;
1993
- }
1994
- const parsed = Number(value);
1995
- if (Number.isFinite(parsed) && Math.floor(parsed) >= 1) settings.watchdog.cadenceToolCalls = Math.floor(parsed);
2120
+ case "watchdog.cadenceToolCalls":
2121
+ applyNumberSetting("watchdog.cadenceToolCalls", value, (next) => {
2122
+ if (next === null) delete settings.watchdog.cadenceToolCalls;
2123
+ else settings.watchdog.cadenceToolCalls = next;
2124
+ });
1996
2125
  return;
1997
- }
1998
2126
  case "terminal.smoothStreaming":
1999
2127
  if (value === "off" || value === "auto" || value === "on") settings.terminal.smoothStreaming = value;
2000
2128
  return;
@@ -2005,18 +2133,18 @@ export function applySettingChange(settings: ClioSettings, id: string, value: st
2005
2133
  .filter(Boolean);
2006
2134
  return;
2007
2135
  case "delegation.defaults.connectTimeoutMs":
2008
- applyPositiveInteger(value, (next) => {
2009
- settings.delegation.defaults.connectTimeoutMs = next;
2136
+ applyNumberSetting("delegation.defaults.connectTimeoutMs", value, (next) => {
2137
+ if (next !== null) settings.delegation.defaults.connectTimeoutMs = next;
2010
2138
  });
2011
2139
  return;
2012
2140
  case "delegation.defaults.turnTimeoutMs":
2013
- applyPositiveInteger(value, (next) => {
2014
- settings.delegation.defaults.turnTimeoutMs = next;
2141
+ applyNumberSetting("delegation.defaults.turnTimeoutMs", value, (next) => {
2142
+ if (next !== null) settings.delegation.defaults.turnTimeoutMs = next;
2015
2143
  });
2016
2144
  return;
2017
2145
  case "delegation.defaults.permissionTimeoutMs":
2018
- applyPositiveInteger(value, (next) => {
2019
- settings.delegation.defaults.permissionTimeoutMs = next;
2146
+ applyNumberSetting("delegation.defaults.permissionTimeoutMs", value, (next) => {
2147
+ if (next !== null) settings.delegation.defaults.permissionTimeoutMs = next;
2020
2148
  });
2021
2149
  return;
2022
2150
  case "orchestrator.target": {
@@ -2067,11 +2195,11 @@ export function applySettingChange(settings: ClioSettings, id: string, value: st
2067
2195
  .map((v) => v.trim())
2068
2196
  .filter(Boolean);
2069
2197
  return;
2070
- case "budget.sessionCeilingUsd": {
2071
- const parsed = Number(value);
2072
- if (Number.isFinite(parsed) && parsed >= 0) settings.budget.sessionCeilingUsd = parsed;
2198
+ case "budget.sessionCeilingUsd":
2199
+ applyNumberSetting("budget.sessionCeilingUsd", value, (next) => {
2200
+ if (next !== null) settings.budget.sessionCeilingUsd = next;
2201
+ });
2073
2202
  return;
2074
- }
2075
2203
  }
2076
2204
  }
2077
2205