@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
@@ -0,0 +1,175 @@
1
+ import { gunzipSync, inflateRawSync } from "node:zlib";
2
+
3
+ /**
4
+ * Just enough zip and tar.gz reading to unpack a pinned release asset.
5
+ *
6
+ * Node ships neither reader, and the alternatives were a dependency or shelling
7
+ * out to `unzip` and `tar`. A dependency for two hundred lines of well-specified
8
+ * format is a poor trade, and shelling out makes the install fail on a machine
9
+ * that has one of the two but not the other, which is exactly the machine a
10
+ * vendored-tool installer exists to serve. Both readers are deliberately narrow:
11
+ * they read the members the registry names and refuse anything they do not
12
+ * understand rather than guessing.
13
+ *
14
+ * The bytes reaching here have already been checksum-verified against the pin,
15
+ * so these are not parsing hostile input. The size cap and the path checks are
16
+ * belt and braces for the day a pin is updated carelessly.
17
+ */
18
+
19
+ /** One file read out of an archive. */
20
+ export interface ArchiveEntry {
21
+ path: string;
22
+ data: Buffer;
23
+ /** Unix mode when the archive records one, otherwise null. */
24
+ mode: number | null;
25
+ }
26
+
27
+ /** Refuse to materialize more than this from one asset. */
28
+ const MAX_TOTAL_UNCOMPRESSED_BYTES = 512 * 1024 * 1024;
29
+
30
+ const ZIP_EOCD_SIGNATURE = 0x06054b50;
31
+ const ZIP_CENTRAL_SIGNATURE = 0x02014b50;
32
+ const ZIP_LOCAL_SIGNATURE = 0x04034b50;
33
+ const ZIP_STORED = 0;
34
+ const ZIP_DEFLATED = 8;
35
+
36
+ /** Every regular file in a zip, keyed by its path inside the archive. */
37
+ export function readZipEntries(buffer: Buffer): Map<string, ArchiveEntry> {
38
+ const eocd = findEndOfCentralDirectory(buffer);
39
+ if (eocd < 0) throw new Error("not a zip archive: end-of-central-directory record not found");
40
+ const entryCount = buffer.readUInt16LE(eocd + 10);
41
+ let cursor = buffer.readUInt32LE(eocd + 16);
42
+ const entries = new Map<string, ArchiveEntry>();
43
+ let total = 0;
44
+
45
+ for (let i = 0; i < entryCount; i += 1) {
46
+ if (cursor + 46 > buffer.length || buffer.readUInt32LE(cursor) !== ZIP_CENTRAL_SIGNATURE) {
47
+ throw new Error(`zip central directory is malformed at entry ${i}`);
48
+ }
49
+ const method = buffer.readUInt16LE(cursor + 10);
50
+ const compressedSize = buffer.readUInt32LE(cursor + 20);
51
+ const uncompressedSize = buffer.readUInt32LE(cursor + 24);
52
+ const nameLength = buffer.readUInt16LE(cursor + 28);
53
+ const extraLength = buffer.readUInt16LE(cursor + 30);
54
+ const commentLength = buffer.readUInt16LE(cursor + 32);
55
+ const externalAttributes = buffer.readUInt32LE(cursor + 38);
56
+ const localOffset = buffer.readUInt32LE(cursor + 42);
57
+ const name = buffer.toString("utf8", cursor + 46, cursor + 46 + nameLength);
58
+ cursor += 46 + nameLength + extraLength + commentLength;
59
+
60
+ if (name.endsWith("/")) continue;
61
+ assertSafeMember(name);
62
+ total += uncompressedSize;
63
+ if (total > MAX_TOTAL_UNCOMPRESSED_BYTES) throw new Error("zip archive expands beyond the size cap");
64
+
65
+ if (localOffset + 30 > buffer.length || buffer.readUInt32LE(localOffset) !== ZIP_LOCAL_SIGNATURE) {
66
+ throw new Error(`zip local header is malformed for ${name}`);
67
+ }
68
+ const localNameLength = buffer.readUInt16LE(localOffset + 26);
69
+ const localExtraLength = buffer.readUInt16LE(localOffset + 28);
70
+ const dataStart = localOffset + 30 + localNameLength + localExtraLength;
71
+ const raw = buffer.subarray(dataStart, dataStart + compressedSize);
72
+ const data =
73
+ method === ZIP_STORED
74
+ ? Buffer.from(raw)
75
+ : method === ZIP_DEFLATED
76
+ ? inflateRawSync(raw)
77
+ : (() => {
78
+ throw new Error(`unsupported zip compression method ${method} for ${name}`);
79
+ })();
80
+ // The high 16 bits of the external attributes carry the unix mode when the
81
+ // archive was produced on a unix host, which is where the executable bit
82
+ // on a release binary lives.
83
+ const unixMode = (externalAttributes >>> 16) & 0o7777;
84
+ entries.set(name, { path: name, data, mode: unixMode === 0 ? null : unixMode });
85
+ }
86
+ return entries;
87
+ }
88
+
89
+ /** Every regular file in a gzipped tar, keyed by its path inside the archive. */
90
+ export function readTarGzEntries(buffer: Buffer): Map<string, ArchiveEntry> {
91
+ const tar = gunzipSync(buffer);
92
+ const entries = new Map<string, ArchiveEntry>();
93
+ let offset = 0;
94
+ let total = 0;
95
+ let pendingLongName: string | null = null;
96
+
97
+ while (offset + 512 <= tar.length) {
98
+ const header = tar.subarray(offset, offset + 512);
99
+ // Two consecutive zero blocks end the archive; one is enough to stop.
100
+ if (header.every((byte) => byte === 0)) break;
101
+ const rawName = readTarString(header, 0, 100);
102
+ const prefix = readTarString(header, 345, 155);
103
+ const size = readTarOctal(header, 124, 12);
104
+ const mode = readTarOctal(header, 100, 8);
105
+ const typeFlag = String.fromCharCode(header[156] ?? 0);
106
+ const dataStart = offset + 512;
107
+ const padded = Math.ceil(size / 512) * 512;
108
+ offset = dataStart + padded;
109
+
110
+ // GNU long-name extension: this block's payload is the next block's name.
111
+ if (typeFlag === "L") {
112
+ pendingLongName = tar.toString("utf8", dataStart, dataStart + size).replace(/\0+$/, "");
113
+ continue;
114
+ }
115
+ // PAX headers and directories carry no file content Clio wants.
116
+ if (typeFlag === "x" || typeFlag === "g" || typeFlag === "5") {
117
+ pendingLongName = null;
118
+ continue;
119
+ }
120
+ if (typeFlag !== "0" && typeFlag !== "\0") {
121
+ pendingLongName = null;
122
+ continue;
123
+ }
124
+
125
+ const name = pendingLongName ?? (prefix.length > 0 ? `${prefix}/${rawName}` : rawName);
126
+ pendingLongName = null;
127
+ if (name.length === 0 || name.endsWith("/")) continue;
128
+ assertSafeMember(name);
129
+ total += size;
130
+ if (total > MAX_TOTAL_UNCOMPRESSED_BYTES) throw new Error("tar archive expands beyond the size cap");
131
+ entries.set(name, {
132
+ path: name,
133
+ data: Buffer.from(tar.subarray(dataStart, dataStart + size)),
134
+ mode: mode === 0 ? null : mode & 0o7777,
135
+ });
136
+ }
137
+ return entries;
138
+ }
139
+
140
+ function findEndOfCentralDirectory(buffer: Buffer): number {
141
+ // The record is at the very end unless a zip comment follows it, and the
142
+ // comment length field is 16 bits, so 22 + 65535 bytes is the whole search.
143
+ const start = Math.max(0, buffer.length - (22 + 0xffff));
144
+ for (let i = buffer.length - 22; i >= start; i -= 1) {
145
+ if (buffer.readUInt32LE(i) === ZIP_EOCD_SIGNATURE) return i;
146
+ }
147
+ return -1;
148
+ }
149
+
150
+ function readTarString(header: Buffer, offset: number, length: number): string {
151
+ const slice = header.subarray(offset, offset + length);
152
+ const end = slice.indexOf(0);
153
+ return slice.toString("utf8", 0, end === -1 ? slice.length : end);
154
+ }
155
+
156
+ function readTarOctal(header: Buffer, offset: number, length: number): number {
157
+ const text = readTarString(header, offset, length).trim();
158
+ if (text.length === 0) return 0;
159
+ const value = Number.parseInt(text, 8);
160
+ return Number.isNaN(value) ? 0 : value;
161
+ }
162
+
163
+ /**
164
+ * An archive member's path is used to look up a registry-named member, never to
165
+ * build a write path, but a member escaping the archive root still means the
166
+ * asset is not what the pin says it is.
167
+ */
168
+ function assertSafeMember(name: string): void {
169
+ if (name.startsWith("/") || name.startsWith("\\") || /^[A-Za-z]:/.test(name)) {
170
+ throw new Error(`archive member has an absolute path: ${name}`);
171
+ }
172
+ if (name.split(/[/\\]/).includes("..")) {
173
+ throw new Error(`archive member escapes the archive root: ${name}`);
174
+ }
175
+ }
@@ -0,0 +1,28 @@
1
+ import type { DomainContract } from "../../core/domain-loader.js";
2
+ import type { ToolInstallOptions, ToolInstallResult } from "./install.js";
3
+ import type { ToolRemoveOptions, ToolRemoveResult } from "./remove.js";
4
+ import type { PinnedTool, ToolResolution, ToolStatus } from "./types.js";
5
+
6
+ export interface ToolchainContract extends DomainContract {
7
+ /** The pinned table, in registry order. */
8
+ list(): ReadonlyArray<PinnedTool>;
9
+ /** One row by id. */
10
+ get(id: string): PinnedTool | null;
11
+ /** Resolve an executable name through PATH, then the vendored pin, then none. */
12
+ resolve(name: string): ToolResolution;
13
+ /** Per-entry state for one row. */
14
+ status(id: string): ToolStatus | null;
15
+ /** Per-entry state for every row. */
16
+ statuses(): ToolStatus[];
17
+ /**
18
+ * Download and vendor one tool. Only ever called from an explicit operator
19
+ * command; nothing on a startup path may reach it.
20
+ */
21
+ install(id: string, options?: ToolInstallOptions): Promise<ToolInstallResult>;
22
+ /**
23
+ * Delete every vendored version of one tool. Nothing outside
24
+ * `<data>/tools/<id>` is reachable, and a tool with nothing installed is a
25
+ * successful no-op rather than an error.
26
+ */
27
+ remove(id: string, options?: ToolRemoveOptions): ToolRemoveResult;
28
+ }
@@ -0,0 +1,47 @@
1
+ import type { DomainBundle, DomainContext, DomainExtension } from "../../core/domain-loader.js";
2
+ import type { ToolchainContract } from "./contract.js";
3
+ import type { ToolInstallOptions } from "./install.js";
4
+ import { findPinnedTool, PINNED_TOOLS } from "./registry.js";
5
+ import { removeTool, type ToolRemoveOptions } from "./remove.js";
6
+ import { resolveToolBinary, toolStatus, toolStatuses } from "./resolve.js";
7
+
8
+ export function createToolchainBundle(_context: DomainContext): DomainBundle<ToolchainContract> {
9
+ const extension: DomainExtension = {
10
+ start() {
11
+ // Nothing to start. Resolution is on demand and installs are operator
12
+ // commands, so loading the domain touches neither the network nor disk.
13
+ return undefined;
14
+ },
15
+ };
16
+ const contract: ToolchainContract = {
17
+ list() {
18
+ return PINNED_TOOLS;
19
+ },
20
+ get(id) {
21
+ return findPinnedTool(id);
22
+ },
23
+ resolve(name) {
24
+ return resolveToolBinary(name);
25
+ },
26
+ status(id) {
27
+ const entry = findPinnedTool(id);
28
+ return entry === null ? null : toolStatus(entry);
29
+ },
30
+ statuses() {
31
+ return toolStatuses();
32
+ },
33
+ async install(id: string, options: ToolInstallOptions = {}) {
34
+ // Imported lazily so the installer's archive readers stay out of every
35
+ // graph that only ever resolves a binary.
36
+ const { installTool } = await import("./install.js");
37
+ return installTool(id, options);
38
+ },
39
+ remove(id: string, options: ToolRemoveOptions = {}) {
40
+ // Statically imported, unlike install: removal is a readdir and an
41
+ // unlink, so it carries none of the archive readers that keep the
42
+ // installer off graphs which only ever resolve a binary.
43
+ return removeTool(id, options);
44
+ },
45
+ };
46
+ return { extension, contract };
47
+ }
@@ -0,0 +1,39 @@
1
+ import type { DomainModule } from "../../core/domain-loader.js";
2
+ import type { ToolchainContract } from "./contract.js";
3
+ import { createToolchainBundle } from "./extension.js";
4
+ import { ToolchainManifest } from "./manifest.js";
5
+
6
+ export const ToolchainDomainModule: DomainModule<ToolchainContract> = {
7
+ manifest: ToolchainManifest,
8
+ createExtension: createToolchainBundle,
9
+ };
10
+
11
+ export type { ToolchainContract } from "./contract.js";
12
+ export type { ToolFetcher, ToolInstallOptions, ToolInstallResult } from "./install.js";
13
+ export { installPinnedTool, installTool } from "./install.js";
14
+ export { ToolchainManifest } from "./manifest.js";
15
+ export { toolchainRoot, toolVersionDir, vendoredBinaryPath } from "./paths.js";
16
+ export { currentToolPlatform, findPinnedTool, findPinnedToolByBinary, PINNED_TOOLS } from "./registry.js";
17
+ export type { ToolRemoveOptions, ToolRemoveResult } from "./remove.js";
18
+ export { installedToolVersions, pruneSupersededVersions, removeTool, STALE_STAGING_MS } from "./remove.js";
19
+ export {
20
+ describeFloorRejection,
21
+ describeResolution,
22
+ installRemedy,
23
+ resolveEntryBinary,
24
+ resolveToolBinary,
25
+ toolStatus,
26
+ toolStatuses,
27
+ } from "./resolve.js";
28
+ export type {
29
+ PinnedTool,
30
+ PinnedToolDocument,
31
+ PinnedToolDownload,
32
+ ToolArchiveKind,
33
+ ToolPathCandidate,
34
+ ToolPlatform,
35
+ ToolResolution,
36
+ ToolSource,
37
+ ToolStatus,
38
+ } from "./types.js";
39
+ export { compareVersions, parseVersion, resetVersionProbeCache, satisfiesMinimum } from "./version.js";
@@ -0,0 +1,327 @@
1
+ import { createHash } from "node:crypto";
2
+ import { chmodSync, existsSync, mkdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
3
+ import { basename, join } from "node:path";
4
+ import { type ArchiveEntry, readTarGzEntries, readZipEntries } from "./archive.js";
5
+ import { ensureToolchainRoot } from "./paths.js";
6
+ import { currentToolPlatform, findPinnedTool } from "./registry.js";
7
+ import { pruneSupersededVersions } from "./remove.js";
8
+ import type { PinnedTool, PinnedToolDownload, ToolPlatform } from "./types.js";
9
+
10
+ /**
11
+ * Install a pinned tool into the vendor root.
12
+ *
13
+ * Three rules shape this file. Nothing downloads unless an operator asked for
14
+ * this install by name, so no startup path can reach it. Nothing is written
15
+ * until the asset's checksum matches the pin, and a mismatch leaves the disk
16
+ * exactly as it was. And the version directory appears atomically, built in a
17
+ * sibling staging directory and renamed into place, so a killed install leaves
18
+ * a `.incomplete-` directory rather than a half-populated version that the
19
+ * resolution ladder would happily run.
20
+ *
21
+ * A successful install then prunes, so a tool holds exactly the pinned version
22
+ * and nothing else. Only the ladder's own version is ever resolved, so a
23
+ * superseded directory is dead weight an operator would have to find and delete
24
+ * by hand. Pruning runs on the already-installed path too, which is what
25
+ * repairs a machine that bumped pins before this behavior existed.
26
+ */
27
+
28
+ /** How a URL becomes bytes. Injectable so the contract tests never touch the network. */
29
+ export type ToolFetcher = (url: string) => Promise<Buffer>;
30
+
31
+ export interface ToolInstallOptions {
32
+ /** Re-download and replace an already-installed version. */
33
+ force?: boolean;
34
+ /** Install the asset for a platform other than the running one. */
35
+ platform?: ToolPlatform;
36
+ /** Vendor root override. Defaults to the data root's `tools` directory. */
37
+ root?: string;
38
+ fetch?: ToolFetcher;
39
+ onProgress?: (message: string) => void;
40
+ }
41
+
42
+ export interface ToolInstallResult {
43
+ ok: boolean;
44
+ id: string;
45
+ version: string;
46
+ /** Where the version directory is, whether or not this run created it. */
47
+ dir: string;
48
+ /** Absolute paths of the installed executables. */
49
+ binaries: string[];
50
+ /** Absolute paths of the license and notice files placed beside them. */
51
+ documents: string[];
52
+ /** True when the version was already installed and nothing was downloaded. */
53
+ skipped: boolean;
54
+ /** Superseded versions of the same tool this install deleted. */
55
+ pruned: string[];
56
+ message: string;
57
+ }
58
+
59
+ /** How long one asset download may take. */
60
+ const DOWNLOAD_TIMEOUT_MS = 300_000;
61
+
62
+ /** Install by registry id. */
63
+ export async function installTool(id: string, options: ToolInstallOptions = {}): Promise<ToolInstallResult> {
64
+ const entry = findPinnedTool(id);
65
+ if (entry === null) {
66
+ return failure(id, "unknown", "", `unknown tool: ${id}`);
67
+ }
68
+ return installPinnedTool(entry, options);
69
+ }
70
+
71
+ /** Install a registry row. Exported so tests can drive a fabricated entry. */
72
+ export async function installPinnedTool(
73
+ entry: PinnedTool,
74
+ options: ToolInstallOptions = {},
75
+ ): Promise<ToolInstallResult> {
76
+ const root = options.root ?? ensureToolchainRoot();
77
+ const dir = join(root, entry.id, entry.version);
78
+ const platform = options.platform ?? currentToolPlatform();
79
+ const progress = options.onProgress ?? (() => undefined);
80
+
81
+ if (platform === null) {
82
+ return failure(entry.id, entry.version, dir, `no pinned asset for ${process.platform}-${process.arch}`);
83
+ }
84
+ const download = entry.downloads[platform];
85
+ if (download === undefined) {
86
+ return failure(entry.id, entry.version, dir, `no pinned asset for ${platform}`);
87
+ }
88
+
89
+ const installedBinaries = Object.keys(download.binaryMembers).map((name) => join(dir, executableName(name, platform)));
90
+ if (!options.force && installedBinaries.every((path) => existsSync(path))) {
91
+ const swept = prune(root, entry);
92
+ return {
93
+ ok: true,
94
+ id: entry.id,
95
+ version: entry.version,
96
+ dir,
97
+ binaries: installedBinaries,
98
+ documents: [],
99
+ skipped: true,
100
+ pruned: swept.pruned,
101
+ message: `${entry.id} ${entry.version} is already installed at ${dir}${swept.note}`,
102
+ };
103
+ }
104
+
105
+ const fetcher = options.fetch ?? fetchAsset;
106
+ let assetBytes: Buffer;
107
+ try {
108
+ progress(`downloading ${download.url}`);
109
+ assetBytes = await fetcher(download.url);
110
+ } catch (error) {
111
+ return failure(entry.id, entry.version, dir, `download failed: ${messageOf(error)}`);
112
+ }
113
+
114
+ const actual = sha256(assetBytes);
115
+ if (actual !== download.sha256) {
116
+ return failure(
117
+ entry.id,
118
+ entry.version,
119
+ dir,
120
+ `checksum mismatch for ${download.url}: expected ${download.sha256}, got ${actual}. Nothing was written.`,
121
+ );
122
+ }
123
+ progress(`checksum verified (${actual})`);
124
+
125
+ const documentBytes = new Map<string, Buffer>();
126
+ for (const doc of entry.documents) {
127
+ let bytes: Buffer;
128
+ try {
129
+ progress(`downloading ${doc.url}`);
130
+ bytes = await fetcher(doc.url);
131
+ } catch (error) {
132
+ return failure(entry.id, entry.version, dir, `download failed for ${doc.name}: ${messageOf(error)}`);
133
+ }
134
+ const docHash = sha256(bytes);
135
+ if (docHash !== doc.sha256) {
136
+ return failure(
137
+ entry.id,
138
+ entry.version,
139
+ dir,
140
+ `checksum mismatch for ${doc.url}: expected ${doc.sha256}, got ${docHash}. Nothing was written.`,
141
+ );
142
+ }
143
+ documentBytes.set(doc.name, bytes);
144
+ }
145
+
146
+ let members: Map<string, ArchiveEntry>;
147
+ try {
148
+ members = unpack(assetBytes, download);
149
+ } catch (error) {
150
+ return failure(entry.id, entry.version, dir, `could not unpack the asset: ${messageOf(error)}`);
151
+ }
152
+
153
+ for (const memberPath of Object.values(download.binaryMembers)) {
154
+ if (!members.has(memberPath)) {
155
+ return failure(entry.id, entry.version, dir, `the asset does not contain ${memberPath || entry.primaryBinary}`);
156
+ }
157
+ }
158
+ for (const memberPath of download.documentMembers) {
159
+ if (!members.has(memberPath)) {
160
+ return failure(entry.id, entry.version, dir, `the asset does not contain ${memberPath}`);
161
+ }
162
+ }
163
+
164
+ const staging = join(root, entry.id, `.${entry.version}.incomplete-${process.pid}-${Date.now().toString(36)}`);
165
+ // Where a `--force` replacement parks the old version directory while the new
166
+ // one is renamed in. It is unlinked only once the new directory is in place,
167
+ // so the window where neither copy exists is a single rename wide.
168
+ const retired = join(root, entry.id, `.${entry.version}.replaced-${process.pid}-${Date.now().toString(36)}`);
169
+ let retiredOld = false;
170
+ const binaries: string[] = [];
171
+ const documents: string[] = [];
172
+ try {
173
+ mkdirSync(staging, { recursive: true });
174
+ for (const [name, memberPath] of Object.entries(download.binaryMembers)) {
175
+ const member = members.get(memberPath);
176
+ if (member === undefined) continue;
177
+ const target = join(staging, executableName(name, platform));
178
+ writeFileSync(target, member.data);
179
+ chmodSync(target, 0o755);
180
+ binaries.push(join(dir, executableName(name, platform)));
181
+ }
182
+ for (const memberPath of download.documentMembers) {
183
+ const member = members.get(memberPath);
184
+ if (member === undefined) continue;
185
+ const target = join(staging, basename(memberPath));
186
+ writeFileSync(target, member.data, { mode: 0o644 });
187
+ documents.push(join(dir, basename(memberPath)));
188
+ }
189
+ for (const [name, bytes] of documentBytes) {
190
+ writeFileSync(join(staging, name), bytes, { mode: 0o644 });
191
+ documents.push(join(dir, name));
192
+ }
193
+ writeFileSync(
194
+ join(staging, "clio-install.json"),
195
+ `${JSON.stringify(
196
+ {
197
+ id: entry.id,
198
+ version: entry.version,
199
+ platform,
200
+ license: entry.license,
201
+ homepage: entry.homepage,
202
+ url: download.url,
203
+ sha256: download.sha256,
204
+ installedAt: new Date().toISOString(),
205
+ },
206
+ null,
207
+ 2,
208
+ )}\n`,
209
+ { mode: 0o644 },
210
+ );
211
+ if (options.force && existsSync(dir)) {
212
+ renameSync(dir, retired);
213
+ retiredOld = true;
214
+ }
215
+ renameSync(staging, dir);
216
+ if (retiredOld) {
217
+ rmSync(retired, { recursive: true, force: true });
218
+ retiredOld = false;
219
+ }
220
+ } catch (error) {
221
+ rmSync(staging, { recursive: true, force: true });
222
+ // The new version never landed, so put the copy the operator had back.
223
+ if (retiredOld && !existsSync(dir)) {
224
+ try {
225
+ renameSync(retired, dir);
226
+ retiredOld = false;
227
+ } catch {
228
+ // Leave the retired directory in place: it is the only surviving copy.
229
+ }
230
+ }
231
+ // A rename losing to a concurrent installer is not a failure: the version
232
+ // directory the caller asked for exists and holds the same pinned bytes.
233
+ if (!options.force && existsSync(dir)) {
234
+ const swept = prune(root, entry);
235
+ return {
236
+ ok: true,
237
+ id: entry.id,
238
+ version: entry.version,
239
+ dir,
240
+ binaries: installedBinaries,
241
+ documents: [],
242
+ skipped: true,
243
+ pruned: swept.pruned,
244
+ message: `${entry.id} ${entry.version} was installed concurrently at ${dir}${swept.note}`,
245
+ };
246
+ }
247
+ return failure(entry.id, entry.version, dir, `install failed: ${messageOf(error)}`);
248
+ }
249
+
250
+ const swept = prune(root, entry);
251
+ return {
252
+ ok: true,
253
+ id: entry.id,
254
+ version: entry.version,
255
+ dir,
256
+ binaries,
257
+ documents,
258
+ skipped: false,
259
+ pruned: swept.pruned,
260
+ message: `installed ${entry.id} ${entry.version} (${entry.license}) at ${dir}${swept.note}`,
261
+ };
262
+ }
263
+
264
+ /**
265
+ * Delete the versions this pin supersedes, and say what happened.
266
+ *
267
+ * A prune that cannot delete something never fails the install: the version the
268
+ * operator asked for is in place and runnable, and a directory that resisted
269
+ * `rm` is a disk problem to report, not a reason to claim the install did not
270
+ * happen. It is named in the message so the operator can go look.
271
+ */
272
+ function prune(root: string, entry: PinnedTool): { pruned: string[]; note: string } {
273
+ const outcome = pruneSupersededVersions(entry.id, entry.version, { root });
274
+ const parts: string[] = [];
275
+ if (outcome.removed.length > 0) parts.push(`pruned ${outcome.removed.join(", ")}`);
276
+ if (outcome.staleStaging.length > 0) {
277
+ const count = outcome.staleStaging.length;
278
+ parts.push(`swept ${count} abandoned install ${count === 1 ? "directory" : "directories"}`);
279
+ }
280
+ if (outcome.failed.length > 0) {
281
+ parts.push(`could not prune ${outcome.failed.map((item) => `${item.version} (${item.error})`).join(", ")}`);
282
+ }
283
+ return { pruned: outcome.removed, note: parts.length === 0 ? "" : `; ${parts.join("; ")}` };
284
+ }
285
+
286
+ function unpack(bytes: Buffer, download: PinnedToolDownload): Map<string, ArchiveEntry> {
287
+ if (download.archive === "raw") {
288
+ // The asset is the binary. The registry names it with an empty member
289
+ // path, so the synthetic map key matches what the caller looks up.
290
+ return new Map([["", { path: "", data: bytes, mode: 0o755 }]]);
291
+ }
292
+ if (download.archive === "zip") return readZipEntries(bytes);
293
+ return readTarGzEntries(bytes);
294
+ }
295
+
296
+ async function fetchAsset(url: string): Promise<Buffer> {
297
+ const response = await fetch(url, {
298
+ redirect: "follow",
299
+ signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS),
300
+ });
301
+ if (!response.ok) throw new Error(`HTTP ${response.status} ${response.statusText}`);
302
+ return Buffer.from(await response.arrayBuffer());
303
+ }
304
+
305
+ function sha256(bytes: Buffer): string {
306
+ return createHash("sha256").update(bytes).digest("hex");
307
+ }
308
+
309
+ /**
310
+ * What a binary is called once installed, for the platform being installed.
311
+ *
312
+ * Keyed off the target rather than `process.platform`, because `--platform`
313
+ * exists: installing the win32 asset from Linux has to write `herdr.exe`, and
314
+ * writing `herdr` there would produce a directory Windows could never run and
315
+ * that the resolution ladder on Windows would report as not installed.
316
+ */
317
+ function executableName(name: string, platform: ToolPlatform): string {
318
+ return platform.startsWith("win32") ? `${name}.exe` : name;
319
+ }
320
+
321
+ function failure(id: string, version: string, dir: string, message: string): ToolInstallResult {
322
+ return { ok: false, id, version, dir, binaries: [], documents: [], skipped: false, pruned: [], message };
323
+ }
324
+
325
+ function messageOf(error: unknown): string {
326
+ return error instanceof Error ? error.message : String(error);
327
+ }
@@ -0,0 +1,8 @@
1
+ import type { DomainManifest } from "../../core/domain-loader.js";
2
+
3
+ export const ToolchainManifest: DomainManifest = {
4
+ name: "toolchain",
5
+ // The registry is a static table plus filesystem lookups under the data
6
+ // root. It reads no settings and calls no other domain.
7
+ dependsOn: [],
8
+ };
@@ -0,0 +1,34 @@
1
+ import { join } from "node:path";
2
+ import { clioDataDir, resolveClioDirs } from "../../core/xdg.js";
3
+
4
+ /**
5
+ * Vendored tools are durable artifacts Clio downloaded on request, so they live
6
+ * under the data root beside memory and evidence, not under state (machine
7
+ * output) or cache (disposable). The version is part of the path so a pin bump
8
+ * installs beside the old copy instead of over it, and a rollback is a path
9
+ * change rather than a re-download.
10
+ *
11
+ * Every reader resolves through `resolveClioDirs()` rather than `clioDataDir()`
12
+ * because the latter creates the root, and doctor and the resolution ladder both
13
+ * promise to create nothing. Only the installer creates.
14
+ */
15
+
16
+ /** `<data>/tools`, without creating it. */
17
+ export function toolchainRoot(): string {
18
+ return join(resolveClioDirs().data, "tools");
19
+ }
20
+
21
+ /** `<data>/tools/<id>/<version>`, without creating it. */
22
+ export function toolVersionDir(id: string, version: string): string {
23
+ return join(toolchainRoot(), id, version);
24
+ }
25
+
26
+ /** `<data>/tools/<id>/<version>/<binary>`, without creating it. */
27
+ export function vendoredBinaryPath(id: string, version: string, binary: string): string {
28
+ return join(toolVersionDir(id, version), process.platform === "win32" ? `${binary}.exe` : binary);
29
+ }
30
+
31
+ /** `<data>/tools`, creating the data root the way every other writer does. */
32
+ export function ensureToolchainRoot(): string {
33
+ return join(clioDataDir(), "tools");
34
+ }