@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,810 @@
1
+ /**
2
+ * `clio-coder fleet view <runId|fleetRootId> [--follow]`.
3
+ *
4
+ * Given a fleet root id it renders a step index instead; see the fleet root
5
+ * index section below for why a root cannot be rendered as a run.
6
+ *
7
+ * A read-only viewer for one dispatched run, built from durable state only:
8
+ * the run ledger envelope, the run event journal
9
+ * (src/domains/dispatch/run-event-journal.ts), and the sealed receipt once one
10
+ * exists. It shares no memory with the orchestrator, has zero dependency on any
11
+ * pane host, and works over a plain SSH session.
12
+ *
13
+ * Everything the viewer prints comes from `renderRunView`, a pure function over
14
+ * a `RunViewModel`. `--follow` paints that same output into an alternate screen
15
+ * and re-polls the journal at the monitor tool's `WAIT_POLL_MS` cadence until
16
+ * the journal's terminal line lands; it then stays open until `q`. Without
17
+ * `--follow` the snapshot goes to stdout and the process exits.
18
+ *
19
+ * Trust rules the render obeys:
20
+ * - The receipt is authenticated against the ledger envelope through
21
+ * `inspectRunReceiptTrustStatus` before any of its fields are shown. A
22
+ * receipt that fails integrity contributes its failure reason and nothing
23
+ * else.
24
+ * - Task text is operator-supplied and worker-adjacent, so it is sanitized
25
+ * and bounded before rendering, per the `DispatchRunIdentity` warning in
26
+ * src/core/bus-events.ts:524. Journal event details cross the same seam and
27
+ * get the same treatment.
28
+ *
29
+ * Terminal primitives come from src/engine/tui-primitives.ts, never from the
30
+ * src/engine/tui.js barrel, and that is load-bearing. See the header of
31
+ * tui-primitives.ts for why an edge from here to the barrel breaks the Stage 0
32
+ * instant-shell chunk budget.
33
+ */
34
+
35
+ import { readdirSync, readFileSync } from "node:fs";
36
+ import { join } from "node:path";
37
+ import { clioStateDir, resetXdgCache } from "../core/xdg.js";
38
+ import type { ExecutionStepResult } from "../domains/dispatch/execution-scheduler.js";
39
+ import {
40
+ type RunEventJournalLine,
41
+ readRunEventJournal,
42
+ runEventJournalPath,
43
+ } from "../domains/dispatch/run-event-journal.js";
44
+ import { openLedger, readFleetRun } from "../domains/dispatch/state.js";
45
+ import type { RunEnvelope, RunReceipt } from "../domains/dispatch/types.js";
46
+ import { WRITE_BOUNDARY_VIOLATION_REASON } from "../domains/dispatch/write-boundary.js";
47
+ import { formatTrustSummaryLine } from "../domains/evidence/trust-projection.js";
48
+ import { inspectRunReceiptTrustStatus } from "../domains/evidence/trust-status.js";
49
+ import { sanitizeCallTargetText } from "../domains/safety/call-target.js";
50
+ import { truncateToWidth } from "../engine/tui-primitives.js";
51
+
52
+ const HELP = `clio-coder fleet view <runId|fleetRootId> [--follow]
53
+ clio-coder fleet view --watch <selection-file>
54
+
55
+ Follow one dispatched run from its durable state: the run ledger entry, the
56
+ run event journal, and the sealed receipt once it exists.
57
+
58
+ --follow keep tailing until the run's terminal line, then stay open (q exits)
59
+ --watch follow whichever run id the selection file names, retargeting
60
+ live as the file changes (q exits). This is the process the
61
+ interactive workers view (Alt+W) runs inside its watch pane.
62
+
63
+ Without --follow the current snapshot is printed and the command exits.
64
+ The transcript comes from <state>/runs/<runId>/events.ndjson, which the
65
+ orchestrator writes when panes.journal is on (the default). A run dispatched
66
+ with the journal off shows its ledger and receipt but no transcript.
67
+
68
+ Given the fleet root id that \`fleet run\` prints (fleet-<hex>), this prints the
69
+ run's step index instead: one line per step with its run id and outcome. Pass
70
+ one of those run ids back to see that step.
71
+ `;
72
+
73
+ /** Journal poll cadence, matching WAIT_POLL_MS in src/tools/monitor.ts. */
74
+ const POLL_MS = 250;
75
+ /** Transcript lines kept in the model. Older lines are dropped from the head. */
76
+ const TRANSCRIPT_LIMIT = 400;
77
+ /** Visible width cap for one sanitized task line before the renderer wraps it. */
78
+ const TASK_MAX_WIDTH = 400;
79
+ /** Visible width cap for one sanitized journal detail. */
80
+ const DETAIL_MAX_WIDTH = 200;
81
+ const DEFAULT_WIDTH = 100;
82
+ const MIN_WIDTH = 40;
83
+ /** Raw-mode Ctrl-C arrives as a byte, not as SIGINT, so the follow loop matches it. */
84
+ const QUIT_CTRL_C = String.fromCharCode(3);
85
+ const QUIT_ESCAPE = String.fromCharCode(27);
86
+
87
+ // ---------------------------------------------------------------------------
88
+ // Model
89
+ // ---------------------------------------------------------------------------
90
+
91
+ export interface RunViewTranscriptLine {
92
+ at: string;
93
+ label: string;
94
+ detail: string | undefined;
95
+ }
96
+
97
+ export interface RunViewModel {
98
+ runId: string;
99
+ agentId: string;
100
+ model: string;
101
+ target: string;
102
+ node: string;
103
+ phase: string;
104
+ startedAt: string;
105
+ elapsedMs: number;
106
+ task: string | undefined;
107
+ transcript: RunViewTranscriptLine[];
108
+ /** True when the writer dropped display lines or the reader skipped a head. */
109
+ transcriptTruncated: boolean;
110
+ journalPresent: boolean;
111
+ journalPath: string;
112
+ /** Authenticated receipt status line, or the reason there is not one. */
113
+ evidence: string;
114
+ receiptPath: string | null;
115
+ outcome: string | null;
116
+ outcomeDetail: string | null;
117
+ /** True once the journal carries its terminal line or the ledger row ended. */
118
+ terminal: boolean;
119
+ }
120
+
121
+ function sanitizeBounded(value: string, maxWidth: number): string {
122
+ const sanitized = sanitizeCallTargetText(value);
123
+ if (sanitized.length === 0) return "";
124
+ // truncateToWidth appends reset sequences when it elides, so the result goes
125
+ // back through the sanitizer and stays plain text.
126
+ return sanitizeCallTargetText(truncateToWidth(sanitized, maxWidth, "…", false));
127
+ }
128
+
129
+ function receiptPathFor(run: RunEnvelope): string {
130
+ return run.receiptPath ?? join(clioStateDir(), "receipts", `${run.id}.json`);
131
+ }
132
+
133
+ /**
134
+ * Authenticate the receipt, then reduce it to one status line in the shared
135
+ * trust vocabulary. Nothing from an unauthenticated receipt reaches the line
136
+ * beyond the reason it could not be trusted.
137
+ */
138
+ function evidenceLine(run: RunEnvelope): { text: string; receiptPath: string | null } {
139
+ if (run.receiptPath === null && run.endedAt === null) {
140
+ return { text: "receipt pending; the run has not finalized", receiptPath: null };
141
+ }
142
+ const path = receiptPathFor(run);
143
+ let receipt: RunReceipt;
144
+ try {
145
+ receipt = JSON.parse(readFileSync(path, "utf8")) as RunReceipt;
146
+ } catch (error) {
147
+ return {
148
+ text: `receipt unavailable: cannot read ${path} (${error instanceof Error ? error.message : String(error)})`,
149
+ receiptPath: null,
150
+ };
151
+ }
152
+ const inspection = inspectRunReceiptTrustStatus(receipt, run);
153
+ if (!inspection.integrity.ok) {
154
+ return {
155
+ text: `RECEIPT INTEGRITY FAILED: ${inspection.integrity.reason}; stored receipt fields are untrusted`,
156
+ receiptPath: path,
157
+ };
158
+ }
159
+ return { text: formatTrustSummaryLine(inspection.status), receiptPath: path };
160
+ }
161
+
162
+ function transcriptLine(line: RunEventJournalLine): RunViewTranscriptLine | null {
163
+ switch (line.kind) {
164
+ case "open":
165
+ return { at: line.at, label: `run opened (${sanitizeBounded(line.agentId, 64)})`, detail: undefined };
166
+ case "event":
167
+ return {
168
+ at: line.at,
169
+ label: sanitizeBounded(line.type, 64),
170
+ detail: line.detail === undefined ? undefined : sanitizeBounded(line.detail, DETAIL_MAX_WIDTH),
171
+ };
172
+ case "journal_truncated":
173
+ return { at: line.at, label: "journal truncated", detail: sanitizeBounded(line.reason, 64) };
174
+ case "receipt":
175
+ return {
176
+ at: line.at,
177
+ label: `receipt sealed (${sanitizeBounded(line.outcome, 32)})`,
178
+ detail: line.digest === undefined ? undefined : `sha256:${sanitizeBounded(line.digest, 64)}`,
179
+ };
180
+ case "terminal":
181
+ return {
182
+ at: line.at,
183
+ label: `terminal (${sanitizeBounded(line.outcome, 32)})`,
184
+ detail: line.detail === undefined ? undefined : sanitizeBounded(line.detail, DETAIL_MAX_WIDTH),
185
+ };
186
+ default:
187
+ return null;
188
+ }
189
+ }
190
+
191
+ export interface LoadRunViewOptions {
192
+ /** Journal root override; defaults to `<state>/runs`. */
193
+ journalRoot?: string;
194
+ now?: () => number;
195
+ }
196
+
197
+ /**
198
+ * Resolve a run id the way `fleet status` prints them: an exact ledger id
199
+ * first, then a unique prefix. An ambiguous prefix resolves to nothing so the
200
+ * caller can say which ids it matched.
201
+ */
202
+ export function resolveRunId(runId: string): { runId: string } | { candidates: string[] } {
203
+ const ledger = openLedger();
204
+ if (ledger.get(runId) !== null) return { runId };
205
+ const candidates = ledger
206
+ .list()
207
+ .filter((run) => run.id.startsWith(runId))
208
+ .map((run) => run.id);
209
+ if (candidates.length === 1 && candidates[0] !== undefined) return { runId: candidates[0] };
210
+ return { candidates };
211
+ }
212
+
213
+ export function loadRunViewModel(runId: string, options: LoadRunViewOptions = {}): RunViewModel | null {
214
+ const run = openLedger().get(runId);
215
+ if (run === null) return null;
216
+ const now = options.now ?? (() => Date.now());
217
+ const journal = readRunEventJournal(runId, {
218
+ maxLines: TRANSCRIPT_LIMIT,
219
+ ...(options.journalRoot === undefined ? {} : { root: options.journalRoot }),
220
+ });
221
+ const transcript: RunViewTranscriptLine[] = [];
222
+ for (const line of journal.lines) {
223
+ const rendered = transcriptLine(line);
224
+ if (rendered !== null) transcript.push(rendered);
225
+ }
226
+ const evidence = evidenceLine(run);
227
+ const startedMs = Date.parse(run.startedAt);
228
+ const endedMs = run.endedAt === null ? now() : Date.parse(run.endedAt);
229
+ const task = sanitizeBounded(run.task, TASK_MAX_WIDTH);
230
+ return {
231
+ runId: run.id,
232
+ agentId: run.agentId,
233
+ model: run.wireModelId,
234
+ target: run.targetId,
235
+ node: run.node?.id ?? "local",
236
+ phase: run.outcome ?? run.status,
237
+ startedAt: run.startedAt,
238
+ elapsedMs: Number.isFinite(startedMs) && Number.isFinite(endedMs) ? Math.max(0, endedMs - startedMs) : 0,
239
+ task: task.length > 0 ? task : undefined,
240
+ transcript,
241
+ transcriptTruncated: journal.truncated,
242
+ journalPresent: journal.present,
243
+ journalPath: runEventJournalPath(runId, options.journalRoot === undefined ? undefined : options.journalRoot),
244
+ evidence: evidence.text,
245
+ receiptPath: evidence.receiptPath,
246
+ outcome: journal.terminal?.outcome ?? run.outcome ?? null,
247
+ outcomeDetail: journal.terminal?.detail ?? run.outcomeDetail ?? null,
248
+ terminal: journal.terminal !== null || run.endedAt !== null,
249
+ };
250
+ }
251
+
252
+ // ---------------------------------------------------------------------------
253
+ // Render
254
+ // ---------------------------------------------------------------------------
255
+
256
+ function formatElapsed(ms: number): string {
257
+ const seconds = Math.floor(ms / 1000);
258
+ if (seconds < 60) return `${seconds}s`;
259
+ const minutes = Math.floor(seconds / 60);
260
+ if (minutes < 60) return `${minutes}m${String(seconds % 60).padStart(2, "0")}s`;
261
+ return `${Math.floor(minutes / 60)}h${String(minutes % 60).padStart(2, "0")}m`;
262
+ }
263
+
264
+ function clockOf(iso: string): string {
265
+ // The transcript is a within-run timeline, so the date carries no
266
+ // information the header does not already give.
267
+ const time = iso.slice(11, 19);
268
+ return time.length === 8 ? time : iso.slice(0, 8);
269
+ }
270
+
271
+ /**
272
+ * The whole viewer surface as lines. Pure: given the same model and width it
273
+ * returns the same strings, which is what lets the data-source test assert the
274
+ * header and outcome without a PTY.
275
+ */
276
+ export function renderRunView(model: RunViewModel, width: number = DEFAULT_WIDTH): string[] {
277
+ const columns = Math.max(MIN_WIDTH, width);
278
+ const rule = "─".repeat(columns);
279
+ const lines: string[] = [];
280
+ lines.push(truncateToWidth(`run ${model.runId} ${model.agentId}`, columns, "…", false));
281
+ lines.push(
282
+ truncateToWidth(
283
+ `model ${model.model} target ${model.target} node ${model.node} phase ${model.phase} elapsed ${formatElapsed(model.elapsedMs)}`,
284
+ columns,
285
+ "…",
286
+ false,
287
+ ),
288
+ );
289
+ if (model.task !== undefined) {
290
+ lines.push(truncateToWidth(`task ${model.task}`, columns, "…", false));
291
+ }
292
+ lines.push(rule);
293
+ if (!model.journalPresent) {
294
+ lines.push("no event journal for this run.");
295
+ lines.push(`expected ${model.journalPath} (panes.journal may have been off when it ran)`);
296
+ } else if (model.transcript.length === 0) {
297
+ lines.push("journal is empty; no events recorded yet.");
298
+ } else {
299
+ if (model.transcriptTruncated) lines.push("… earlier events dropped (journal truncated)");
300
+ for (const entry of model.transcript) {
301
+ const head = `${clockOf(entry.at)} ${entry.label}`;
302
+ lines.push(truncateToWidth(entry.detail === undefined ? head : `${head}: ${entry.detail}`, columns, "…", false));
303
+ }
304
+ }
305
+ lines.push(rule);
306
+ lines.push(truncateToWidth(`evidence ${model.evidence}`, columns, "…", false));
307
+ if (model.receiptPath !== null) {
308
+ lines.push(truncateToWidth(`receipt ${model.receiptPath}`, columns, "…", false));
309
+ }
310
+ const outcome =
311
+ model.outcome === null
312
+ ? model.terminal
313
+ ? "terminal, outcome not recorded"
314
+ : "running"
315
+ : model.outcomeDetail === null || model.outcomeDetail.length === 0
316
+ ? model.outcome
317
+ : `${model.outcome} (${sanitizeBounded(model.outcomeDetail, DETAIL_MAX_WIDTH)})`;
318
+ lines.push(truncateToWidth(`outcome ${outcome}`, columns, "…", false));
319
+ return lines;
320
+ }
321
+
322
+ /**
323
+ * The watch surface as lines. Pure for the same reason {@link renderRunView}
324
+ * is. A missing selection and a selection the ledger does not know yet are the
325
+ * two states the run renderer cannot say anything about, and both are normal:
326
+ * the first is the pane before any worker was entered, the second is a queued
327
+ * run the operator selected before its ledger row landed.
328
+ */
329
+ export function renderWatchView(
330
+ runId: string | null,
331
+ model: RunViewModel | null,
332
+ width: number = DEFAULT_WIDTH,
333
+ ledgerRoot: string = clioStateDir(),
334
+ ): string[] {
335
+ const columns = Math.max(MIN_WIDTH, width);
336
+ if (runId === null) {
337
+ return [
338
+ "no worker selected.",
339
+ "",
340
+ "In clio-coder, open the workers view (Alt+W), pick a run, press Enter.",
341
+ "Arrow keys there retarget this pane live.",
342
+ ];
343
+ }
344
+ if (model === null) {
345
+ const missing = `run ${sanitizeBounded(runId, 64)} is not in the run ledger under ${ledgerRoot}.`;
346
+ const wrappedMissing: string[] = [];
347
+ for (let offset = 0; offset < missing.length; offset += columns) {
348
+ wrappedMissing.push(missing.slice(offset, offset + columns));
349
+ }
350
+ return [
351
+ ...wrappedMissing,
352
+ "",
353
+ "If this run is queued, it has not started yet; this view will update when its ledger entry appears.",
354
+ "If it already started or finished, it was not found in this ledger.",
355
+ ];
356
+ }
357
+ return renderRunView(model, columns);
358
+ }
359
+
360
+ // ---------------------------------------------------------------------------
361
+ // Fleet root index
362
+ // ---------------------------------------------------------------------------
363
+
364
+ /**
365
+ * `fleet run` advertises a root id (`fleet-<hex>`) that names the whole run,
366
+ * while the ledger and this viewer are keyed by the 12-character run id of one
367
+ * dispatched step. An operator who copies the id off the end of `fleet run` was
368
+ * being told "unknown run".
369
+ *
370
+ * The root is not a run and has no ledger row, no receipt, and no journal of
371
+ * its own, so it cannot be rendered as one. What it does have is a durable
372
+ * record under `<state>/fleet-runs/<rootId>.json` listing the planned steps and,
373
+ * for each step that settled, the run id it terminated on. That is enough for an
374
+ * index: one line per step naming the run id to view next. Deliberately not a
375
+ * combined transcript. Steps interleave across waves, and splicing several runs
376
+ * into one scroll would invent an ordering the durable record does not have.
377
+ */
378
+
379
+ /** Fleet root ids are `fleet-` plus hex; see newFleetRootId in src/cli/fleet.ts. */
380
+ const FLEET_ROOT_PREFIX = "fleet-";
381
+ /** Visible cap for one sanitized step name before the index elides it. */
382
+ const STEP_ID_MAX_WIDTH = 32;
383
+
384
+ export interface FleetRunStepRow {
385
+ stepId: string;
386
+ /** The run the step terminated on, or null when the step never ran. */
387
+ runId: string | null;
388
+ agentId: string | null;
389
+ outcome: string;
390
+ detail: string | undefined;
391
+ }
392
+
393
+ export interface FleetRunViewModel {
394
+ rootId: string;
395
+ fleet: string;
396
+ startedAt: string;
397
+ elapsedMs: number;
398
+ /** True while the record carries no end stamp. */
399
+ running: boolean;
400
+ resumedFrom: string | null;
401
+ plannedSteps: number;
402
+ recordedSteps: number;
403
+ steps: FleetRunStepRow[];
404
+ }
405
+
406
+ function fleetRunsDir(): string {
407
+ // Mirrors fleetRunPath() in src/domains/dispatch/state.ts, which is private
408
+ // to that module; only the directory is needed here, for prefix resolution
409
+ // and for naming the path in a "no such root" message.
410
+ return join(clioStateDir(), "fleet-runs");
411
+ }
412
+
413
+ /**
414
+ * Resolve a fleet root the way {@link resolveRunId} resolves a run: exact id
415
+ * first, then a unique prefix over the durable fleet-run records. An ambiguous
416
+ * prefix resolves to nothing so the caller can name what it matched.
417
+ */
418
+ export function resolveFleetRootId(token: string): { rootId: string } | { candidates: string[] } {
419
+ if (readFleetRun(token) !== null) return { rootId: token };
420
+ let entries: string[];
421
+ try {
422
+ entries = readdirSync(fleetRunsDir());
423
+ } catch {
424
+ return { candidates: [] };
425
+ }
426
+ const candidates = entries
427
+ .filter((name) => name.endsWith(".json"))
428
+ .map((name) => name.slice(0, -".json".length))
429
+ .filter((id) => id.startsWith(token));
430
+ if (candidates.length === 1 && candidates[0] !== undefined) return { rootId: candidates[0] };
431
+ return { candidates };
432
+ }
433
+
434
+ /**
435
+ * Read defensively. A fleet-run record is written by an older or newer build
436
+ * than the one reading it, and a missing field must cost the index one column,
437
+ * never the whole listing.
438
+ */
439
+ function stepResultOf(value: unknown): Partial<ExecutionStepResult> {
440
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? (value as ExecutionStepResult) : {};
441
+ }
442
+
443
+ export function loadFleetRunViewModel(rootId: string, options: { now?: () => number } = {}): FleetRunViewModel | null {
444
+ const record = readFleetRun(rootId);
445
+ if (record === null) return null;
446
+ const ledger = openLedger();
447
+ const now = options.now ?? (() => Date.now());
448
+ const settled = new Map<string, Partial<ExecutionStepResult>>();
449
+ for (const entry of record.steps ?? []) {
450
+ if (typeof entry?.stepId === "string") settled.set(entry.stepId, stepResultOf(entry.result));
451
+ }
452
+ // Planned order, not settle order: it is the order the operator wrote the
453
+ // fleet in, and a step that never ran still belongs on the index.
454
+ const stepIds = record.stepIds?.length > 0 ? record.stepIds : [...settled.keys()];
455
+ const steps: FleetRunStepRow[] = stepIds.map((stepId) => {
456
+ const result = settled.get(stepId);
457
+ const runId = typeof result?.terminalRunId === "string" ? result.terminalRunId : null;
458
+ if (result === undefined || runId === null) {
459
+ return { stepId, runId: null, agentId: null, outcome: "not run", detail: undefined };
460
+ }
461
+ const envelope = ledger.get(runId);
462
+ const outcome = envelope?.outcome ?? (result.succeeded === true ? "succeeded" : "failed");
463
+ const detail = result.boundaryViolated === true ? WRITE_BOUNDARY_VIOLATION_REASON : result.failureReason;
464
+ return {
465
+ stepId,
466
+ runId,
467
+ agentId: envelope?.agentId ?? null,
468
+ outcome,
469
+ detail: typeof detail === "string" && detail.length > 0 ? detail : undefined,
470
+ };
471
+ });
472
+ const startedMs = Date.parse(record.startedAt);
473
+ const endedMs = record.endedAt === null ? now() : Date.parse(record.endedAt);
474
+ return {
475
+ rootId: record.id,
476
+ fleet: record.fleet,
477
+ startedAt: record.startedAt,
478
+ elapsedMs: Number.isFinite(startedMs) && Number.isFinite(endedMs) ? Math.max(0, endedMs - startedMs) : 0,
479
+ running: record.endedAt === null,
480
+ resumedFrom: record.resumedFrom,
481
+ plannedSteps: stepIds.length,
482
+ recordedSteps: settled.size,
483
+ steps,
484
+ };
485
+ }
486
+
487
+ /**
488
+ * The step index as lines. Pure, for the same reason {@link renderRunView} is:
489
+ * asserting the strings is asserting what an operator sees.
490
+ */
491
+ export function renderFleetRunView(model: FleetRunViewModel, width: number = DEFAULT_WIDTH): string[] {
492
+ const columns = Math.max(MIN_WIDTH, width);
493
+ const rule = "─".repeat(columns);
494
+ const lines: string[] = [];
495
+ lines.push(truncateToWidth(`fleet ${sanitizeBounded(model.fleet, 64)} root ${model.rootId}`, columns, "…", false));
496
+ const resumed = model.resumedFrom === null ? "" : ` resumed from ${sanitizeBounded(model.resumedFrom, 64)}`;
497
+ lines.push(
498
+ truncateToWidth(
499
+ `started ${model.startedAt} elapsed ${formatElapsed(model.elapsedMs)} ${model.recordedSteps} of ${model.plannedSteps} steps recorded${model.running ? " (running)" : ""}${resumed}`,
500
+ columns,
501
+ "…",
502
+ false,
503
+ ),
504
+ );
505
+ lines.push(rule);
506
+ if (model.steps.length === 0) {
507
+ lines.push("no steps recorded for this fleet run.");
508
+ } else {
509
+ const rows = model.steps.map((step) => ({
510
+ step: sanitizeBounded(step.stepId, STEP_ID_MAX_WIDTH),
511
+ runId: step.runId ?? "-",
512
+ outcome: sanitizeBounded(step.outcome, 32),
513
+ tail: [step.agentId, step.detail].filter((part) => part !== null && part !== undefined).join(": "),
514
+ }));
515
+ const stepWidth = Math.max(...rows.map((row) => row.step.length));
516
+ const runWidth = Math.max(...rows.map((row) => row.runId.length));
517
+ const outcomeWidth = Math.max(...rows.map((row) => row.outcome.length));
518
+ for (const row of rows) {
519
+ const head = `${row.step.padEnd(stepWidth)} ${row.runId.padEnd(runWidth)} ${row.outcome.padEnd(outcomeWidth)}`;
520
+ const text = row.tail.length === 0 ? head : `${head} ${sanitizeBounded(row.tail, DETAIL_MAX_WIDTH)}`;
521
+ lines.push(truncateToWidth(text.trimEnd(), columns, "…", false));
522
+ }
523
+ }
524
+ lines.push(rule);
525
+ lines.push("clio-coder fleet view <run id> for one step's transcript, receipt, and ledger entry");
526
+ return lines;
527
+ }
528
+
529
+ // ---------------------------------------------------------------------------
530
+ // Command
531
+ // ---------------------------------------------------------------------------
532
+
533
+ function fail(message: string): number {
534
+ process.stderr.write(`clio-coder fleet view: ${message}\n`);
535
+ return 2;
536
+ }
537
+
538
+ interface ParsedViewArgs {
539
+ runId?: string;
540
+ watchPath?: string;
541
+ dirs?: {
542
+ config?: string;
543
+ data?: string;
544
+ state?: string;
545
+ cache?: string;
546
+ };
547
+ follow: boolean;
548
+ help: boolean;
549
+ }
550
+
551
+ function parseViewArgs(args: ReadonlyArray<string>): ParsedViewArgs | string {
552
+ const parsed: ParsedViewArgs = { follow: false, help: false };
553
+ for (let i = 0; i < args.length; i += 1) {
554
+ const arg = args[i];
555
+ if (arg === undefined) continue;
556
+ if (arg === "--follow" || arg === "-f") {
557
+ parsed.follow = true;
558
+ continue;
559
+ }
560
+ if (arg === "--watch") {
561
+ const value = args[i + 1];
562
+ if (value === undefined || value.startsWith("-")) return "--watch requires a selection-file path";
563
+ parsed.watchPath = value;
564
+ i += 1;
565
+ continue;
566
+ }
567
+ const dirRole =
568
+ arg === "--config-dir"
569
+ ? "config"
570
+ : arg === "--data-dir"
571
+ ? "data"
572
+ : arg === "--state-dir"
573
+ ? "state"
574
+ : arg === "--cache-dir"
575
+ ? "cache"
576
+ : null;
577
+ if (dirRole !== null) {
578
+ const value = args[i + 1];
579
+ if (value === undefined || value.startsWith("-")) return `${arg} requires a directory path`;
580
+ parsed.dirs ??= {};
581
+ parsed.dirs[dirRole] = value;
582
+ i += 1;
583
+ continue;
584
+ }
585
+ if (arg === "--help" || arg === "-h") {
586
+ parsed.help = true;
587
+ continue;
588
+ }
589
+ if (arg.startsWith("-")) return `unknown flag: ${arg}`;
590
+ if (parsed.runId !== undefined) return `unexpected argument: ${arg}`;
591
+ parsed.runId = arg;
592
+ }
593
+ if (parsed.watchPath !== undefined && parsed.runId !== undefined) {
594
+ return "--watch follows the selection file; it does not take a run id";
595
+ }
596
+ if (parsed.watchPath !== undefined && parsed.follow) {
597
+ return "--watch already follows; drop --follow";
598
+ }
599
+ return parsed;
600
+ }
601
+
602
+ /**
603
+ * Pin the already-resolved parent layout before any fleet-view read. These are
604
+ * internal self-invocation flags rather than inherited raw environment: the
605
+ * pane shell may discard or replace its environment, but it cannot reinterpret
606
+ * the four absolute values already present in argv.
607
+ */
608
+ function applyViewDirs(dirs: ParsedViewArgs["dirs"]): void {
609
+ if (dirs === undefined) return;
610
+ if (dirs.config !== undefined) process.env.CLIO_CODER_CONFIG_DIR = dirs.config;
611
+ if (dirs.data !== undefined) process.env.CLIO_CODER_DATA_DIR = dirs.data;
612
+ if (dirs.state !== undefined) process.env.CLIO_CODER_STATE_DIR = dirs.state;
613
+ if (dirs.cache !== undefined) process.env.CLIO_CODER_CACHE_DIR = dirs.cache;
614
+ resetXdgCache();
615
+ }
616
+
617
+ /**
618
+ * The selection file is one line: the run id the watch view should render.
619
+ * Empty, missing, or torn content reads as "no selection" and costs one poll
620
+ * interval, never a crash; the writer (src/interactive/watch-pane.ts) replaces
621
+ * the file atomically.
622
+ */
623
+ export function readWatchSelection(path: string): string | null {
624
+ let raw: string;
625
+ try {
626
+ raw = readFileSync(path, "utf8");
627
+ } catch {
628
+ return null;
629
+ }
630
+ const line = raw.split("\n", 1)[0]?.trim() ?? "";
631
+ return line.length > 0 && line.length <= 128 ? line : null;
632
+ }
633
+
634
+ function terminalWidth(): number {
635
+ const columns = process.stdout.columns;
636
+ return typeof columns === "number" && columns >= MIN_WIDTH ? columns : DEFAULT_WIDTH;
637
+ }
638
+
639
+ export async function runFleetView(args: ReadonlyArray<string>): Promise<number> {
640
+ const parsed = parseViewArgs(args);
641
+ if (typeof parsed === "string") {
642
+ process.stderr.write(`clio-coder fleet view: ${parsed}\n`);
643
+ process.stdout.write(HELP);
644
+ return 2;
645
+ }
646
+ if (parsed.help) {
647
+ process.stdout.write(HELP);
648
+ return 0;
649
+ }
650
+ applyViewDirs(parsed.dirs);
651
+ if (parsed.watchPath !== undefined) {
652
+ // The watch loop needs an interactive terminal for the same reason
653
+ // --follow does; without one, print the selected run's snapshot and exit.
654
+ if (process.stdout.isTTY !== true || process.stdin.isTTY !== true) {
655
+ const selected = readWatchSelection(parsed.watchPath);
656
+ const model = selected === null ? null : loadRunViewModel(selected);
657
+ process.stdout.write(`${renderWatchView(selected, model, terminalWidth()).join("\n")}\n`);
658
+ return 0;
659
+ }
660
+ return watchSelection(parsed.watchPath);
661
+ }
662
+ if (parsed.runId === undefined) {
663
+ process.stderr.write(HELP);
664
+ return 2;
665
+ }
666
+ // A fleet root names a whole run, not a dispatched one, so it renders as the
667
+ // step index rather than as a run. --follow has nothing to tail on a root:
668
+ // the index changes only when a step settles.
669
+ if (parsed.runId.startsWith(FLEET_ROOT_PREFIX)) {
670
+ const root = resolveFleetRootId(parsed.runId);
671
+ if (!("rootId" in root)) {
672
+ return root.candidates.length === 0
673
+ ? fail(`unknown fleet run '${parsed.runId}' (no record under ${fleetRunsDir()})`)
674
+ : fail(`fleet root id '${parsed.runId}' is ambiguous: ${root.candidates.slice(0, 8).join(", ")}`);
675
+ }
676
+ const fleetModel = loadFleetRunViewModel(root.rootId);
677
+ if (fleetModel === null) return fail(`unknown fleet run '${root.rootId}'`);
678
+ if (parsed.follow) {
679
+ process.stderr.write("clio-coder fleet view: --follow applies to a run id, not a fleet root; printing the index\n");
680
+ }
681
+ process.stdout.write(`${renderFleetRunView(fleetModel, terminalWidth()).join("\n")}\n`);
682
+ return 0;
683
+ }
684
+ const resolved = resolveRunId(parsed.runId);
685
+ if (!("runId" in resolved)) {
686
+ return resolved.candidates.length === 0
687
+ ? fail(`unknown run '${parsed.runId}' (not in the run ledger under ${clioStateDir()})`)
688
+ : fail(`run id '${parsed.runId}' is ambiguous: ${resolved.candidates.slice(0, 8).join(", ")}`);
689
+ }
690
+ const runId = resolved.runId;
691
+ const snapshot = loadRunViewModel(runId);
692
+ if (snapshot === null) return fail(`unknown run '${runId}'`);
693
+
694
+ // A non-TTY stdout has no alternate screen and no keypresses, so --follow
695
+ // there would spin without ever being readable or quittable. Print the
696
+ // snapshot and say why, rather than hanging a pipe.
697
+ if (!parsed.follow || process.stdout.isTTY !== true || process.stdin.isTTY !== true) {
698
+ if (parsed.follow) {
699
+ process.stderr.write("clio-coder fleet view: --follow needs an interactive terminal; printing a snapshot\n");
700
+ }
701
+ process.stdout.write(`${renderRunView(snapshot, terminalWidth()).join("\n")}\n`);
702
+ return 0;
703
+ }
704
+ return followRun(runId);
705
+ }
706
+
707
+ /**
708
+ * Alternate-screen watch loop: the workers-view pane process. Every poll it
709
+ * re-reads the selection file and renders whichever run it names, so arrow-key
710
+ * navigation in the TUI retargets this process through one small file write
711
+ * and no socket traffic. It never stops on a terminal run; the selection is
712
+ * the operator's cursor, and the cursor outlives any one run.
713
+ */
714
+ async function watchSelection(selectionPath: string): Promise<number> {
715
+ const { ProcessTerminal, TuiAltScreen } = await import("../engine/tui-primitives.js");
716
+ const terminal = new ProcessTerminal();
717
+ const tui = new TuiAltScreen(terminal);
718
+ let selected = readWatchSelection(selectionPath);
719
+ let model = selected === null ? null : loadRunViewModel(selected);
720
+
721
+ const view = {
722
+ render(width: number): string[] {
723
+ const lines = renderWatchView(selected, model, width);
724
+ lines.push("");
725
+ lines.push("watching the workers-view selection… q to quit");
726
+ return lines;
727
+ },
728
+ invalidate(): void {},
729
+ };
730
+ tui.addChild(view);
731
+
732
+ return await new Promise<number>((resolve) => {
733
+ let settled = false;
734
+ const timer = setInterval(() => {
735
+ selected = readWatchSelection(selectionPath);
736
+ model = selected === null ? null : loadRunViewModel(selected);
737
+ tui.requestRender();
738
+ }, POLL_MS);
739
+ const finish = (): void => {
740
+ if (settled) return;
741
+ settled = true;
742
+ clearInterval(timer);
743
+ removeInput();
744
+ tui.stop();
745
+ resolve(0);
746
+ };
747
+ const removeInput = tui.addInputListener((data: string) => {
748
+ if (data === "q" || data === QUIT_CTRL_C || data === QUIT_ESCAPE) {
749
+ finish();
750
+ return { consume: true };
751
+ }
752
+ return undefined;
753
+ });
754
+ tui.start();
755
+ tui.requestRender();
756
+ });
757
+ }
758
+
759
+ /**
760
+ * Alternate-screen follow loop on the Clio terminal-engine primitives. The
761
+ * component is a plain line source over the same pure renderer the snapshot
762
+ * path uses, so the two surfaces cannot drift.
763
+ */
764
+ async function followRun(runId: string): Promise<number> {
765
+ const { ProcessTerminal, TuiAltScreen } = await import("../engine/tui-primitives.js");
766
+ const terminal = new ProcessTerminal();
767
+ const tui = new TuiAltScreen(terminal);
768
+ let model = loadRunViewModel(runId);
769
+ let following = true;
770
+
771
+ const view = {
772
+ render(width: number): string[] {
773
+ if (model === null) return [`run ${runId} left the ledger.`];
774
+ const lines = renderRunView(model, width);
775
+ lines.push("");
776
+ lines.push(following ? "following… q to quit" : "run finished. q to quit");
777
+ return lines;
778
+ },
779
+ invalidate(): void {},
780
+ };
781
+ tui.addChild(view);
782
+
783
+ return await new Promise<number>((resolve) => {
784
+ let settled = false;
785
+ const timer = setInterval(() => {
786
+ model = loadRunViewModel(runId);
787
+ if (model?.terminal === true) following = false;
788
+ tui.requestRender();
789
+ }, POLL_MS);
790
+ const finish = (): void => {
791
+ if (settled) return;
792
+ settled = true;
793
+ clearInterval(timer);
794
+ removeInput();
795
+ tui.stop();
796
+ resolve(0);
797
+ };
798
+ const removeInput = tui.addInputListener((data: string) => {
799
+ // The terminal is in raw mode, so Ctrl-C arrives as a byte rather than
800
+ // as SIGINT and has to be honored here.
801
+ if (data === "q" || data === QUIT_CTRL_C || data === QUIT_ESCAPE) {
802
+ finish();
803
+ return { consume: true };
804
+ }
805
+ return undefined;
806
+ });
807
+ tui.start();
808
+ tui.requestRender();
809
+ });
810
+ }