@iowarp/clio-coder 0.3.9 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (416) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/NOTICE +33 -0
  3. package/README.md +7 -2
  4. package/dist/{acp-7LOELQFP.js → acp-G5WJBNCT.js} +13 -12
  5. package/dist/{agents-FIBG2SHA.js → agents-FMV2Q5G4.js} +39 -36
  6. package/dist/assets/codewiki.json +1 -1
  7. package/dist/{auth-OI4LIH2I.js → auth-3IDSJEIK.js} +16 -15
  8. package/dist/{builtins-AD25UL3C.js → builtins-XCZWXSC7.js} +3 -3
  9. package/dist/{chunk-IFBNV6H6.js → chunk-2ANTL7MR.js} +3 -3
  10. package/dist/{chunk-56KB5IJP.js → chunk-2OQE55CK.js} +3 -3
  11. package/dist/{chunk-N3PBVRTZ.js → chunk-32KWKNSF.js} +9 -7
  12. package/dist/{chunk-VKBMFOYV.js → chunk-36EJLSQQ.js} +23 -27
  13. package/dist/{chunk-465CC7FK.js → chunk-3BT2XMV4.js} +16 -13
  14. package/dist/{chunk-NQ6UCCOD.js → chunk-3URVFKWK.js} +7 -7
  15. package/dist/{chunk-WIE7ZOSW.js → chunk-3XML7CDN.js} +2 -2
  16. package/dist/chunk-42FMPA75.js +101 -0
  17. package/dist/{chunk-A2NJGIB3.js → chunk-5LXZXPKX.js} +2 -2
  18. package/dist/{chunk-HAY4ZE2P.js → chunk-5PSMVOLM.js} +2 -2
  19. package/dist/{chunk-JEQQR47K.js → chunk-6DB53AJS.js} +168 -9
  20. package/dist/{chunk-KV2AOLDF.js → chunk-76ONBSIA.js} +5 -25
  21. package/dist/{chunk-FALJGAWU.js → chunk-7MCTRUCE.js} +2 -2
  22. package/dist/{chunk-5T7RBWN2.js → chunk-AB44T6BB.js} +5 -5
  23. package/dist/chunk-B7OBL7PK.js +1405 -0
  24. package/dist/{chunk-B5CSFE7B.js → chunk-BBVJUZHB.js} +2 -2
  25. package/dist/{chunk-EQ63NRB7.js → chunk-BBVYXMFO.js} +2 -2
  26. package/dist/{chunk-O6I4CIEU.js → chunk-BKFJHQCA.js} +6 -6
  27. package/dist/chunk-BKFM6EJV.js +462 -0
  28. package/dist/chunk-BMS5RKQY.js +27 -0
  29. package/dist/{chunk-MV2VUEJC.js → chunk-BPKCPIL7.js} +2 -2
  30. package/dist/{chunk-TEO2TLVN.js → chunk-BUMFYQFY.js} +964 -1120
  31. package/dist/{chunk-BNAZZHFG.js → chunk-BYP5D4HI.js} +1 -1
  32. package/dist/chunk-C2LTL2W6.js +2447 -0
  33. package/dist/{chunk-EBOC7MT3.js → chunk-CODPRO7Q.js} +8 -8
  34. package/dist/{chunk-HPCTNZM2.js → chunk-CTJ4RNAA.js} +4 -4
  35. package/dist/{chunk-ODFEOB4F.js → chunk-CY6FY24N.js} +26 -8
  36. package/dist/{chunk-MIX5N5AC.js → chunk-DQITNCXG.js} +394 -154
  37. package/dist/{chunk-S4COXYBG.js → chunk-DYJP44XW.js} +129 -106
  38. package/dist/{chunk-N5XKWMDW.js → chunk-F4CKPOEQ.js} +6 -6
  39. package/dist/{chunk-AMKHQW3C.js → chunk-FEFIFZTL.js} +3 -3
  40. package/dist/{chunk-MV3K5QF2.js → chunk-GCSMB2KY.js} +2 -2
  41. package/dist/{chunk-7C6RYZGQ.js → chunk-GKF55TAZ.js} +3 -3
  42. package/dist/{chunk-B5XRQOLB.js → chunk-GR5G2PVF.js} +9 -8
  43. package/dist/{chunk-XDOQXGFO.js → chunk-GXNLGKAB.js} +64 -8
  44. package/dist/chunk-HHV2GANA.js +88 -0
  45. package/dist/{chunk-774ILSRL.js → chunk-HI63TFOG.js} +4 -3
  46. package/dist/{chunk-R7LNVMCS.js → chunk-HJJTYUHX.js} +64 -72
  47. package/dist/{chunk-RKRLDWD3.js → chunk-HLW2MRKE.js} +1 -1
  48. package/dist/{chunk-CA42X6KT.js → chunk-HWHKMHUA.js} +6 -6
  49. package/dist/chunk-HZHHCK24.js +1631 -0
  50. package/dist/{chunk-HJB5IUKP.js → chunk-I5VEOC6I.js} +12 -12
  51. package/dist/chunk-IBEBSCYA.js +564 -0
  52. package/dist/chunk-IQ7KR472.js +362 -0
  53. package/dist/{chunk-4H6ULJ3H.js → chunk-J4W7KFM7.js} +921 -990
  54. package/dist/{chunk-4LJX2PUC.js → chunk-JDG2WCRO.js} +5 -5
  55. package/dist/{chunk-NZU6YDNV.js → chunk-K5C3NCBD.js} +4 -4
  56. package/dist/{chunk-CGKSTWHD.js → chunk-K6BSR66V.js} +2 -1
  57. package/dist/{chunk-NNNWO6F2.js → chunk-KFZI4NIL.js} +93 -3
  58. package/dist/chunk-KMVISBZR.js +132 -0
  59. package/dist/{chunk-BVDVID7E.js → chunk-LDQ2ZF2M.js} +2 -2
  60. package/dist/{chunk-OEDBCISO.js → chunk-LQ3DZAMX.js} +2 -2
  61. package/dist/{chunk-PZ4I4JE2.js → chunk-LY4S7GJC.js} +124 -116
  62. package/dist/{chunk-3DUR4WUA.js → chunk-MLKNTWH2.js} +19 -19
  63. package/dist/chunk-NEKRRTYW.js +56 -0
  64. package/dist/{chunk-5DQRIYDZ.js → chunk-NHCZP4K7.js} +2 -2
  65. package/dist/chunk-NHLBIGRH.js +1506 -0
  66. package/dist/chunk-NQQH3YT7.js +302 -0
  67. package/dist/chunk-NYS75XW5.js +15 -0
  68. package/dist/{chunk-TD7UE2L5.js → chunk-O4XIVISU.js} +7 -7
  69. package/dist/{chunk-WXCJ7VME.js → chunk-O6TL7WWY.js} +6 -6
  70. package/dist/chunk-OQBA45DZ.js +97 -0
  71. package/dist/{chunk-LU7P4LHA.js → chunk-P3FOHJT4.js} +2 -2
  72. package/dist/chunk-PMZCIOCJ.js +25 -0
  73. package/dist/{chunk-I4HZDVNP.js → chunk-PQEFIJ36.js} +2 -2
  74. package/dist/{chunk-HKO36JWF.js → chunk-QBJA7R7N.js} +31 -3
  75. package/dist/chunk-QDC3K2U3.js +262 -0
  76. package/dist/{chunk-5HFBWUMU.js → chunk-QLFS5GO2.js} +4 -4
  77. package/dist/{chunk-GAYUJ7LE.js → chunk-QLL7ILRG.js} +41 -32
  78. package/dist/{chunk-LXPJXFM5.js → chunk-QREDIESB.js} +7 -7
  79. package/dist/chunk-QSNYB6ZV.js +195 -0
  80. package/dist/chunk-R346GLFC.js +31 -0
  81. package/dist/{chunk-VPTUJU4P.js → chunk-SJ5ZKQ4S.js} +2 -2
  82. package/dist/{chunk-QQ7EKM72.js → chunk-SP2RXXYO.js} +6 -54
  83. package/dist/chunk-SUCTJL45.js +45 -0
  84. package/dist/{chunk-5QKCQQ3E.js → chunk-SUW5DORT.js} +2 -2
  85. package/dist/chunk-T56WDKA5.js +183 -0
  86. package/dist/chunk-TVHHYFHE.js +255 -0
  87. package/dist/{chunk-IHKBWSXF.js → chunk-TZ3SGWZZ.js} +2 -2
  88. package/dist/{chunk-ECUO3KDP.js → chunk-U77AMWDL.js} +47 -7
  89. package/dist/{chunk-3UUY7R3Z.js → chunk-ULC6OTWO.js} +7 -7
  90. package/dist/{chunk-AEYBF3TB.js → chunk-UM7N4G5A.js} +2 -2
  91. package/dist/{chunk-3V5AYSEQ.js → chunk-UXMFQ54G.js} +43 -36
  92. package/dist/{chunk-YW7UVM5V.js → chunk-V5DHCITQ.js} +34 -1
  93. package/dist/{chunk-47CMYGET.js → chunk-VAZSBTKF.js} +2 -2
  94. package/dist/{chunk-3MRC2YSQ.js → chunk-VEO4AP2K.js} +2 -2
  95. package/dist/{chunk-PNY46YEY.js → chunk-VFA6GDY5.js} +47 -3
  96. package/dist/chunk-VO67MWHC.js +75 -0
  97. package/dist/{chunk-XK56QHLX.js → chunk-VPKWYKEY.js} +19 -5
  98. package/dist/{chunk-AD7Y7STJ.js → chunk-VYMXRQI6.js} +36 -22
  99. package/dist/{chunk-T3Z6VAAF.js → chunk-W5VSYASO.js} +19 -17
  100. package/dist/{chunk-5PVQ4SRS.js → chunk-WZR7K7ZX.js} +7 -123
  101. package/dist/{chunk-DG4M6ZUE.js → chunk-X3YGUTOB.js} +4 -4
  102. package/dist/chunk-X75E3D2N.js +686 -0
  103. package/dist/{chunk-P3JGPQFL.js → chunk-YDFRH54B.js} +4 -4
  104. package/dist/chunk-YJX4SHTD.js +40 -0
  105. package/dist/{chunk-ZI647VB5.js → chunk-YPI3QQCF.js} +2 -2
  106. package/dist/{chunk-YSEHGPCT.js → chunk-Z2RR6MAK.js} +3 -3
  107. package/dist/{chunk-HCBCAYZU.js → chunk-Z4TXYIEG.js} +5 -5
  108. package/dist/cli/index.js +47 -36
  109. package/dist/{clio-LT5V7SSZ.js → clio-2JXHBBY5.js} +7 -7
  110. package/dist/{code-nav-LMW275PA.js → code-nav-3YYRMYNF.js} +6 -6
  111. package/dist/{compile-cache-CVJMMODC.js → compile-cache-7FPE6PS3.js} +3 -3
  112. package/dist/{components-ZFA3SAER.js → components-RYZV4JGP.js} +5 -5
  113. package/dist/{config-RXS5T3JT.js → config-QZPCMYSO.js} +58 -50
  114. package/dist/{configure-2WYWSCSD.js → configure-TEGEBYCA.js} +21 -19
  115. package/dist/{context-I3BTOTCS.js → context-AV7OEZ4D.js} +11 -11
  116. package/dist/{context-MVOORGMF.js → context-E6H5RNMC.js} +49 -41
  117. package/dist/{context-PALKKQYL.js → context-GSXUE4CT.js} +25 -23
  118. package/dist/{context-clear-N2WOYZ2K.js → context-clear-SHIBYK6T.js} +48 -40
  119. package/dist/{context-working-set-MIEVECVZ.js → context-working-set-5ZGKPGZQ.js} +11 -10
  120. package/dist/{dispatch-runner-VVA4SRRH.js → dispatch-runner-EFMJT4LD.js} +88 -60
  121. package/dist/{docs-7LQ23DLM.js → docs-23KQS3XK.js} +5 -5
  122. package/dist/doctor-QOA5FNY5.js +313 -0
  123. package/dist/{eval-IJ5VEZDJ.js → eval-TFBYQH4H.js} +66 -64
  124. package/dist/eval-inventory-SXH7PDKX.js +316 -0
  125. package/dist/{evidence-L5APPXNV.js → evidence-ERGESKGN.js} +198 -44
  126. package/dist/{evolve-RGNKFJ52.js → evolve-VDXTSYCJ.js} +46 -38
  127. package/dist/{extensions-7WYWUX5A.js → extensions-7BGBHN57.js} +6 -6
  128. package/dist/{fleet-6CNVBZZP.js → fleet-2RRVDF2V.js} +189 -101
  129. package/dist/{fleet-commands-L2SXSYEI.js → fleet-commands-VJ726XIA.js} +10 -10
  130. package/dist/fleet-decisions-EPAPM3XJ.js +157 -0
  131. package/dist/{fleet-graph-2J3OOIPO.js → fleet-graph-JF5QOATM.js} +16 -15
  132. package/dist/fleet-inspect-VLY4S7QM.js +442 -0
  133. package/dist/{fleet-preflight-CZRJ4JP5.js → fleet-preflight-AIZUEJOY.js} +6 -5
  134. package/dist/{fleet-validate-C5RI6DP7.js → fleet-validate-AJRPDMDV.js} +20 -18
  135. package/dist/fleet-verify-JFEL2L3H.js +175 -0
  136. package/dist/fleet-view-ZCON35AG.js +102 -0
  137. package/dist/{init-VBN2ACVA.js → init-DN2WWLFE.js} +64 -56
  138. package/dist/install-XGLBQY5E.js +13 -0
  139. package/dist/interop-OZBKXAYL.js +114 -0
  140. package/dist/{library-JHGUMLY2.js → library-YWZG7IMW.js} +19 -18
  141. package/dist/{memory-K4OQIYWG.js → memory-I4C4HMLW.js} +48 -40
  142. package/dist/{models-2NCZUWDD.js → models-CEYXJBO6.js} +30 -28
  143. package/dist/{monitor-MMVTJABD.js → monitor-NZ6GCI3P.js} +53 -47
  144. package/dist/{orchestrator-ZKBPCHW6.js → orchestrator-GCGQ4N5I.js} +6311 -7145
  145. package/dist/panes-HMABYVO4.js +58 -0
  146. package/dist/panes-KY6W3V2E.js +103 -0
  147. package/dist/{paths-DBXMZMDU.js → paths-II4K7DNR.js} +5 -5
  148. package/dist/{reset-DD5JGOY3.js → reset-DQ6FGCSH.js} +13 -11
  149. package/dist/resources-BB3MVJMD.js +111 -0
  150. package/dist/{run-QEGNX7FL.js → run-H2GQDUER.js} +113 -82
  151. package/dist/{share-JKD3BQMW.js → share-GTJN6A5O.js} +18 -17
  152. package/dist/{skills-LMQIKDOZ.js → skills-L55TEW6R.js} +31 -24
  153. package/dist/{skills-eval-I7X2774U.js → skills-eval-XVXPH2JI.js} +61 -52
  154. package/dist/skills-inventory-S4MXPJFV.js +126 -0
  155. package/dist/slash-commands-ZSGASKJC.js +77 -0
  156. package/dist/{steer-CF5TDANS.js → steer-RZGSCY4R.js} +3 -3
  157. package/dist/{support-I7LOJLIF.js → support-PKEUNNQL.js} +4 -4
  158. package/dist/{targets-RUSR6B5Z.js → targets-NCPZ644J.js} +31 -29
  159. package/dist/{terminal-lease-QYVORFR4.js → terminal-lease-44SV3YCN.js} +4 -4
  160. package/dist/tools-DAF3DI3C.js +27 -0
  161. package/dist/{trace-ODOQIVIW.js → trace-FYVW2MQA.js} +149 -7
  162. package/dist/tui-primitives-2AKXQNZK.js +13 -0
  163. package/dist/{uninstall-ZJF5H5ZN.js → uninstall-DW2PNOIC.js} +5 -5
  164. package/dist/{upgrade-XANW3FXB.js → upgrade-3XPP6OQL.js} +26 -24
  165. package/dist/{usage-4H7ZRXQT.js → usage-3NLHGTU2.js} +57 -49
  166. package/dist/{verifiers-UZXNBZEB.js → verifiers-SSQONKRT.js} +171 -12
  167. package/dist/{verify-BVKWTNDL.js → verify-3U6J7FZI.js} +9 -9
  168. package/dist/{web-fetch-2YHJ3KTG.js → web-fetch-S7RR6GZ7.js} +3 -3
  169. package/dist/{wiki-generate-MY7WV2QI.js → wiki-generate-CEHYGPGQ.js} +67 -59
  170. package/dist/with-panes-MKB46MPQ.js +782 -0
  171. package/dist/worker/entry.js +101 -66
  172. package/docs/README.md +3 -2
  173. package/docs/acp.md +24 -3
  174. package/docs/alcf-provider.md +1 -1
  175. package/docs/architecture.md +2 -2
  176. package/docs/artifact-versions.md +4 -1
  177. package/docs/built-in-agents.md +1 -1
  178. package/docs/capacity-and-scheduling.md +43 -5
  179. package/docs/commands-and-modes.md +31 -2
  180. package/docs/configuration-and-targets.md +40 -10
  181. package/docs/context-engine.md +1 -1
  182. package/docs/development-pipeline.md +19 -0
  183. package/docs/dispatch-typed-intent.md +385 -0
  184. package/docs/documentation-coverage.md +2 -2
  185. package/docs/documentation-guide.md +1 -1
  186. package/docs/environment-variables.md +1 -0
  187. package/docs/eval-runner.md +1 -1
  188. package/docs/evals-internal.md +1 -1
  189. package/docs/evidence-and-memory.md +2 -2
  190. package/docs/evolution.md +1 -1
  191. package/docs/exit-codes-and-output.md +1 -1
  192. package/docs/extensions-and-sharing.md +25 -1
  193. package/docs/fleet-dispatch.md +22 -1
  194. package/docs/installation-and-lifecycle.md +15 -2
  195. package/docs/middleware-and-components.md +1 -1
  196. package/docs/model-catalog.md +10 -1
  197. package/docs/observability.md +3 -3
  198. package/docs/proactive-memory.md +1 -1
  199. package/docs/prompt-envelope-and-tools.md +1 -1
  200. package/docs/provider-adapter-cookbook.md +1 -1
  201. package/docs/safety-model.md +2 -2
  202. package/docs/scientific-validation.md +3 -3
  203. package/docs/session-lifecycle.md +1 -1
  204. package/docs/skills-marketplace.md +1 -1
  205. package/docs/tool-usage.md +18 -8
  206. package/docs/trace-store.md +1 -1
  207. package/docs/troubleshooting.md +1 -1
  208. package/docs/tui-design.md +1 -1
  209. package/docs/worker-dispatch-mechanics.md +1 -1
  210. package/package.json +4 -2
  211. package/src/cli/acp.ts +6 -2
  212. package/src/cli/argv.ts +25 -0
  213. package/src/cli/configure.ts +23 -21
  214. package/src/cli/doctor-panes.ts +124 -0
  215. package/src/cli/doctor-toolchain.ts +57 -0
  216. package/src/cli/doctor.ts +20 -1
  217. package/src/cli/eval-inventory.ts +436 -0
  218. package/src/cli/eval.ts +13 -0
  219. package/src/cli/evidence-detail.ts +88 -0
  220. package/src/cli/evidence-inventory.ts +183 -0
  221. package/src/cli/evidence.ts +30 -5
  222. package/src/cli/fleet-decisions.ts +69 -0
  223. package/src/cli/fleet-inspect.ts +334 -0
  224. package/src/cli/fleet-verify.ts +133 -0
  225. package/src/cli/fleet-view.ts +810 -0
  226. package/src/cli/fleet.ts +148 -37
  227. package/src/cli/index.ts +14 -2
  228. package/src/cli/interop-inspect.ts +128 -0
  229. package/src/cli/interop.ts +34 -0
  230. package/src/cli/panes.ts +35 -0
  231. package/src/cli/reset.ts +5 -2
  232. package/src/cli/run.ts +58 -0
  233. package/src/cli/skills-inventory.ts +185 -0
  234. package/src/cli/skills.ts +16 -13
  235. package/src/cli/tools.ts +321 -0
  236. package/src/cli/trace-inspect.ts +252 -0
  237. package/src/cli/trace.ts +23 -2
  238. package/src/cli/verifiers-inspect.ts +347 -0
  239. package/src/cli/verifiers.ts +9 -0
  240. package/src/core/bus-events.ts +4 -0
  241. package/src/core/config.ts +49 -0
  242. package/src/core/defaults.ts +93 -7
  243. package/src/core/response-schema.ts +44 -4
  244. package/src/core/theme-token-hex.ts +43 -0
  245. package/src/core/tool-names.ts +2 -1
  246. package/src/core/xdg.ts +1 -1
  247. package/src/domains/agents/fleets/build-review.md +0 -3
  248. package/src/domains/agents/fleets/build-test.md +0 -3
  249. package/src/domains/agents/result-contract-filesystem.ts +32 -0
  250. package/src/domains/agents/result-contract.ts +164 -35
  251. package/src/domains/config/classify.ts +3 -0
  252. package/src/domains/dispatch/admission-error.ts +9 -0
  253. package/src/domains/dispatch/admission.ts +22 -21
  254. package/src/domains/dispatch/capacity-lease.ts +21 -1
  255. package/src/domains/dispatch/council-topology.ts +398 -0
  256. package/src/domains/dispatch/extension.ts +222 -50
  257. package/src/domains/dispatch/fleet-node-prompt.ts +62 -0
  258. package/src/domains/dispatch/fleet-plan.ts +7 -2
  259. package/src/domains/dispatch/fleet-run.ts +64 -1
  260. package/src/domains/dispatch/gate-decisions.ts +11 -1
  261. package/src/domains/dispatch/gate-role-prompts.ts +9 -0
  262. package/src/domains/dispatch/gate-topology.ts +289 -0
  263. package/src/domains/dispatch/index.ts +19 -0
  264. package/src/domains/dispatch/intent-compatibility.ts +330 -0
  265. package/src/domains/dispatch/intent.ts +85 -1
  266. package/src/domains/dispatch/reservation-store.ts +24 -4
  267. package/src/domains/dispatch/run-event-journal-bridge.ts +149 -0
  268. package/src/domains/dispatch/run-event-journal.ts +598 -0
  269. package/src/domains/dispatch/state.ts +45 -1
  270. package/src/domains/dispatch/types.ts +13 -0
  271. package/src/domains/dispatch/validation.ts +33 -8
  272. package/src/domains/eval/compare/compare.ts +3 -16
  273. package/src/domains/eval/inventory.ts +113 -0
  274. package/src/domains/eval/schema/serving.ts +31 -0
  275. package/src/domains/evidence/store.ts +6 -0
  276. package/src/domains/memory/index.ts +0 -1
  277. package/src/domains/memory/task-memory-policy.ts +6 -10
  278. package/src/domains/memory/task-memory-telemetry.ts +1 -0
  279. package/src/domains/middleware/memory-intervention.ts +33 -21
  280. package/src/domains/mux/contract.ts +434 -0
  281. package/src/domains/mux/detect.ts +158 -0
  282. package/src/domains/mux/extension.ts +47 -0
  283. package/src/domains/mux/index.ts +96 -0
  284. package/src/domains/mux/manifest.ts +6 -0
  285. package/src/domains/mux/operations.ts +164 -0
  286. package/src/domains/mux/pane-registry.ts +90 -0
  287. package/src/domains/mux/protocol.ts +49 -0
  288. package/src/domains/mux/socket-client.ts +816 -0
  289. package/src/domains/mux/types.ts +222 -0
  290. package/src/domains/mux/viewer-command.ts +59 -0
  291. package/src/domains/mux/yazi/assets/init.lua +2 -0
  292. package/src/domains/mux/yazi/assets/plugins/git.yazi/LICENSE +21 -0
  293. package/src/domains/mux/yazi/assets/plugins/git.yazi/README.md +78 -0
  294. package/src/domains/mux/yazi/assets/plugins/git.yazi/main.lua +255 -0
  295. package/src/domains/mux/yazi/assets/plugins/git.yazi/types.lua +12 -0
  296. package/src/domains/mux/yazi/assets/yazi.toml +17 -0
  297. package/src/domains/mux/yazi/event-stream.ts +180 -0
  298. package/src/domains/mux/yazi/profile.ts +299 -0
  299. package/src/domains/mux/yazi/session.ts +228 -0
  300. package/src/domains/mux/yazi/theme.ts +30 -0
  301. package/src/domains/observability/cost.ts +21 -0
  302. package/src/domains/observability/index.ts +2 -0
  303. package/src/domains/observability/trace-store.ts +42 -0
  304. package/src/domains/providers/endpoint-capacity.ts +154 -22
  305. package/src/domains/providers/endpoint-slots-store.ts +189 -0
  306. package/src/domains/providers/extension.ts +20 -3
  307. package/src/domains/providers/index.ts +22 -0
  308. package/src/domains/providers/model-runtime-capabilities.ts +32 -0
  309. package/src/domains/providers/runtimes/boot-manifest.ts +1 -0
  310. package/src/domains/providers/runtimes/builtins.ts +2 -0
  311. package/src/domains/providers/runtimes/protocol/litellm.ts +375 -0
  312. package/src/domains/providers/support.ts +1 -0
  313. package/src/domains/providers/target-model-cache.ts +124 -0
  314. package/src/domains/resources/index.ts +3 -0
  315. package/src/domains/resources/skills/loader.ts +33 -0
  316. package/src/domains/safety/action-classifier.ts +6 -0
  317. package/src/domains/toolchain/archive.ts +175 -0
  318. package/src/domains/toolchain/contract.ts +28 -0
  319. package/src/domains/toolchain/extension.ts +47 -0
  320. package/src/domains/toolchain/index.ts +39 -0
  321. package/src/domains/toolchain/install.ts +327 -0
  322. package/src/domains/toolchain/manifest.ts +8 -0
  323. package/src/domains/toolchain/paths.ts +34 -0
  324. package/src/domains/toolchain/registry.ts +265 -0
  325. package/src/domains/toolchain/remove.ts +218 -0
  326. package/src/domains/toolchain/resolve.ts +182 -0
  327. package/src/domains/toolchain/types.ts +113 -0
  328. package/src/domains/toolchain/version.ts +88 -0
  329. package/src/engine/acp/server.ts +413 -70
  330. package/src/engine/acp/types.ts +19 -1
  331. package/src/engine/claude/sdk-module.ts +98 -0
  332. package/src/engine/claude/sdk-runtime.ts +19 -11
  333. package/src/engine/tui-primitives.ts +21 -0
  334. package/src/engine/tui.ts +1 -0
  335. package/src/engine/worker-runtime.ts +2 -13
  336. package/src/entry/boot-options.ts +2 -0
  337. package/src/entry/orchestrator.ts +103 -5
  338. package/src/entry/panes-activation.ts +31 -0
  339. package/src/entry/with-panes.ts +20 -0
  340. package/src/interactive/context-overlay.ts +1 -0
  341. package/src/interactive/cost-overlay.ts +2 -0
  342. package/src/interactive/council-grid.ts +1 -3
  343. package/src/interactive/council.ts +11 -0
  344. package/src/interactive/dispatch-board.ts +26 -16
  345. package/src/interactive/interactive-application.ts +153 -6
  346. package/src/interactive/interactive-input-runtime.ts +7 -0
  347. package/src/interactive/interactive-slash-runtime.ts +36 -6
  348. package/src/interactive/memory-overlay.ts +1 -0
  349. package/src/interactive/modal-marker.ts +170 -0
  350. package/src/interactive/mux-bridge.ts +214 -0
  351. package/src/interactive/overlay-frame.ts +58 -2
  352. package/src/interactive/overlay-general-openers.ts +1 -0
  353. package/src/interactive/overlay-key-routing.ts +14 -3
  354. package/src/interactive/overlay-lifecycle.ts +19 -3
  355. package/src/interactive/overlay-model-selectors.ts +40 -3
  356. package/src/interactive/overlay-permission-lifecycle.ts +90 -22
  357. package/src/interactive/overlay-transitions.ts +18 -4
  358. package/src/interactive/overlays/agents.ts +1 -0
  359. package/src/interactive/overlays/ask-user.ts +136 -30
  360. package/src/interactive/overlays/auth-dialog.ts +1 -0
  361. package/src/interactive/overlays/context-reset.ts +1 -0
  362. package/src/interactive/overlays/cwd-fallback.ts +1 -0
  363. package/src/interactive/overlays/decisions.ts +11 -11
  364. package/src/interactive/overlays/extensions.ts +1 -0
  365. package/src/interactive/overlays/fleet-run-approval.ts +1 -0
  366. package/src/interactive/overlays/handoff-review.ts +1 -0
  367. package/src/interactive/overlays/help-reference.ts +2 -0
  368. package/src/interactive/overlays/interop.ts +1 -0
  369. package/src/interactive/overlays/library-install-confirm.ts +1 -0
  370. package/src/interactive/overlays/library-tabs.ts +28 -0
  371. package/src/interactive/overlays/list-overlay.ts +10 -1
  372. package/src/interactive/overlays/message-picker.ts +1 -0
  373. package/src/interactive/overlays/model-scope.ts +86 -0
  374. package/src/interactive/overlays/model-selector.ts +1 -0
  375. package/src/interactive/overlays/prompts.ts +1 -0
  376. package/src/interactive/overlays/session-selector.ts +1 -0
  377. package/src/interactive/overlays/settings-sections.ts +30 -0
  378. package/src/interactive/overlays/settings.ts +579 -44
  379. package/src/interactive/overlays/side-question.ts +1 -0
  380. package/src/interactive/overlays/skills-hub.ts +3 -11
  381. package/src/interactive/overlays/tree-selector.ts +1 -0
  382. package/src/interactive/pane-policy.ts +46 -0
  383. package/src/interactive/panes-runtime.ts +292 -0
  384. package/src/interactive/renderers/compaction-summary.ts +29 -0
  385. package/src/interactive/renderers/worker-entry.ts +122 -14
  386. package/src/interactive/slash-commands.ts +251 -15
  387. package/src/interactive/tasks-overlay.ts +1 -0
  388. package/src/interactive/theme/tokens.ts +3 -14
  389. package/src/interactive/turn-context.ts +47 -2
  390. package/src/interactive/view/artifacts.ts +109 -1
  391. package/src/interactive/view/view-overlay.ts +1 -0
  392. package/src/interactive/watch-pane.ts +152 -0
  393. package/src/interactive/worker-progress.ts +7 -1
  394. package/src/interactive/worker-receipts.ts +19 -1
  395. package/src/interactive/worker-stream.ts +5 -0
  396. package/src/interactive/yazi-bridge.ts +444 -0
  397. package/src/tools/bootstrap.ts +26 -2
  398. package/src/tools/builtin-tool-catalog.ts +15 -0
  399. package/src/tools/compete-worktrees.ts +83 -2
  400. package/src/tools/core-bootstrap.ts +2 -1
  401. package/src/tools/dispatch-admission.ts +14 -3
  402. package/src/tools/dispatch-arguments.ts +20 -20
  403. package/src/tools/dispatch-run-events.ts +134 -19
  404. package/src/tools/dispatch-runner.ts +29 -7
  405. package/src/tools/dispatch-types.ts +15 -3
  406. package/src/tools/dispatch.ts +1 -1
  407. package/src/tools/executables.ts +17 -14
  408. package/src/tools/observation.ts +54 -4
  409. package/src/tools/panes-surface.ts +38 -0
  410. package/src/tools/panes.ts +112 -0
  411. package/src/tools/policy.ts +10 -1
  412. package/src/tools/presentation.ts +1 -0
  413. package/dist/chunk-D73KXYPF.js +0 -37
  414. package/dist/chunk-MLOK6ZOS.js +0 -2888
  415. package/dist/chunk-YKOFT37S.js +0 -279
  416. package/dist/doctor-TWBWFK5V.js +0 -165
@@ -178,6 +178,38 @@ export function harmonyReasoningEffort(level: string | undefined): HarmonyReason
178
178
  */
179
179
  export type ReasoningClass = "never" | "switchable" | "always";
180
180
 
181
+ /**
182
+ * Minimum completion room for a memory envelope on an always-on-thinking
183
+ * model. A 9B local route has been measured reasoning for more than 1,800
184
+ * tokens before its first `<operations>` byte, while an always-on 35B spent a
185
+ * 400-token allowance entirely on reasoning and ended at length. Four thousand
186
+ * leaves the configured 2,000-token content budget after that preamble.
187
+ */
188
+ export const MEMORY_ALWAYS_ON_MODEL_MIN_OUTPUT_TOKENS = 4_000;
189
+
190
+ /**
191
+ * Derive the memory step's request budget from the resolved model capability.
192
+ * Switchable models are requested with thinking off and need only the normal
193
+ * content budget. An always-on mechanism cannot honor that request, so it gets
194
+ * explicit reasoning headroom. The runtime's known cap remains authoritative.
195
+ */
196
+ export function memoryInterventionModelMaxTokens(input: {
197
+ configuredMaxTokens: number;
198
+ thinkingMechanism: ThinkingMechanism;
199
+ modelMaxTokens?: number;
200
+ }): number {
201
+ const configured =
202
+ Number.isSafeInteger(input.configuredMaxTokens) && input.configuredMaxTokens > 0 ? input.configuredMaxTokens : 1;
203
+ const requested =
204
+ input.thinkingMechanism === "always-on"
205
+ ? Math.max(configured * 2, MEMORY_ALWAYS_ON_MODEL_MIN_OUTPUT_TOKENS)
206
+ : configured;
207
+ const modelCap = input.modelMaxTokens;
208
+ return typeof modelCap === "number" && Number.isSafeInteger(modelCap) && modelCap > 0
209
+ ? Math.min(requested, modelCap)
210
+ : requested;
211
+ }
212
+
181
213
  export function reasoningClassForMechanism(mechanism: ThinkingMechanism | null | undefined): ReasoningClass {
182
214
  if (mechanism === "none") return "never";
183
215
  if (mechanism === "always-on") return "always";
@@ -42,6 +42,7 @@ export const BUILTIN_RUNTIME_BOOT_MANIFEST: ReadonlyArray<RuntimeBootMetadata> =
42
42
  { id: "lmstudio", aliases: ["lmstudio-native"], kind: "http", tier: "local-native", auth: "api-key" },
43
43
  { id: "ollama-native", kind: "http", tier: "local-native", auth: "none" },
44
44
  { id: "anthropic-compat", kind: "http", tier: "protocol", auth: "api-key" },
45
+ { id: "litellm", kind: "http", tier: "protocol", auth: "api-key" },
45
46
  { id: "openai-compat", kind: "http", tier: "protocol", auth: "api-key" },
46
47
  { id: "sglang", kind: "http", tier: "local-native", auth: "api-key" },
47
48
  { id: "vllm", kind: "http", tier: "local-native", auth: "api-key" },
@@ -36,6 +36,7 @@ import ollamaNative from "./local-native/ollama-native.js";
36
36
  import sglang from "./local-native/sglang.js";
37
37
  import vllm from "./local-native/vllm.js";
38
38
  import anthropicCompat from "./protocol/anthropic-compat.js";
39
+ import litellm from "./protocol/litellm.js";
39
40
  import openaiCompat from "./protocol/openai-compat.js";
40
41
 
41
42
  const BUILTIN_RUNTIMES: ReadonlyArray<RuntimeDescriptor> = [
@@ -60,6 +61,7 @@ const BUILTIN_RUNTIMES: ReadonlyArray<RuntimeDescriptor> = [
60
61
  lmstudio,
61
62
  ollamaNative,
62
63
  anthropicCompat,
64
+ litellm,
63
65
  openaiCompat,
64
66
  sglang,
65
67
  vllm,
@@ -0,0 +1,375 @@
1
+ /**
2
+ * LiteLLM proxy: an OpenAI-compatible gateway that fronts a fleet.
3
+ *
4
+ * This is a separate runtime from `openai-compat` rather than a target flag on
5
+ * it, because a gateway answers two questions the generic protocol cannot.
6
+ *
7
+ * First, capability discovery. A gateway publishes semantic aliases: the client
8
+ * asks for `code` and the proxy decides that means `katcoder2.5-35b-moe` on one
9
+ * particular host. LiteLLM's `/v1/models` is the plain OpenAI listing, so every
10
+ * row flattens to a bare id and the context window, tool support, and modality
11
+ * of the model actually behind the alias are all erased. Reached over
12
+ * `openai-compat`, a 262k tool-calling coder is indistinguishable from a 4k
13
+ * chat toy, and Clio plans the session against its own assumed floor. LiteLLM
14
+ * publishes the real numbers on `/v1/model/info` instead, which is what this
15
+ * probe reads.
16
+ *
17
+ * Second, routed-deployment attribution. Which physical model answered is a
18
+ * per-response fact behind an alias, and LiteLLM returns it in `x-litellm-*`
19
+ * response headers. See {@link routedDeploymentFromHeaders}.
20
+ *
21
+ * Structured output is the one property worth stating explicitly, because
22
+ * getting it wrong is silent. `src/core/response-schema.ts` deliberately keys
23
+ * the schema dialect on runtime id and not on api family, since a generic
24
+ * OpenAI-compatible gateway answers HTTP 200 to a `response_format` spelling it
25
+ * does not implement and returns unconstrained JSON. LiteLLM earns its entry in
26
+ * that table by measurement rather than by assumption: against the homelab
27
+ * gateway on 2026-08-30, `{"type":"json_schema", json_schema:{...}}` was
28
+ * grammar-enforced end to end on both a llama.cpp upstream and an LM Studio
29
+ * upstream, `{"type":"json_object", schema:{...}}` was enforced on llama.cpp
30
+ * and rejected HTTP 400 by LM Studio, and the same prompt with no
31
+ * `response_format` returned plain prose. `drop_params: true` did not strip
32
+ * either spelling. The standard `json_schema` dialect is therefore the one that
33
+ * holds across every upstream, and it is what this runtime declares.
34
+ */
35
+
36
+ import { CLIO_MIN_CONTEXT_WINDOW, CLIO_MIN_MAX_OUTPUT_TOKENS } from "../../../../core/context-floor.js";
37
+ import type { Api, Model } from "../../../../engine/types.js";
38
+ import { probeJson } from "../../probe/http.js";
39
+ import type { CapabilityFlags } from "../../types/capability-flags.js";
40
+ import type { KnowledgeBaseHit } from "../../types/knowledge-base.js";
41
+ import type { ProbeContext, ProbeModelStatus, ProbeResult, RuntimeDescriptor } from "../../types/runtime-descriptor.js";
42
+ import type { TargetDescriptor } from "../../types/target-descriptor.js";
43
+ import { stripTrailingSlash, synthLocalModel, withV1 } from "../common/local-synth.js";
44
+
45
+ const LITELLM_PROVIDER = "litellm";
46
+
47
+ /**
48
+ * Conservative until the probe answers. A gateway that does not publish
49
+ * `model_info` has told Clio nothing, and claiming tools on a target whose tool
50
+ * surface is unknown produces a worker that fails on its first tool call rather
51
+ * than one that is never admitted.
52
+ */
53
+ const defaultCapabilities: CapabilityFlags = {
54
+ chat: true,
55
+ tools: false,
56
+ reasoning: false,
57
+ vision: false,
58
+ audio: false,
59
+ embeddings: false,
60
+ rerank: false,
61
+ fim: false,
62
+ structuredOutputs: "json-schema",
63
+ contextWindow: CLIO_MIN_CONTEXT_WINDOW,
64
+ maxTokens: CLIO_MIN_MAX_OUTPUT_TOKENS,
65
+ };
66
+
67
+ /** The server root. This runtime's own paths carry `/v1` where they need it. */
68
+ function rootUrl(target: TargetDescriptor): string | null {
69
+ if (!target.url) return null;
70
+ const trimmed = stripTrailingSlash(target.url);
71
+ return trimmed.endsWith("/v1") ? trimmed.slice(0, -"/v1".length) : trimmed;
72
+ }
73
+
74
+ function bearerHeaders(target: TargetDescriptor, ctx: ProbeContext): Record<string, string> {
75
+ const headers: Record<string, string> = { ...(target.auth?.headers ?? {}) };
76
+ const envName = target.auth?.apiKeyEnvVar;
77
+ const fromEnv = envName && ctx.credentialsPresent.has(envName) ? process.env[envName]?.trim() : undefined;
78
+ const token = ctx.authToken?.trim() || fromEnv;
79
+ if (token) headers.authorization = `Bearer ${token}`;
80
+ return headers;
81
+ }
82
+
83
+ interface LiteLLMModelInfoRow {
84
+ model_name?: unknown;
85
+ litellm_params?: { model?: unknown; api_base?: unknown };
86
+ model_info?: Record<string, unknown>;
87
+ }
88
+
89
+ interface LiteLLMModelInfoResponse {
90
+ data?: ReadonlyArray<LiteLLMModelInfoRow>;
91
+ }
92
+
93
+ interface LiteLLMModelsResponse {
94
+ data?: ReadonlyArray<{ id?: unknown }>;
95
+ }
96
+
97
+ function positiveInteger(value: unknown): number | undefined {
98
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : undefined;
99
+ }
100
+
101
+ function boolean(value: unknown): boolean | undefined {
102
+ return typeof value === "boolean" ? value : undefined;
103
+ }
104
+
105
+ /**
106
+ * Project one LiteLLM `model_info` block onto Clio's capability flags.
107
+ *
108
+ * Only fields the gateway actually filled in are returned. A `model_info` that
109
+ * declares nothing leaves the whole record empty rather than reporting zeros,
110
+ * so the caller can tell "the gateway says this model has no vision" apart from
111
+ * "the gateway did not say", and merge accordingly.
112
+ */
113
+ export function capabilitiesFromLiteLLMModelInfo(info: Record<string, unknown>): Partial<CapabilityFlags> {
114
+ const caps: Partial<CapabilityFlags> = {};
115
+ const contextWindow = positiveInteger(info.max_input_tokens) ?? positiveInteger(info.max_tokens);
116
+ if (contextWindow !== undefined) caps.contextWindow = contextWindow;
117
+ const maxTokens = positiveInteger(info.max_output_tokens);
118
+ if (maxTokens !== undefined) caps.maxTokens = maxTokens;
119
+ const tools = boolean(info.supports_function_calling);
120
+ if (tools !== undefined) caps.tools = tools;
121
+ const vision = boolean(info.supports_vision);
122
+ if (vision !== undefined) caps.vision = vision;
123
+ const reasoning = boolean(info.supports_reasoning);
124
+ if (reasoning !== undefined) caps.reasoning = reasoning;
125
+ const audio = boolean(info.supports_audio_input);
126
+ if (audio !== undefined) caps.audio = audio;
127
+ // `mode` is LiteLLM's own surface discriminator. An embedding deployment is
128
+ // not a chat deployment, and admitting it as one produces a run that fails
129
+ // at the first completion rather than at selection.
130
+ if (info.mode === "embedding") {
131
+ caps.chat = false;
132
+ caps.embeddings = true;
133
+ }
134
+ if (info.mode === "rerank") {
135
+ caps.chat = false;
136
+ caps.rerank = true;
137
+ }
138
+ if (info.mode === "chat") caps.chat = true;
139
+ // Measured, not inferred: see the module header.
140
+ if (boolean(info.supports_response_schema) === true) caps.structuredOutputs = "json-schema";
141
+ return caps;
142
+ }
143
+
144
+ interface LiteLLMCatalog {
145
+ models: string[];
146
+ modelCapabilities: Record<string, Partial<CapabilityFlags>>;
147
+ modelStates: Record<string, ProbeModelStatus>;
148
+ /** Physical deployment behind each alias, for display. */
149
+ deployments: Record<string, { model: string; apiBase?: string }>;
150
+ }
151
+
152
+ const EMPTY_CATALOG: LiteLLMCatalog = { models: [], modelCapabilities: {}, modelStates: {}, deployments: {} };
153
+
154
+ /**
155
+ * Read the catalog from `/v1/model/info`, falling back to `/v1/models`.
156
+ *
157
+ * The detail endpoint is the only one that carries capabilities, but it can be
158
+ * refused for a virtual key with narrower permissions than the master key. A
159
+ * gateway that answers the plain listing and refuses the detail is still usable;
160
+ * it just cannot tell Clio anything beyond which aliases exist, so the target
161
+ * falls back to its declared capabilities exactly as `openai-compat` would.
162
+ */
163
+ async function fetchCatalog(base: string, ctx: ProbeContext, headers: Record<string, string>): Promise<LiteLLMCatalog> {
164
+ const detailOpts = { url: `${base}/v1/model/info`, timeoutMs: ctx.httpTimeoutMs, headers } as const;
165
+ const detail = await (ctx.signal
166
+ ? probeJson<LiteLLMModelInfoResponse>({ ...detailOpts, signal: ctx.signal })
167
+ : probeJson<LiteLLMModelInfoResponse>(detailOpts));
168
+
169
+ if (detail.ok && Array.isArray(detail.data?.data)) {
170
+ const catalog: LiteLLMCatalog = { models: [], modelCapabilities: {}, modelStates: {}, deployments: {} };
171
+ for (const row of detail.data.data) {
172
+ if (typeof row?.model_name !== "string" || row.model_name.length === 0) continue;
173
+ const alias = row.model_name;
174
+ // One alias can carry several deployments. They share a name and Clio
175
+ // addresses them as one model, so the first row establishes the entry
176
+ // and later rows for the same alias are the load-balancing pool behind
177
+ // it rather than separate models.
178
+ if (catalog.deployments[alias] !== undefined) continue;
179
+ catalog.models.push(alias);
180
+ const caps = row.model_info ? capabilitiesFromLiteLLMModelInfo(row.model_info) : {};
181
+ if (Object.keys(caps).length > 0) catalog.modelCapabilities[alias] = caps;
182
+ const upstream = row.litellm_params?.model;
183
+ const apiBase = row.litellm_params?.api_base;
184
+ catalog.deployments[alias] = {
185
+ model: typeof upstream === "string" ? upstream : alias,
186
+ ...(typeof apiBase === "string" ? { apiBase } : {}),
187
+ };
188
+ }
189
+ if (catalog.models.length > 0) return catalog;
190
+ }
191
+
192
+ const listOpts = { url: `${base}/v1/models`, timeoutMs: ctx.httpTimeoutMs, headers } as const;
193
+ const list = await (ctx.signal
194
+ ? probeJson<LiteLLMModelsResponse>({ ...listOpts, signal: ctx.signal })
195
+ : probeJson<LiteLLMModelsResponse>(listOpts));
196
+ if (!list.ok || !Array.isArray(list.data?.data)) return EMPTY_CATALOG;
197
+ const models = list.data.data
198
+ .map((row) => (typeof row?.id === "string" ? row.id : null))
199
+ .filter((id): id is string => id !== null && id.length > 0);
200
+ return { models, modelCapabilities: {}, modelStates: {}, deployments: {} };
201
+ }
202
+
203
+ /**
204
+ * The routed deployment LiteLLM reports for one response.
205
+ *
206
+ * Behind an alias the physical model is a per-request fact: a fallback, a
207
+ * context-window promotion, or an ordinary load-balancing choice all change it
208
+ * without changing what the client asked for. Reporting the alias back as the
209
+ * model is how a gateway hides an outage, and it is exactly what an operator
210
+ * needs to see. LiteLLM answers in headers on every completion.
211
+ */
212
+ export interface RoutedDeployment {
213
+ /** The alias the client asked for, e.g. `code`. */
214
+ group?: string;
215
+ /** The physical model that answered, e.g. `openai/katcoder2.5-35b-moe`. */
216
+ model?: string;
217
+ /** The upstream that served it. */
218
+ apiBase?: string;
219
+ /** Stable id of the specific deployment inside the group. */
220
+ deploymentId?: string;
221
+ /** Non-zero when the request did not reach its first-choice deployment. */
222
+ attemptedFallbacks?: number;
223
+ attemptedRetries?: number;
224
+ }
225
+
226
+ function headerValue(headers: { get(name: string): string | null }, name: string): string | undefined {
227
+ const raw = headers.get(name)?.trim();
228
+ return raw && raw.length > 0 ? raw : undefined;
229
+ }
230
+
231
+ function headerInteger(headers: { get(name: string): string | null }, name: string): number | undefined {
232
+ const raw = headerValue(headers, name);
233
+ if (raw === undefined) return undefined;
234
+ const parsed = Number.parseInt(raw, 10);
235
+ return Number.isFinite(parsed) ? parsed : undefined;
236
+ }
237
+
238
+ /**
239
+ * Extract the routed deployment from a LiteLLM response, or null when the
240
+ * headers are absent. Absent is the normal answer for every non-LiteLLM target,
241
+ * so callers treat null as "no attribution available" rather than an error.
242
+ */
243
+ export function routedDeploymentFromHeaders(headers: { get(name: string): string | null }): RoutedDeployment | null {
244
+ const routed: RoutedDeployment = {};
245
+ const group = headerValue(headers, "x-litellm-model-group");
246
+ if (group !== undefined) routed.group = group;
247
+ const model = headerValue(headers, "x-litellm-model-name");
248
+ if (model !== undefined) routed.model = model;
249
+ const apiBase = headerValue(headers, "x-litellm-model-api-base");
250
+ if (apiBase !== undefined) routed.apiBase = apiBase;
251
+ const deploymentId = headerValue(headers, "x-litellm-model-id");
252
+ if (deploymentId !== undefined) routed.deploymentId = deploymentId;
253
+ const fallbacks = headerInteger(headers, "x-litellm-attempted-fallbacks");
254
+ if (fallbacks !== undefined) routed.attemptedFallbacks = fallbacks;
255
+ const retries = headerInteger(headers, "x-litellm-attempted-retries");
256
+ if (retries !== undefined) routed.attemptedRetries = retries;
257
+ return Object.keys(routed).length > 0 ? routed : null;
258
+ }
259
+
260
+ /**
261
+ * One-line attribution for a routed response.
262
+ *
263
+ * A request that fell back says so. `code -> openai/qwen3.8-27b-dense @
264
+ * mini.tail78f300.ts.net:8080 (fallback)` is the whole point of the runtime:
265
+ * the operator asked for the coder, the coder was asleep, and something else
266
+ * answered.
267
+ */
268
+ export function formatRoutedDeployment(routed: RoutedDeployment): string {
269
+ const parts: string[] = [];
270
+ if (routed.group && routed.model && routed.group !== routed.model) {
271
+ parts.push(`${routed.group} -> ${routed.model}`);
272
+ } else if (routed.model ?? routed.group) {
273
+ parts.push((routed.model ?? routed.group) as string);
274
+ }
275
+ if (routed.apiBase) {
276
+ let host = routed.apiBase;
277
+ try {
278
+ host = new URL(routed.apiBase).host;
279
+ } catch {
280
+ // A non-URL api_base is displayed verbatim.
281
+ }
282
+ parts.push(`@ ${host}`);
283
+ }
284
+ if ((routed.attemptedFallbacks ?? 0) > 0) parts.push("(fallback)");
285
+ else if ((routed.attemptedRetries ?? 0) > 0) parts.push("(retried)");
286
+ return parts.join(" ");
287
+ }
288
+
289
+ const litellmRuntime: RuntimeDescriptor = {
290
+ id: "litellm",
291
+ displayName: "LiteLLM gateway",
292
+ kind: "http",
293
+ tier: "protocol",
294
+ apiFamily: "openai-completions",
295
+ auth: "api-key",
296
+ defaultCapabilities,
297
+
298
+ async probe(target: TargetDescriptor, ctx: ProbeContext): Promise<ProbeResult> {
299
+ const base = rootUrl(target);
300
+ if (!base) return { ok: false, error: "target has no url" };
301
+ const headers = bearerHeaders(target, ctx);
302
+
303
+ // Liveness first, and deliberately on the unauthenticated endpoint. A
304
+ // gateway with authentication enforced answers 401 to `/v1/models` when
305
+ // credentials are missing or wrong, which is indistinguishable from an
306
+ // unreachable host if that is the only thing probed. `/health/liveliness`
307
+ // separates "the gateway is down" from "this key is not valid", and the
308
+ // two have completely different remedies.
309
+ const liveOpts = { url: `${base}/health/liveliness`, timeoutMs: ctx.httpTimeoutMs } as const;
310
+ const live = await (ctx.signal
311
+ ? probeJson<unknown>({ ...liveOpts, signal: ctx.signal })
312
+ : probeJson<unknown>(liveOpts));
313
+ if (!live.ok) return { ok: false, error: live.error ?? "gateway is not reachable" };
314
+
315
+ const catalog = await fetchCatalog(base, ctx, headers);
316
+ if (catalog.models.length === 0) {
317
+ const result: ProbeResult = {
318
+ ok: false,
319
+ error: "gateway is live but served no model catalog; check the target's API key",
320
+ };
321
+ if (live.latencyMs !== undefined) result.latencyMs = live.latencyMs;
322
+ return result;
323
+ }
324
+
325
+ const result: ProbeResult = { ok: true, models: catalog.models };
326
+ if (live.latencyMs !== undefined) result.latencyMs = live.latencyMs;
327
+ if (Object.keys(catalog.modelCapabilities).length > 0) {
328
+ result.modelCapabilities = catalog.modelCapabilities;
329
+ const selected = target.defaultModel?.trim();
330
+ const selectedCaps = selected ? catalog.modelCapabilities[selected] : undefined;
331
+ if (selected && selectedCaps) {
332
+ result.discoveredCapabilities = selectedCaps;
333
+ result.capabilityModelId = selected;
334
+ }
335
+ }
336
+ const notes: string[] = [];
337
+ const configured = target.defaultModel?.trim();
338
+ if (configured && !catalog.models.includes(configured)) {
339
+ notes.push(`configured model '${configured}' is not in the gateway catalog`);
340
+ }
341
+ // Naming the physical model behind each alias is the difference between a
342
+ // catalog an operator can audit and a list of words.
343
+ const routed = Object.entries(catalog.deployments)
344
+ .map(([alias, deployment]) => `${alias}=${deployment.model}`)
345
+ .sort();
346
+ if (routed.length > 0) notes.push(`routes: ${routed.join(", ")}`);
347
+ if (notes.length > 0) result.notes = notes;
348
+ return result;
349
+ },
350
+
351
+ async probeModels(target: TargetDescriptor, ctx: ProbeContext): Promise<string[]> {
352
+ const base = rootUrl(target);
353
+ if (!base) return [];
354
+ return (await fetchCatalog(base, ctx, bearerHeaders(target, ctx))).models;
355
+ },
356
+
357
+ synthesizeModel(target: TargetDescriptor, wireModelId: string, kb: KnowledgeBaseHit | null): Model<Api> {
358
+ return synthLocalModel({
359
+ // A LiteLLM target is a gateway whether or not settings said so. The
360
+ // residency layer reads this to stay observe-only: the models behind an
361
+ // alias are the proxy's to load and evict, and a Clio stream that tried
362
+ // to manage them would be issuing load commands to a host that does not
363
+ // take them.
364
+ target: { ...target, gateway: true },
365
+ wireModelId,
366
+ kb,
367
+ defaultCapabilities,
368
+ apiFamily: "openai-completions",
369
+ provider: LITELLM_PROVIDER,
370
+ baseUrlForTarget: withV1,
371
+ });
372
+ },
373
+ };
374
+
375
+ export default litellmRuntime;
@@ -59,6 +59,7 @@ const SUMMARY_BY_RUNTIME_ID: Readonly<Record<string, string>> = {
59
59
  llamacpp: "llama.cpp server (auto-detect surface)",
60
60
  "anthropic-compat": "Generic Anthropic-compatible REST",
61
61
  "openai-compat": "Generic OpenAI-compatible REST",
62
+ litellm: "LiteLLM gateway with per-alias capability discovery and routed-model attribution",
62
63
  };
63
64
 
64
65
  function groupPriority(group: ProviderSupportGroup): number {
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Disposable per-target model catalogs discovered by provider probes.
3
+ *
4
+ * A live endpoint catalog is machine state, not operator policy. Persisting it
5
+ * in settings.yaml made a three-target config carry hundreds of volatile model
6
+ * ids. Cache files keep that snapshot available to a new process without
7
+ * turning the hand-edited settings file into a probe transcript.
8
+ */
9
+
10
+ import { createHash } from "node:crypto";
11
+ import { existsSync, readFileSync } from "node:fs";
12
+ import { join } from "node:path";
13
+ import { safeResourceWrite } from "../../core/safe-resource-write.js";
14
+ import { clioCacheDir } from "../../core/xdg.js";
15
+ import type { TargetDescriptor } from "./types/target-descriptor.js";
16
+
17
+ export const TARGET_MODEL_CACHE_TTL_MS = 24 * 60 * 60 * 1_000;
18
+ const TARGET_MODEL_CACHE_VERSION = 1;
19
+ const TARGET_MODEL_CACHE_MAX_MODELS = 4_096;
20
+ const TARGET_MODEL_CACHE_MAX_MODEL_ID_CHARS = 1_024;
21
+
22
+ export interface TargetModelSnapshot {
23
+ version: 1;
24
+ targetId: string;
25
+ runtimeId: string;
26
+ url: string | null;
27
+ models: string[];
28
+ observedAt: string;
29
+ }
30
+
31
+ interface TargetModelCacheOptions {
32
+ cacheDir?: string;
33
+ nowMs?: number;
34
+ ttlMs?: number;
35
+ }
36
+
37
+ function targetCacheKey(targetId: string): string {
38
+ return createHash("sha256").update(targetId).digest("hex").slice(0, 24);
39
+ }
40
+
41
+ export function targetModelSnapshotPath(targetId: string, cacheDir = clioCacheDir()): string {
42
+ return join(cacheDir, "provider-target-models", `${targetCacheKey(targetId)}.json`);
43
+ }
44
+
45
+ function normalizedModels(models: ReadonlyArray<string>): string[] {
46
+ const seen = new Set<string>();
47
+ const out: string[] = [];
48
+ for (const raw of models) {
49
+ const model = raw.trim();
50
+ if (model.length === 0 || model.length > TARGET_MODEL_CACHE_MAX_MODEL_ID_CHARS || seen.has(model)) continue;
51
+ seen.add(model);
52
+ out.push(model);
53
+ if (out.length >= TARGET_MODEL_CACHE_MAX_MODELS) break;
54
+ }
55
+ return out;
56
+ }
57
+
58
+ function parseSnapshot(value: unknown): TargetModelSnapshot | null {
59
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
60
+ const row = value as Partial<TargetModelSnapshot>;
61
+ if (row.version !== TARGET_MODEL_CACHE_VERSION) return null;
62
+ if (typeof row.targetId !== "string" || row.targetId.length === 0) return null;
63
+ if (typeof row.runtimeId !== "string" || row.runtimeId.length === 0) return null;
64
+ if (row.url !== null && typeof row.url !== "string") return null;
65
+ if (!Array.isArray(row.models) || row.models.some((model) => typeof model !== "string")) return null;
66
+ if (typeof row.observedAt !== "string" || !Number.isFinite(Date.parse(row.observedAt))) return null;
67
+ const models = normalizedModels(row.models);
68
+ if (models.length !== row.models.length) return null;
69
+ return {
70
+ version: TARGET_MODEL_CACHE_VERSION,
71
+ targetId: row.targetId,
72
+ runtimeId: row.runtimeId,
73
+ url: row.url,
74
+ models,
75
+ observedAt: row.observedAt,
76
+ };
77
+ }
78
+
79
+ /** Read a fresh snapshot only when it still describes this target identity. */
80
+ export function readTargetModelSnapshot(
81
+ target: Pick<TargetDescriptor, "id" | "runtime" | "url">,
82
+ options: TargetModelCacheOptions = {},
83
+ ): TargetModelSnapshot | null {
84
+ const path = targetModelSnapshotPath(target.id, options.cacheDir);
85
+ if (!existsSync(path)) return null;
86
+ try {
87
+ const snapshot = parseSnapshot(JSON.parse(readFileSync(path, "utf8")) as unknown);
88
+ if (snapshot === null) return null;
89
+ if (snapshot.targetId !== target.id || snapshot.runtimeId !== target.runtime) return null;
90
+ if (snapshot.url !== (target.url ?? null)) return null;
91
+ const observedMs = Date.parse(snapshot.observedAt);
92
+ const nowMs = options.nowMs ?? Date.now();
93
+ const ttlMs = options.ttlMs ?? TARGET_MODEL_CACHE_TTL_MS;
94
+ if (observedMs > nowMs || nowMs - observedMs >= ttlMs) return null;
95
+ return snapshot;
96
+ } catch {
97
+ return null;
98
+ }
99
+ }
100
+
101
+ /** Best-effort atomic replacement of one target's disposable probe snapshot. */
102
+ export function recordTargetModelSnapshot(
103
+ target: Pick<TargetDescriptor, "id" | "runtime" | "url">,
104
+ models: ReadonlyArray<string>,
105
+ options: Pick<TargetModelCacheOptions, "cacheDir" | "nowMs"> = {},
106
+ ): boolean {
107
+ if (target.id.length === 0 || target.runtime.length === 0) return false;
108
+ const snapshot: TargetModelSnapshot = {
109
+ version: TARGET_MODEL_CACHE_VERSION,
110
+ targetId: target.id,
111
+ runtimeId: target.runtime,
112
+ url: target.url ?? null,
113
+ models: normalizedModels(models),
114
+ observedAt: new Date(options.nowMs ?? Date.now()).toISOString(),
115
+ };
116
+ try {
117
+ safeResourceWrite(targetModelSnapshotPath(target.id, options.cacheDir), `${JSON.stringify(snapshot, null, 2)}\n`, {
118
+ encoding: "utf8",
119
+ });
120
+ return true;
121
+ } catch {
122
+ return false;
123
+ }
124
+ }
@@ -68,11 +68,14 @@ export {
68
68
  parsePendingSkillRequests,
69
69
  parseSkillCommand,
70
70
  type Skill,
71
+ type SkillCatalogInvalidReason,
72
+ type SkillCatalogValidity,
71
73
  type SkillExpansion,
72
74
  type SkillList,
73
75
  type SkillProvenance,
74
76
  type SkillRoot,
75
77
  type SkillSource,
78
+ skillCatalogValidity,
76
79
  } from "./skills/loader.js";
77
80
  export {
78
81
  type DiscoverMarketplaceOptions,
@@ -893,6 +893,39 @@ export function modelVisibleSkills(skills: ReadonlyArray<Skill>): Skill[] {
893
893
  return skills.filter((skill) => skill.trusted && !skill.disableModelInvocation);
894
894
  }
895
895
 
896
+ /** Why a loaded skill catalog is not valid. Ordered by which failure to report first. */
897
+ export type SkillCatalogInvalidReason = "load-error" | "collision" | "unloadable-file" | "no-skills";
898
+
899
+ export interface SkillCatalogValidity {
900
+ ok: boolean;
901
+ reason: SkillCatalogInvalidReason | null;
902
+ }
903
+
904
+ /**
905
+ * The rule `clio-coder skills validate` applies, in one place.
906
+ *
907
+ * A scanned file that produced no loaded skill is malformed, and a collision
908
+ * drops a skill: both make the catalog invalid, as does any hard error. Benign
909
+ * warnings (name/path mismatch, name format, description length) attach to a
910
+ * file that still loaded, so their path is among the loaded skills and they do
911
+ * not fail validation.
912
+ *
913
+ * Lives here rather than in the CLI because two callers now need the same
914
+ * answer, and a verdict that drifts between the terminal and a GUI is worse
915
+ * than no verdict at all.
916
+ */
917
+ export function skillCatalogValidity(list: SkillList): SkillCatalogValidity {
918
+ if (list.diagnostics.some((diag) => diag.type === "error")) return { ok: false, reason: "load-error" };
919
+ if (list.diagnostics.some((diag) => diag.type === "collision")) return { ok: false, reason: "collision" };
920
+ const loadedPaths = new Set(list.items.map((skill) => skill.filePath));
921
+ const unloadable = list.diagnostics.some(
922
+ (diag) => diag.type === "warning" && diag.path !== undefined && !loadedPaths.has(diag.path),
923
+ );
924
+ if (unloadable) return { ok: false, reason: "unloadable-file" };
925
+ if (list.items.length === 0) return { ok: false, reason: "no-skills" };
926
+ return { ok: true, reason: null };
927
+ }
928
+
896
929
  export function parseSkillCommand(input: string): { name: string; args: string } | null {
897
930
  const trimmed = input.trim();
898
931
  if (!trimmed.startsWith("/skill ")) return null;
@@ -104,6 +104,12 @@ function baseClassify(tool: string): ActionClass | null {
104
104
  // local mirror. Neither touches the workspace, so it is never gated
105
105
  // behind a confirmation.
106
106
  case ToolNames.Ledger:
107
+ // panes focuses, opens, and closes terminal panes Clio itself created, in
108
+ // a pane host that lives outside the workspace. Nothing it does writes a
109
+ // file, runs a model-supplied command (its schema exposes presets only),
110
+ // or survives the session, so it is reversible, local, and never gated
111
+ // behind a confirmation.
112
+ case ToolNames.Panes:
107
113
  return "read";
108
114
  case ToolNames.Write:
109
115
  case ToolNames.Edit: