@iowarp/clio-coder 0.3.9 → 0.4.1

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 (733) hide show
  1. package/CHANGELOG.md +92 -0
  2. package/NOTICE +33 -0
  3. package/README.md +121 -16
  4. package/dist/{acp-7LOELQFP.js → acp-ZILU3AUO.js} +19 -18
  5. package/dist/{agents-FIBG2SHA.js → agents-HYWGBGQR.js} +53 -39
  6. package/dist/assets/codewiki.json +1 -1
  7. package/dist/{auth-OI4LIH2I.js → auth-N3QT7CBO.js} +24 -21
  8. package/dist/{builtins-AD25UL3C.js → builtins-UJLMOVOV.js} +6 -6
  9. package/dist/{chunk-I4HZDVNP.js → chunk-2LEAKD4C.js} +2 -3
  10. package/dist/{chunk-465CC7FK.js → chunk-2NM363SV.js} +60 -64
  11. package/dist/{chunk-4H6ULJ3H.js → chunk-2VG7KLYV.js} +1046 -1370
  12. package/dist/{chunk-SST6Z5JA.js → chunk-336NSSKI.js} +1 -1
  13. package/dist/{chunk-NZU6YDNV.js → chunk-3I5NY75V.js} +5 -5
  14. package/dist/{chunk-47CMYGET.js → chunk-3QSOM6PA.js} +2 -2
  15. package/dist/chunk-42FMPA75.js +101 -0
  16. package/dist/{chunk-HAY4ZE2P.js → chunk-5PSMVOLM.js} +2 -2
  17. package/dist/{chunk-IHKBWSXF.js → chunk-5QIAJV2D.js} +3 -3
  18. package/dist/chunk-5YHDIDBP.js +97 -0
  19. package/dist/chunk-6CCS4G3W.js +1529 -0
  20. package/dist/{chunk-2JDWVJND.js → chunk-6DWBAZ5U.js} +2 -2
  21. package/dist/{chunk-BVDVID7E.js → chunk-6FN3E6KX.js} +6 -6
  22. package/dist/{chunk-DG4M6ZUE.js → chunk-6HMJX2VU.js} +59 -59
  23. package/dist/{chunk-3MRC2YSQ.js → chunk-6I5ILFOF.js} +3 -2
  24. package/dist/chunk-6NJQITNH.js +1412 -0
  25. package/dist/chunk-6VC4OV3Z.js +365 -0
  26. package/dist/{chunk-LU7P4LHA.js → chunk-72GZI5EV.js} +2 -2
  27. package/dist/{chunk-5T7RBWN2.js → chunk-74YWRRU5.js} +9 -8
  28. package/dist/{chunk-VPTUJU4P.js → chunk-7EPLI7VL.js} +2 -2
  29. package/dist/{chunk-CA42X6KT.js → chunk-7OBGU7UB.js} +7 -7
  30. package/dist/{chunk-56KB5IJP.js → chunk-7PWAODYW.js} +3 -3
  31. package/dist/chunk-7RY5VZPH.js +573 -0
  32. package/dist/{chunk-OEDBCISO.js → chunk-ABLSQ6JX.js} +3 -3
  33. package/dist/{chunk-5HFBWUMU.js → chunk-AFKWHWXF.js} +13 -23
  34. package/dist/{chunk-FWDFM5ZU.js → chunk-AK5XEFVZ.js} +3 -3
  35. package/dist/{chunk-WXCJ7VME.js → chunk-AKB4GYDL.js} +7 -7
  36. package/dist/chunk-AP73CFDC.js +308 -0
  37. package/dist/{chunk-4LJX2PUC.js → chunk-BBTJOK6Y.js} +5 -5
  38. package/dist/{chunk-QQ7EKM72.js → chunk-BDPT6GTK.js} +13 -61
  39. package/dist/chunk-BJGUKIG4.js +302 -0
  40. package/dist/chunk-BMS5RKQY.js +27 -0
  41. package/dist/{chunk-TD7UE2L5.js → chunk-BTGG6BG2.js} +81 -40
  42. package/dist/chunk-BTK3SSSK.js +67 -0
  43. package/dist/{chunk-M6L6IDJG.js → chunk-BWH6N36D.js} +1 -1
  44. package/dist/chunk-BWW4HLO4.js +301 -0
  45. package/dist/{chunk-BNAZZHFG.js → chunk-BYP5D4HI.js} +1 -1
  46. package/dist/{chunk-HJB5IUKP.js → chunk-C537JADH.js} +18 -18
  47. package/dist/{chunk-774ILSRL.js → chunk-CDNVLKUX.js} +5 -4
  48. package/dist/{chunk-HPCTNZM2.js → chunk-CTJ4RNAA.js} +4 -4
  49. package/dist/{chunk-AEYBF3TB.js → chunk-CWVRRIEI.js} +2 -2
  50. package/dist/chunk-D4CJV46V.js +462 -0
  51. package/dist/chunk-D7E7WCDF.js +27 -0
  52. package/dist/{chunk-5PVQ4SRS.js → chunk-E67WX76H.js} +10 -126
  53. package/dist/{chunk-FQ4SKYE4.js → chunk-EBY63B7G.js} +2 -5
  54. package/dist/chunk-EIMVLWB3.js +1842 -0
  55. package/dist/{chunk-AMKHQW3C.js → chunk-FEFIFZTL.js} +3 -3
  56. package/dist/{chunk-RAPCMZL4.js → chunk-FSP7CMNU.js} +9 -1
  57. package/dist/{chunk-3DUR4WUA.js → chunk-FYUN5KZ3.js} +25 -25
  58. package/dist/{chunk-MV3K5QF2.js → chunk-GCSMB2KY.js} +2 -2
  59. package/dist/chunk-GIZNH63R.js +40 -0
  60. package/dist/chunk-GPPB3JBE.js +688 -0
  61. package/dist/{chunk-B5XRQOLB.js → chunk-GVQJ5CCZ.js} +17 -11
  62. package/dist/chunk-HHV2GANA.js +88 -0
  63. package/dist/chunk-HJWWJ6IL.js +184 -0
  64. package/dist/{chunk-RKRLDWD3.js → chunk-HLW2MRKE.js} +1 -1
  65. package/dist/chunk-HRQMFLJP.js +75 -0
  66. package/dist/{chunk-AD7Y7STJ.js → chunk-HUAS7ITX.js} +107 -97
  67. package/dist/{chunk-O6I4CIEU.js → chunk-I66EAJFY.js} +6 -6
  68. package/dist/{chunk-S4COXYBG.js → chunk-IG7BCQBA.js} +129 -106
  69. package/dist/{chunk-N3PBVRTZ.js → chunk-IXJT6DCX.js} +9 -7
  70. package/dist/chunk-J4HBWF6Y.js +2635 -0
  71. package/dist/chunk-J5LZHVIT.js +284 -0
  72. package/dist/{chunk-GAYUJ7LE.js → chunk-JA5QWE4Z.js} +544 -227
  73. package/dist/{chunk-B5CSFE7B.js → chunk-JIEGK6UF.js} +6 -174
  74. package/dist/{chunk-VKBMFOYV.js → chunk-JZWT5J3Y.js} +23 -27
  75. package/dist/chunk-K6BF4U2H.js +1785 -0
  76. package/dist/{chunk-CGKSTWHD.js → chunk-K6BSR66V.js} +2 -1
  77. package/dist/{chunk-YSEHGPCT.js → chunk-MA3H6DM5.js} +6 -6
  78. package/dist/{chunk-EBOC7MT3.js → chunk-MOPSG2X7.js} +9 -9
  79. package/dist/{chunk-KV2AOLDF.js → chunk-MQXIVJ35.js} +18 -29
  80. package/dist/{chunk-P3JGPQFL.js → chunk-N5UK64DP.js} +4 -4
  81. package/dist/chunk-NDDGJUXD.js +81 -0
  82. package/dist/chunk-NEKRRTYW.js +56 -0
  83. package/dist/{chunk-4ZG3XFUR.js → chunk-NLFAQR7Z.js} +12 -1
  84. package/dist/{chunk-VO2LKSTM.js → chunk-O42A54GG.js} +2 -2
  85. package/dist/{chunk-T3Z6VAAF.js → chunk-OJTRZGR3.js} +53 -92
  86. package/dist/{chunk-5DQRIYDZ.js → chunk-PBP4B7XR.js} +3 -3
  87. package/dist/{chunk-NQ6UCCOD.js → chunk-PJJ6MY27.js} +8 -8
  88. package/dist/chunk-PMZCIOCJ.js +25 -0
  89. package/dist/{chunk-ZGVHUX3M.js → chunk-PMZVFWSR.js} +2 -2
  90. package/dist/{chunk-7C6RYZGQ.js → chunk-PVAMAVBB.js} +5 -4
  91. package/dist/chunk-QDC3K2U3.js +262 -0
  92. package/dist/{chunk-JEQQR47K.js → chunk-QKIFBZKT.js} +173 -14
  93. package/dist/{chunk-R7LNVMCS.js → chunk-QMXC4JB7.js} +70 -78
  94. package/dist/chunk-QSNYB6ZV.js +195 -0
  95. package/dist/chunk-QTFGO774.js +644 -0
  96. package/dist/{chunk-ODFEOB4F.js → chunk-R23Z6K6I.js} +27 -9
  97. package/dist/{chunk-3UUY7R3Z.js → chunk-R32CLGZ6.js} +14 -14
  98. package/dist/chunk-R346GLFC.js +31 -0
  99. package/dist/chunk-RKSR6VSF.js +132 -0
  100. package/dist/{chunk-N5XKWMDW.js → chunk-SCYB3HA4.js} +73 -27
  101. package/dist/{chunk-PZ4I4JE2.js → chunk-SINK3QR6.js} +135 -125
  102. package/dist/{chunk-5QKCQQ3E.js → chunk-SUW5DORT.js} +2 -2
  103. package/dist/chunk-T56WDKA5.js +183 -0
  104. package/dist/{chunk-ECUO3KDP.js → chunk-TD3PGPQA.js} +87 -26
  105. package/dist/{chunk-HKO36JWF.js → chunk-THYWACCR.js} +31 -3
  106. package/dist/{chunk-LXPJXFM5.js → chunk-TVH4ONAM.js} +18 -12
  107. package/dist/chunk-TVHHYFHE.js +255 -0
  108. package/dist/chunk-U2WB7TZS.js +271 -0
  109. package/dist/{chunk-NNNWO6F2.js → chunk-UAPGZHYC.js} +237 -33
  110. package/dist/{chunk-MIX5N5AC.js → chunk-UEDMSP56.js} +415 -160
  111. package/dist/{chunk-WIE7ZOSW.js → chunk-UXN6JT4W.js} +3 -3
  112. package/dist/{chunk-CMZWFGD2.js → chunk-VAVR4IJY.js} +19 -15
  113. package/dist/{chunk-MV2VUEJC.js → chunk-VEGN6WIQ.js} +2 -2
  114. package/dist/chunk-VKFQTNDV.js +66 -0
  115. package/dist/{chunk-FALJGAWU.js → chunk-VKRH2TCS.js} +2 -2
  116. package/dist/{chunk-A2NJGIB3.js → chunk-VN3SHNBN.js} +2 -2
  117. package/dist/{chunk-XK56QHLX.js → chunk-VPKWYKEY.js} +19 -5
  118. package/dist/{chunk-UOSL25KY.js → chunk-VXMFAE2W.js} +1 -1
  119. package/dist/{chunk-EQ63NRB7.js → chunk-WNP7O5WZ.js} +3 -3
  120. package/dist/chunk-WR6U3OVP.js +45 -0
  121. package/dist/{chunk-TEO2TLVN.js → chunk-XKA2ICR3.js} +1051 -1196
  122. package/dist/{chunk-3V5AYSEQ.js → chunk-Y45G3AXC.js} +72 -65
  123. package/dist/{chunk-J5Q24KAG.js → chunk-Y4CAGMM6.js} +2 -2
  124. package/dist/chunk-YAKGTXEV.js +17 -0
  125. package/dist/{chunk-ZI647VB5.js → chunk-YPI3QQCF.js} +2 -2
  126. package/dist/{chunk-YW7UVM5V.js → chunk-ZAZB4JMW.js} +55 -11
  127. package/dist/{chunk-HCBCAYZU.js → chunk-ZJLUDYFY.js} +11 -11
  128. package/dist/{chunk-HKIYEGME.js → chunk-ZTJUVLTU.js} +2 -2
  129. package/dist/{chunk-IFBNV6H6.js → chunk-ZW55JB7N.js} +3 -3
  130. package/dist/cli/index.js +48 -37
  131. package/dist/clio-IT3G3VQH.js +25 -0
  132. package/dist/{code-nav-LMW275PA.js → code-nav-RK6S7F6E.js} +8 -8
  133. package/dist/codewiki/build-worker.js +1 -1
  134. package/dist/{compile-cache-CVJMMODC.js → compile-cache-OQAVB7Y5.js} +3 -5
  135. package/dist/{components-ZFA3SAER.js → components-UBWCQSRW.js} +6 -6
  136. package/dist/{config-RXS5T3JT.js → config-3QZRWZJF.js} +73 -61
  137. package/dist/configure-FL7Y3KJF.js +54 -0
  138. package/dist/{context-MVOORGMF.js → context-5HE7ODYK.js} +61 -49
  139. package/dist/{context-I3BTOTCS.js → context-KYQFRVDC.js} +11 -11
  140. package/dist/{context-PALKKQYL.js → context-XNHL75JV.js} +43 -28
  141. package/dist/{context-clear-N2WOYZ2K.js → context-clear-N545L53A.js} +60 -48
  142. package/dist/{context-working-set-MIEVECVZ.js → context-working-set-QHKXSV2F.js} +15 -13
  143. package/dist/{dispatch-runner-VVA4SRRH.js → dispatch-runner-RGIE5PCT.js} +109 -72
  144. package/dist/{docs-7LQ23DLM.js → docs-5NAF6AU7.js} +7 -13
  145. package/dist/doctor-ZGPEGHIP.js +772 -0
  146. package/dist/{eval-IJ5VEZDJ.js → eval-GXLL44RD.js} +106 -86
  147. package/dist/eval-inventory-HBWSWQOK.js +315 -0
  148. package/dist/{evidence-L5APPXNV.js → evidence-HWLBRH3Q.js} +209 -51
  149. package/dist/{evolve-RGNKFJ52.js → evolve-FTZBMNVW.js} +57 -45
  150. package/dist/{extensions-7WYWUX5A.js → extensions-VHRBEID7.js} +7 -7
  151. package/dist/{fleet-6CNVBZZP.js → fleet-CKZHJWZJ.js} +215 -114
  152. package/dist/{fleet-commands-L2SXSYEI.js → fleet-commands-EXDXBMV6.js} +13 -12
  153. package/dist/fleet-decisions-OTHB6KRL.js +157 -0
  154. package/dist/{fleet-graph-2J3OOIPO.js → fleet-graph-YTEZUCUT.js} +20 -17
  155. package/dist/fleet-inspect-SS6YMDCK.js +445 -0
  156. package/dist/{fleet-preflight-CZRJ4JP5.js → fleet-preflight-PBY4VYOM.js} +6 -9
  157. package/dist/{fleet-validate-C5RI6DP7.js → fleet-validate-KMEM5L3S.js} +25 -20
  158. package/dist/fleet-verify-QD5M7E7Q.js +178 -0
  159. package/dist/fleet-view-WAMJYNDT.js +94 -0
  160. package/dist/{init-VBN2ACVA.js → init-5XQRBOFV.js} +79 -66
  161. package/dist/install-NKNG7KD4.js +13 -0
  162. package/dist/interop-34TVO25M.js +116 -0
  163. package/dist/{library-JHGUMLY2.js → library-3QY6KF57.js} +25 -21
  164. package/dist/{memory-K4OQIYWG.js → memory-L4UTIIIW.js} +59 -47
  165. package/dist/{models-2NCZUWDD.js → models-ZVX3QOWE.js} +47 -37
  166. package/dist/{monitor-MMVTJABD.js → monitor-CEKVSYTS.js} +67 -57
  167. package/dist/{orchestrator-ZKBPCHW6.js → orchestrator-77BAP6BC.js} +7633 -7699
  168. package/dist/panes-7STHOAUJ.js +103 -0
  169. package/dist/panes-SHAUIRXY.js +60 -0
  170. package/dist/{paths-DBXMZMDU.js → paths-L7LGY6RN.js} +6 -6
  171. package/dist/{reset-DD5JGOY3.js → reset-EOLM7GVE.js} +15 -14
  172. package/dist/resources-74GKTLSF.js +115 -0
  173. package/dist/{run-QEGNX7FL.js → run-HBAUJNNZ.js} +139 -98
  174. package/dist/{share-JKD3BQMW.js → share-G3APVLVP.js} +25 -21
  175. package/dist/{skills-LMQIKDOZ.js → skills-35HHUKCR.js} +37 -27
  176. package/dist/{skills-eval-I7X2774U.js → skills-eval-QN4HSHDC.js} +80 -73
  177. package/dist/skills-inventory-J357J34F.js +129 -0
  178. package/dist/slash-commands-JZZCQA32.js +79 -0
  179. package/dist/{steer-CF5TDANS.js → steer-XAVHJM22.js} +4 -4
  180. package/dist/{support-I7LOJLIF.js → support-U7QOWY26.js} +7 -7
  181. package/dist/{targets-RUSR6B5Z.js → targets-DSM6CY3M.js} +67 -59
  182. package/dist/{terminal-lease-QYVORFR4.js → terminal-lease-JOPFUVEM.js} +15 -10
  183. package/dist/tools-MKNWVPBH.js +29 -0
  184. package/dist/{trace-ODOQIVIW.js → trace-ECQ7TIYZ.js} +194 -7
  185. package/dist/tui-primitives-K5PASC3O.js +17 -0
  186. package/dist/{uninstall-ZJF5H5ZN.js → uninstall-N34PCTGJ.js} +6 -8
  187. package/dist/{upgrade-XANW3FXB.js → upgrade-H7TOM7YL.js} +37 -62
  188. package/dist/{usage-4H7ZRXQT.js → usage-X52N3IDJ.js} +70 -59
  189. package/dist/{verifiers-UZXNBZEB.js → verifiers-EJTVVSMA.js} +174 -14
  190. package/dist/{verify-BVKWTNDL.js → verify-YJL6XET2.js} +12 -11
  191. package/dist/{web-fetch-2YHJ3KTG.js → web-fetch-MPIFL3LL.js} +3 -4
  192. package/dist/{wiki-generate-MY7WV2QI.js → wiki-generate-4NDZTQ4B.js} +86 -75
  193. package/dist/with-panes-OBOBFIIR.js +818 -0
  194. package/dist/worker/entry.js +131 -91
  195. package/docs/README.md +3 -2
  196. package/docs/acp.md +25 -4
  197. package/docs/alcf-provider.md +1 -1
  198. package/docs/architecture.md +2 -2
  199. package/docs/artifact-versions.md +4 -1
  200. package/docs/built-in-agents.md +40 -32
  201. package/docs/capacity-and-scheduling.md +43 -5
  202. package/docs/commands-and-modes.md +39 -4
  203. package/docs/config-knobs-audit.md +3 -0
  204. package/docs/configuration-and-targets.md +138 -167
  205. package/docs/context-engine.md +1 -1
  206. package/docs/development-pipeline.md +19 -0
  207. package/docs/dispatch-typed-intent.md +385 -0
  208. package/docs/documentation-coverage.md +2 -2
  209. package/docs/documentation-guide.md +1 -1
  210. package/docs/environment-variables.md +3 -1
  211. package/docs/eval-runner.md +1 -1
  212. package/docs/evals-internal.md +1 -1
  213. package/docs/evidence-and-memory.md +2 -2
  214. package/docs/evolution.md +1 -1
  215. package/docs/exit-codes-and-output.md +3 -1
  216. package/docs/extensions-and-sharing.md +25 -1
  217. package/docs/fleet-demo-runbook.md +6 -3
  218. package/docs/fleet-dispatch.md +30 -7
  219. package/docs/installation-and-lifecycle.md +16 -3
  220. package/docs/middleware-and-components.md +1 -1
  221. package/docs/model-catalog.md +10 -1
  222. package/docs/observability.md +3 -3
  223. package/docs/proactive-memory.md +1 -1
  224. package/docs/prompt-envelope-and-tools.md +1 -1
  225. package/docs/provider-adapter-cookbook.md +1 -1
  226. package/docs/release-cut-checklist.md +164 -177
  227. package/docs/safety-model.md +2 -2
  228. package/docs/scientific-validation.md +3 -3
  229. package/docs/session-lifecycle.md +1 -1
  230. package/docs/skills-marketplace.md +1 -1
  231. package/docs/tool-usage.md +18 -8
  232. package/docs/trace-store.md +16 -11
  233. package/docs/troubleshooting.md +2 -2
  234. package/docs/tui-design.md +1 -1
  235. package/docs/worker-dispatch-mechanics.md +1 -1
  236. package/evals/README.md +5 -0
  237. package/evals/behavioral-corpus-grader.mjs +103 -0
  238. package/evals/behavioral-machinery-baseline.json +2276 -0
  239. package/evals/behavioral-machinery-driver.ts +250 -0
  240. package/evals/behavioral-machinery-support.ts +253 -0
  241. package/evals/behavioral-machinery.yaml +614 -0
  242. package/evals/behavioral-model-negative-control.yaml +68 -0
  243. package/evals/behavioral-model.yaml +216 -0
  244. package/evals/fixtures/behavioral-facts.txt +3 -0
  245. package/evals/fixtures/behavioral-main.ts +7 -0
  246. package/evals/fixtures/decoy/note.txt +2 -0
  247. package/evals/fixtures/private/credentials.txt +1 -0
  248. package/evals/tracked-metrics-baseline.yaml +229 -0
  249. package/evals/tracked-metrics-grader.mjs +129 -0
  250. package/package.json +8 -16
  251. package/skills/README.md +6 -6
  252. package/skills/coding/ast-grep/SKILL.md +8 -2
  253. package/skills/coding/coding-standards/SKILL.md +8 -2
  254. package/skills/coding/prototype/SKILL.md +8 -2
  255. package/skills/coding/tdd/SKILL.md +8 -2
  256. package/skills/context/context-handoff/SKILL.md +12 -3
  257. package/skills/context/context-handoff/evals.md +3 -3
  258. package/skills/context/context-prime/SKILL.md +8 -2
  259. package/skills/context/context-prime/evals.md +2 -2
  260. package/skills/git/file-ticket/SKILL.md +9 -4
  261. package/skills/git/file-ticket/evals.md +2 -2
  262. package/skills/git/fix-issue/SKILL.md +12 -5
  263. package/skills/git/fix-issue/evals.md +4 -4
  264. package/skills/git/resolve-merge-conflicts/SKILL.md +8 -2
  265. package/skills/git/resolve-merge-conflicts/evals.md +2 -2
  266. package/skills/git/ship/SKILL.md +9 -3
  267. package/skills/git/ship/evals.md +4 -4
  268. package/skills/git/worktree-create/SKILL.md +7 -2
  269. package/skills/git/worktree-create/evals.md +6 -6
  270. package/skills/git/worktree-merge/SKILL.md +7 -2
  271. package/skills/git/worktree-merge/evals.md +6 -6
  272. package/skills/meta/{clio-dev → clio-coder-dev}/SKILL.md +14 -8
  273. package/skills/meta/{clio-dev → clio-coder-dev}/evals.md +3 -3
  274. package/skills/meta/{clio-test → clio-coder-test}/SKILL.md +11 -5
  275. package/skills/meta/{clio-test → clio-coder-test}/evals.md +1 -1
  276. package/skills/meta/credentials/SKILL.md +9 -2
  277. package/skills/meta/find-skills/SKILL.md +8 -2
  278. package/skills/meta/herdr/SKILL.md +8 -2
  279. package/skills/meta/skill-craft/SKILL.md +21 -4
  280. package/skills/planning/architecture/SKILL.md +9 -3
  281. package/skills/planning/backlog/SKILL.md +8 -2
  282. package/skills/planning/prd/SKILL.md +8 -2
  283. package/skills/planning/product-intent/SKILL.md +8 -2
  284. package/skills/planning/tech-spec/SKILL.md +9 -3
  285. package/skills/registry.yaml +66 -66
  286. package/skills/research/arxiv-literature/SKILL.md +8 -2
  287. package/skills/research/experiment-protocol/SKILL.md +9 -2
  288. package/skills/research/scientific-debugging/SKILL.md +9 -2
  289. package/skills/research/scientific-debugging/evals.md +2 -2
  290. package/skills/research/scientific-modernization/SKILL.md +9 -2
  291. package/skills/skill-marketplace.json +257 -38
  292. package/skills/workflow/cut-it/SKILL.md +10 -2
  293. package/skills/workflow/design-council/SKILL.md +11 -2
  294. package/skills/workflow/grill-me/SKILL.md +8 -2
  295. package/skills/workflow/workflow-distiller/SKILL.md +8 -2
  296. package/src/cli/acp.ts +8 -4
  297. package/src/cli/argv.ts +25 -0
  298. package/src/cli/auth.ts +2 -2
  299. package/src/cli/bootstrap-generate.ts +5 -5
  300. package/src/cli/clio.ts +6 -1
  301. package/src/cli/config-inspect.ts +3 -3
  302. package/src/cli/configure-interop.ts +1 -1
  303. package/src/cli/configure.ts +50 -48
  304. package/src/cli/default-target.ts +1 -1
  305. package/src/cli/docs.ts +6 -6
  306. package/src/cli/doctor-naming.ts +323 -0
  307. package/src/cli/doctor-panes.ts +156 -0
  308. package/src/cli/doctor-toolchain.ts +78 -0
  309. package/src/cli/doctor.ts +34 -1
  310. package/src/cli/eval-inventory.ts +436 -0
  311. package/src/cli/eval.ts +13 -0
  312. package/src/cli/evidence-detail.ts +88 -0
  313. package/src/cli/evidence-inventory.ts +183 -0
  314. package/src/cli/evidence.ts +30 -5
  315. package/src/cli/fleet-decisions.ts +69 -0
  316. package/src/cli/fleet-inspect.ts +334 -0
  317. package/src/cli/fleet-preflight.ts +1 -1
  318. package/src/cli/fleet-verify.ts +133 -0
  319. package/src/cli/fleet-view.ts +827 -0
  320. package/src/cli/fleet.ts +154 -40
  321. package/src/cli/index.ts +14 -2
  322. package/src/cli/interop-inspect.ts +128 -0
  323. package/src/cli/interop.ts +34 -0
  324. package/src/cli/models.ts +3 -3
  325. package/src/cli/modes/print.ts +1 -1
  326. package/src/cli/panes.ts +35 -0
  327. package/src/cli/reset.ts +6 -3
  328. package/src/cli/run.ts +67 -13
  329. package/src/cli/share.ts +1 -1
  330. package/src/cli/skills-eval.ts +11 -11
  331. package/src/cli/skills-inventory.ts +185 -0
  332. package/src/cli/skills.ts +16 -13
  333. package/src/cli/targets.ts +23 -24
  334. package/src/cli/tools.ts +321 -0
  335. package/src/cli/trace-inspect.ts +252 -0
  336. package/src/cli/trace.ts +64 -2
  337. package/src/cli/uninstall.ts +2 -2
  338. package/src/cli/upgrade.ts +29 -34
  339. package/src/cli/usage.ts +2 -2
  340. package/src/cli/validate-model.ts +1 -1
  341. package/src/cli/verifiers-inspect.ts +347 -0
  342. package/src/cli/verifiers.ts +9 -0
  343. package/src/cli/wiki-generate.ts +5 -5
  344. package/src/core/bash-exec.ts +1 -1
  345. package/src/core/boot-trace.ts +3 -3
  346. package/src/core/bus-events.ts +4 -0
  347. package/src/core/bus-trace.ts +2 -2
  348. package/src/core/compile-cache.ts +1 -1
  349. package/src/core/config.ts +1070 -845
  350. package/src/core/defaults.ts +342 -463
  351. package/src/core/domain-loader.ts +6 -4
  352. package/src/core/event-bus.ts +1 -1
  353. package/src/core/file-references.ts +78 -88
  354. package/src/core/git-commit-attribution.ts +3 -9
  355. package/src/core/guardrails.ts +24 -4
  356. package/src/core/naming-compat.ts +45 -0
  357. package/src/core/naming-events.ts +62 -0
  358. package/src/core/recent-models.ts +1 -7
  359. package/src/core/residency-protection.ts +4 -8
  360. package/src/core/resolve-config-value.ts +1 -1
  361. package/src/core/response-model-id.ts +0 -14
  362. package/src/core/response-schema.ts +44 -4
  363. package/src/core/session-routing.ts +84 -74
  364. package/src/core/skill-activation.ts +18 -0
  365. package/src/core/termination.ts +4 -4
  366. package/src/core/theme-token-hex.ts +43 -0
  367. package/src/core/timers.ts +2 -12
  368. package/src/core/tool-names.ts +2 -1
  369. package/src/core/xdg.ts +1 -1
  370. package/src/domains/agents/catalog.ts +2 -12
  371. package/src/domains/agents/extension.ts +3 -3
  372. package/src/domains/agents/fleets/build-review.md +0 -3
  373. package/src/domains/agents/fleets/build-test.md +0 -3
  374. package/src/domains/agents/registry.ts +7 -7
  375. package/src/domains/agents/result-contract-filesystem.ts +32 -0
  376. package/src/domains/agents/result-contract.ts +165 -36
  377. package/src/domains/agents/spec.ts +0 -11
  378. package/src/domains/config/classify.ts +37 -46
  379. package/src/domains/config/extension.ts +5 -5
  380. package/src/domains/config/keybindings.ts +48 -47
  381. package/src/domains/config/watcher.ts +1 -1
  382. package/src/domains/context/codewiki/indexer.ts +1 -1
  383. package/src/domains/context/extension.ts +3 -3
  384. package/src/domains/context/prompt-context.ts +1 -1
  385. package/src/domains/context/wiki/assemble.ts +2 -2
  386. package/src/domains/context/working-set/replay/metrics.ts +0 -4
  387. package/src/domains/context/working-set/replay/report.ts +1 -1
  388. package/src/domains/context/working-set/replay/synthetic.ts +2 -2
  389. package/src/domains/dispatch/active-route-planner.ts +2 -2
  390. package/src/domains/dispatch/admission-error.ts +9 -0
  391. package/src/domains/dispatch/admission.ts +22 -21
  392. package/src/domains/dispatch/agent-candidates.ts +1 -1
  393. package/src/domains/dispatch/agent-ledger-store.ts +1 -1
  394. package/src/domains/dispatch/agent-ledger.ts +1 -1
  395. package/src/domains/dispatch/assignment.ts +5 -5
  396. package/src/domains/dispatch/capacity-lease.ts +23 -6
  397. package/src/domains/dispatch/code-step.ts +1 -1
  398. package/src/domains/dispatch/contract.ts +2 -2
  399. package/src/domains/dispatch/council-topology.ts +398 -0
  400. package/src/domains/dispatch/extension.ts +287 -111
  401. package/src/domains/dispatch/fleet-node-prompt.ts +62 -0
  402. package/src/domains/dispatch/fleet-plan.ts +7 -2
  403. package/src/domains/dispatch/fleet-preflight.ts +8 -17
  404. package/src/domains/dispatch/fleet-run.ts +59 -3
  405. package/src/domains/dispatch/gate-decisions.ts +36 -15
  406. package/src/domains/dispatch/gate-role-prompts.ts +9 -0
  407. package/src/domains/dispatch/gate-topology.ts +289 -0
  408. package/src/domains/dispatch/index.ts +19 -0
  409. package/src/domains/dispatch/intent-compatibility.ts +330 -0
  410. package/src/domains/dispatch/intent.ts +85 -1
  411. package/src/domains/dispatch/joint-route-resolver.ts +1 -1
  412. package/src/domains/dispatch/path-scope.ts +0 -61
  413. package/src/domains/dispatch/placement.ts +2 -2
  414. package/src/domains/dispatch/receipt-findings.ts +1 -1
  415. package/src/domains/dispatch/receipt-integrity.ts +20 -8
  416. package/src/domains/dispatch/reservation-store.ts +24 -27
  417. package/src/domains/dispatch/route-decision.ts +1 -1
  418. package/src/domains/dispatch/route-history.ts +1 -1
  419. package/src/domains/dispatch/route-policy.ts +1 -1
  420. package/src/domains/dispatch/route-readiness.ts +0 -28
  421. package/src/domains/dispatch/run-event-journal-bridge.ts +149 -0
  422. package/src/domains/dispatch/run-event-journal.ts +599 -0
  423. package/src/domains/dispatch/state.ts +45 -1
  424. package/src/domains/dispatch/tool-stats.ts +1 -1
  425. package/src/domains/dispatch/transport.ts +2 -19
  426. package/src/domains/dispatch/types.ts +16 -3
  427. package/src/domains/dispatch/validation.ts +33 -8
  428. package/src/domains/dispatch/worker-protocol.ts +2 -2
  429. package/src/domains/eval/artifacts/store.ts +5 -4
  430. package/src/domains/eval/compare/compare.ts +3 -16
  431. package/src/domains/eval/compare/thresholds.ts +0 -8
  432. package/src/domains/eval/execution-provenance.ts +1 -1
  433. package/src/domains/eval/inventory.ts +113 -0
  434. package/src/domains/eval/metrics/token-stream.ts +0 -7
  435. package/src/domains/eval/report.ts +1 -1
  436. package/src/domains/eval/runner.ts +1 -1
  437. package/src/domains/eval/runners/clio-run.ts +7 -6
  438. package/src/domains/eval/runners/external-command.ts +4 -3
  439. package/src/domains/eval/schema/artifact.ts +1 -1
  440. package/src/domains/eval/schema/behavioral-metrics.ts +3 -2
  441. package/src/domains/eval/schema/behavioral.ts +7 -6
  442. package/src/domains/eval/schema/execution-envelope.ts +4 -2
  443. package/src/domains/eval/schema/naming.ts +10 -0
  444. package/src/domains/eval/schema/serving.ts +31 -0
  445. package/src/domains/eval/schema/suite.ts +2 -2
  446. package/src/domains/eval/schema/validate.ts +9 -5
  447. package/src/domains/eval/schema/verdict.ts +4 -2
  448. package/src/domains/eval/store.ts +12 -2
  449. package/src/domains/eval/suites/run.ts +12 -6
  450. package/src/domains/eval/types.ts +1 -1
  451. package/src/domains/eval/workspaces/git.ts +1 -1
  452. package/src/domains/eval/workspaces/temp-copy.ts +1 -1
  453. package/src/domains/evidence/build.ts +13 -5
  454. package/src/domains/evidence/store.ts +6 -0
  455. package/src/domains/interop/consent.ts +7 -7
  456. package/src/domains/interop/extension.ts +1 -1
  457. package/src/domains/lifecycle/doctor.ts +27 -21
  458. package/src/domains/lifecycle/migrations/2026-09-01-clio-coder-naming.ts +155 -0
  459. package/src/domains/lifecycle/migrations/2026-09-01-retire-panes-knobs.ts +59 -0
  460. package/src/domains/lifecycle/migrations/2026-09-01-settings-v2.ts +338 -0
  461. package/src/domains/lifecycle/migrations/index.ts +20 -1
  462. package/src/domains/lifecycle/naming-history.ts +153 -0
  463. package/src/domains/lifecycle/naming-mutable-state.ts +104 -0
  464. package/src/domains/lifecycle/naming-resources.ts +261 -0
  465. package/src/domains/lifecycle/naming-tool-markers.ts +139 -0
  466. package/src/domains/lifecycle/naming-yazi.ts +81 -0
  467. package/src/domains/lifecycle/upgrade-notice.ts +1 -1
  468. package/src/domains/memory/index.ts +0 -1
  469. package/src/domains/memory/task-memory-handoff.ts +6 -3
  470. package/src/domains/memory/task-memory-policy.ts +6 -10
  471. package/src/domains/memory/task-memory-telemetry.ts +1 -0
  472. package/src/domains/middleware/index.ts +7 -0
  473. package/src/domains/middleware/marketplace-offer.ts +339 -0
  474. package/src/domains/middleware/memory-intervention.ts +33 -21
  475. package/src/domains/middleware/runtime.ts +2 -2
  476. package/src/domains/mux/contract.ts +566 -0
  477. package/src/domains/mux/detect.ts +182 -0
  478. package/src/domains/mux/dock-controller.ts +365 -0
  479. package/src/domains/mux/extension.ts +53 -0
  480. package/src/domains/mux/index.ts +116 -0
  481. package/src/domains/mux/manifest.ts +6 -0
  482. package/src/domains/mux/operations.ts +192 -0
  483. package/src/domains/mux/pane-registry.ts +90 -0
  484. package/src/domains/mux/protocol.ts +62 -0
  485. package/src/domains/mux/socket-client.ts +964 -0
  486. package/src/domains/mux/types.ts +265 -0
  487. package/src/domains/mux/viewer-command.ts +59 -0
  488. package/src/domains/mux/yazi/assets/init.lua +2 -0
  489. package/src/domains/mux/yazi/assets/plugins/git.yazi/LICENSE +21 -0
  490. package/src/domains/mux/yazi/assets/plugins/git.yazi/README.md +78 -0
  491. package/src/domains/mux/yazi/assets/plugins/git.yazi/main.lua +255 -0
  492. package/src/domains/mux/yazi/assets/plugins/git.yazi/types.lua +12 -0
  493. package/src/domains/mux/yazi/assets/yazi.toml +17 -0
  494. package/src/domains/mux/yazi/event-stream.ts +182 -0
  495. package/src/domains/mux/yazi/profile.ts +299 -0
  496. package/src/domains/mux/yazi/session.ts +251 -0
  497. package/src/domains/mux/yazi/theme.ts +30 -0
  498. package/src/domains/observability/cost.ts +21 -0
  499. package/src/domains/observability/extension.ts +1 -1
  500. package/src/domains/observability/index.ts +2 -0
  501. package/src/domains/observability/out-of-turn-usage.ts +2 -2
  502. package/src/domains/observability/projection.ts +1 -1
  503. package/src/domains/observability/trace-store.ts +63 -11
  504. package/src/domains/prompts/compiler.ts +2 -2
  505. package/src/domains/prompts/extension.ts +3 -3
  506. package/src/domains/prompts/fragments/identity/clio-worker.md +1 -1
  507. package/src/domains/prompts/fragments/operating/skills.md +25 -18
  508. package/src/domains/providers/endpoint-capacity.ts +154 -22
  509. package/src/domains/providers/endpoint-slots-store.ts +189 -0
  510. package/src/domains/providers/extension.ts +25 -8
  511. package/src/domains/providers/index.ts +22 -0
  512. package/src/domains/providers/knowledge-base-path.ts +1 -1
  513. package/src/domains/providers/model-runtime-capabilities.ts +46 -2
  514. package/src/domains/providers/models/cloud-models/alcf.yaml +1 -1
  515. package/src/domains/providers/models/local-models/{clio-local-coding-targets.yaml → clio-coder-local-coding-targets.yaml} +4 -4
  516. package/src/domains/providers/plugins.ts +1 -1
  517. package/src/domains/providers/resolver.ts +1 -1
  518. package/src/domains/providers/runtimes/boot-manifest.ts +1 -0
  519. package/src/domains/providers/runtimes/builtins.ts +2 -0
  520. package/src/domains/providers/runtimes/cloud/alcf.ts +4 -4
  521. package/src/domains/providers/runtimes/common/lmstudio-http.ts +1 -5
  522. package/src/domains/providers/runtimes/common/local-synth.ts +3 -3
  523. package/src/domains/providers/runtimes/common/probe-helpers.ts +2 -2
  524. package/src/domains/providers/runtimes/local-native/llamacpp-completion.ts +1 -1
  525. package/src/domains/providers/runtimes/local-native/lmstudio.ts +2 -9
  526. package/src/domains/providers/runtimes/protocol/litellm.ts +285 -0
  527. package/src/domains/providers/support.ts +1 -0
  528. package/src/domains/providers/target-model-cache.ts +124 -0
  529. package/src/domains/providers/types/knowledge-base.ts +13 -0
  530. package/src/domains/providers/types/local-model-quirks.ts +2 -2
  531. package/src/domains/providers/types/target-descriptor.ts +3 -3
  532. package/src/domains/resources/extension.ts +1 -1
  533. package/src/domains/resources/index.ts +3 -0
  534. package/src/domains/resources/library.ts +6 -6
  535. package/src/domains/resources/prompts/loader.ts +1 -1
  536. package/src/domains/resources/skills/content-hash.ts +7 -6
  537. package/src/domains/resources/skills/evals.ts +1 -1
  538. package/src/domains/resources/skills/install.ts +36 -12
  539. package/src/domains/resources/skills/loader.ts +72 -13
  540. package/src/domains/resources/skills/marketplace.ts +21 -2
  541. package/src/domains/resources/skills/promotion.ts +214 -0
  542. package/src/domains/safety/action-classifier.ts +6 -0
  543. package/src/domains/safety/audit.ts +2 -2
  544. package/src/domains/safety/finish-contract.ts +2 -1
  545. package/src/domains/safety/run-effects.ts +1 -1
  546. package/src/domains/scheduling/extension.ts +4 -4
  547. package/src/domains/session/archive-readers.ts +9 -1
  548. package/src/domains/session/compaction/branch-summary.ts +4 -0
  549. package/src/domains/session/compaction/compact.ts +1 -1
  550. package/src/domains/session/compaction/tokens.ts +4 -0
  551. package/src/domains/session/context-accounting.ts +1 -1
  552. package/src/domains/session/decision-board.ts +1 -1
  553. package/src/domains/session/extension.ts +1 -1
  554. package/src/domains/session/handoff.ts +2 -2
  555. package/src/domains/session/prompt-manifest.ts +0 -5
  556. package/src/domains/session/protected-artifact-journal.ts +12 -6
  557. package/src/domains/session/retry.ts +1 -1
  558. package/src/domains/share/archive.ts +85 -36
  559. package/src/domains/toolchain/archive.ts +175 -0
  560. package/src/domains/toolchain/contract.ts +28 -0
  561. package/src/domains/toolchain/extension.ts +47 -0
  562. package/src/domains/toolchain/index.ts +39 -0
  563. package/src/domains/toolchain/install.ts +327 -0
  564. package/src/domains/toolchain/manifest.ts +8 -0
  565. package/src/domains/toolchain/paths.ts +34 -0
  566. package/src/domains/toolchain/registry.ts +265 -0
  567. package/src/domains/toolchain/remove.ts +218 -0
  568. package/src/domains/toolchain/resolve.ts +182 -0
  569. package/src/domains/toolchain/types.ts +113 -0
  570. package/src/domains/toolchain/version.ts +88 -0
  571. package/src/engine/acp/adapter.ts +3 -3
  572. package/src/engine/acp/event-mapper.ts +3 -3
  573. package/src/engine/acp/server.ts +440 -96
  574. package/src/engine/acp/tool-mediator.ts +2 -2
  575. package/src/engine/acp/types.ts +20 -2
  576. package/src/engine/alcf-oauth.ts +5 -5
  577. package/src/engine/antigravity/subprocess-runtime.ts +2 -2
  578. package/src/engine/api-registry.ts +1 -1
  579. package/src/engine/apis/llamacpp-residency.ts +1 -19
  580. package/src/engine/apis/lmstudio.ts +1 -6
  581. package/src/engine/apis/ollama-native.ts +3 -3
  582. package/src/engine/apis/openai-completions.ts +2 -2
  583. package/src/engine/apis/residency.ts +3 -9
  584. package/src/engine/claude/sdk-module.ts +87 -0
  585. package/src/engine/claude/sdk-runtime.ts +25 -17
  586. package/src/engine/claude/subprocess-runtime.ts +1 -1
  587. package/src/engine/claude/tool-safety.ts +4 -4
  588. package/src/engine/loop-guard.ts +0 -5
  589. package/src/engine/session.ts +14 -7
  590. package/src/engine/tui-primitives.ts +21 -0
  591. package/src/engine/tui.ts +1 -0
  592. package/src/engine/worker-events.ts +6 -6
  593. package/src/engine/worker-runtime.ts +16 -26
  594. package/src/entry/boot-options.ts +2 -0
  595. package/src/entry/orchestrator.ts +207 -90
  596. package/src/entry/panes-activation.ts +31 -0
  597. package/src/entry/with-panes.ts +20 -0
  598. package/src/interactive/application-controller.ts +21 -21
  599. package/src/interactive/bus-notices.ts +2 -2
  600. package/src/interactive/chat-loop.ts +8 -8
  601. package/src/interactive/chat-panel.ts +1 -1
  602. package/src/interactive/clio-editor.ts +105 -1
  603. package/src/interactive/context-activity.ts +8 -6
  604. package/src/interactive/context-meter.ts +3 -6
  605. package/src/interactive/context-overlay.ts +2 -1
  606. package/src/interactive/cost-overlay.ts +3 -1
  607. package/src/interactive/council-grid.ts +3 -5
  608. package/src/interactive/council.ts +11 -0
  609. package/src/interactive/dispatch-board.ts +56 -49
  610. package/src/interactive/editor-bash.ts +21 -0
  611. package/src/interactive/editor-steer.ts +9 -4
  612. package/src/interactive/editor-submit.ts +24 -18
  613. package/src/interactive/external-editor.ts +1 -1
  614. package/src/interactive/file-reference-completion.ts +360 -0
  615. package/src/interactive/footer/dashboard.ts +15 -17
  616. package/src/interactive/footer/notifications.ts +14 -8
  617. package/src/interactive/footer/widgets.ts +2 -44
  618. package/src/interactive/interactive-application.ts +229 -62
  619. package/src/interactive/interactive-event-projection.ts +1 -1
  620. package/src/interactive/interactive-input-runtime.ts +9 -2
  621. package/src/interactive/interactive-presentation.ts +21 -16
  622. package/src/interactive/interactive-shell.ts +2 -2
  623. package/src/interactive/interactive-slash-runtime.ts +99 -23
  624. package/src/interactive/interactive-subscriptions.ts +1 -1
  625. package/src/interactive/interactive-tickers.ts +1 -1
  626. package/src/interactive/keybinding-manager.ts +4 -4
  627. package/src/interactive/layout.ts +1 -1
  628. package/src/interactive/memory-overlay.ts +3 -2
  629. package/src/interactive/modal-marker.ts +151 -0
  630. package/src/interactive/mutation-preview.ts +1 -1
  631. package/src/interactive/mux-bridge.ts +214 -0
  632. package/src/interactive/overlay-frame.ts +62 -14
  633. package/src/interactive/overlay-general-openers.ts +7 -2
  634. package/src/interactive/overlay-key-routing.ts +21 -10
  635. package/src/interactive/overlay-lifecycle.ts +20 -4
  636. package/src/interactive/overlay-model-selectors.ts +43 -6
  637. package/src/interactive/overlay-permission-lifecycle.ts +90 -22
  638. package/src/interactive/overlay-transitions.ts +18 -4
  639. package/src/interactive/overlays/agents.ts +3 -2
  640. package/src/interactive/overlays/ask-user.ts +161 -41
  641. package/src/interactive/overlays/auth-dialog.ts +1 -0
  642. package/src/interactive/overlays/context-reset.ts +29 -4
  643. package/src/interactive/overlays/cwd-fallback.ts +29 -4
  644. package/src/interactive/overlays/decisions.ts +15 -13
  645. package/src/interactive/overlays/extensions.ts +1 -0
  646. package/src/interactive/overlays/fleet-run-approval.ts +4 -3
  647. package/src/interactive/overlays/handoff-review.ts +3 -2
  648. package/src/interactive/overlays/help-reference.ts +2 -0
  649. package/src/interactive/overlays/interop.ts +2 -1
  650. package/src/interactive/overlays/library-install-confirm.ts +2 -1
  651. package/src/interactive/overlays/library-tabs.ts +28 -0
  652. package/src/interactive/overlays/list-overlay.ts +10 -1
  653. package/src/interactive/overlays/message-picker.ts +2 -1
  654. package/src/interactive/overlays/model-scope.ts +86 -0
  655. package/src/interactive/overlays/model-selector.ts +40 -31
  656. package/src/interactive/overlays/prompts.ts +11 -4
  657. package/src/interactive/overlays/session-selector.ts +3 -2
  658. package/src/interactive/overlays/settings-sections.ts +30 -0
  659. package/src/interactive/overlays/settings.ts +849 -268
  660. package/src/interactive/overlays/side-question.ts +3 -2
  661. package/src/interactive/overlays/skills-hub.ts +11 -19
  662. package/src/interactive/overlays/tree-selector.ts +3 -2
  663. package/src/interactive/pane-policy.ts +46 -0
  664. package/src/interactive/panes-runtime.ts +318 -0
  665. package/src/interactive/permission-overlay.ts +2 -2
  666. package/src/interactive/renderers/compaction-summary.ts +29 -0
  667. package/src/interactive/renderers/retry-status.ts +12 -4
  668. package/src/interactive/renderers/tool-execution.ts +4 -4
  669. package/src/interactive/renderers/worker-entry.ts +122 -14
  670. package/src/interactive/session-transcript.ts +3 -3
  671. package/src/interactive/slash-autocomplete.ts +454 -165
  672. package/src/interactive/slash-commands.ts +521 -189
  673. package/src/interactive/slash-spec.ts +160 -17
  674. package/src/interactive/status/controller.ts +2 -2
  675. package/src/interactive/stream-pacing-policy.ts +1 -1
  676. package/src/interactive/tasks-overlay.ts +21 -13
  677. package/src/interactive/terminal-lease.ts +6 -7
  678. package/src/interactive/theme/tokens.ts +3 -14
  679. package/src/interactive/turn-context.ts +58 -13
  680. package/src/interactive/turn-persistence.ts +2 -2
  681. package/src/interactive/turn-prewarm.ts +2 -2
  682. package/src/interactive/turn-runtime.ts +8 -8
  683. package/src/interactive/turn-state.ts +1 -1
  684. package/src/interactive/view/artifacts.ts +120 -9
  685. package/src/interactive/view/view-overlay.ts +14 -10
  686. package/src/interactive/watch-pane.ts +180 -0
  687. package/src/interactive/welcome-dashboard.ts +15 -15
  688. package/src/interactive/worker-progress.ts +11 -5
  689. package/src/interactive/worker-receipts.ts +20 -2
  690. package/src/interactive/worker-stream.ts +6 -4
  691. package/src/interactive/yazi-bridge.ts +451 -0
  692. package/src/tools/ask-user.ts +3 -3
  693. package/src/tools/bash.ts +1 -1
  694. package/src/tools/bootstrap.ts +26 -2
  695. package/src/tools/builtin-tool-catalog.ts +16 -1
  696. package/src/tools/compete-worktrees.ts +108 -17
  697. package/src/tools/context/index.ts +15 -1
  698. package/src/tools/core-bootstrap.ts +2 -1
  699. package/src/tools/dispatch-admission.ts +18 -5
  700. package/src/tools/dispatch-arguments.ts +21 -21
  701. package/src/tools/dispatch-plan.ts +0 -11
  702. package/src/tools/dispatch-run-events.ts +145 -28
  703. package/src/tools/dispatch-runner.ts +40 -13
  704. package/src/tools/dispatch-types.ts +15 -3
  705. package/src/tools/dispatch.ts +2 -2
  706. package/src/tools/executables.ts +17 -14
  707. package/src/tools/find.ts +1 -6
  708. package/src/tools/monitor.ts +4 -4
  709. package/src/tools/observation.ts +54 -4
  710. package/src/tools/observers.ts +0 -33
  711. package/src/tools/panes-surface.ts +38 -0
  712. package/src/tools/panes.ts +112 -0
  713. package/src/tools/policy.ts +10 -1
  714. package/src/tools/presentation.ts +1 -0
  715. package/src/tools/task-worktree.ts +28 -7
  716. package/src/tools/verify/authoring.ts +3 -6
  717. package/src/tools/verify/frontend.ts +1 -1
  718. package/src/tools/web-fetch.ts +1 -1
  719. package/src/worker/entry.ts +4 -3
  720. package/src/worker/protocol.ts +16 -14
  721. package/src/worker/spec-contract.ts +2 -2
  722. package/src/worker/stdin-demux.ts +2 -2
  723. package/dist/chunk-D73KXYPF.js +0 -37
  724. package/dist/chunk-MLOK6ZOS.js +0 -2888
  725. package/dist/chunk-PNY46YEY.js +0 -1581
  726. package/dist/chunk-XDOQXGFO.js +0 -515
  727. package/dist/chunk-YKOFT37S.js +0 -279
  728. package/dist/clio-LT5V7SSZ.js +0 -26
  729. package/dist/configure-2WYWSCSD.js +0 -49
  730. package/dist/doctor-TWBWFK5V.js +0 -165
  731. package/src/engine/apis/degraded-inference.ts +0 -95
  732. /package/skills/meta/{clio-test → clio-coder-test}/references/harness.md +0 -0
  733. /package/skills/meta/{clio-test → clio-coder-test}/references/test-map.md +0 -0
@@ -5,11 +5,11 @@
5
5
  * loader has finished booting.
6
6
  *
7
7
  * There is exactly one schema: the file on disk and the in-memory shape use
8
- * the same key names (`targets`, `orchestrator.target`, ...). Validation is
8
+ * the same version-2 key names (`targets`, `chat.target`, ...). Validation is
9
9
  * strict: unknown keys and type violations are errors carrying the exact key
10
- * path; there are no legacy readers, aliases, or migrations. Missing keys take
11
- * DEFAULT_SETTINGS values, which is well-defined because the file is
12
- * machine-owned and written whole.
10
+ * path. Version-1 paths are non-executing tombstones with migration hints;
11
+ * lifecycle owns the one-time file migration. Missing keys take
12
+ * DEFAULT_SETTINGS values.
13
13
  */
14
14
 
15
15
  import { existsSync, readFileSync } from "node:fs";
@@ -25,6 +25,7 @@ import {
25
25
  THINKING_LEVELS,
26
26
  type ThinkingLevel,
27
27
  } from "./defaults.js";
28
+ import { warnLegacyNaming } from "./naming-compat.js";
28
29
  import { safeResourceWrite } from "./safe-resource-write.js";
29
30
  import { withStateFileLockSync } from "./state-file-lock.js";
30
31
  import { MAX_TIMER_DELAY_MS } from "./timers.js";
@@ -47,7 +48,7 @@ export function settingsPath(): string {
47
48
  export type SettingsIssueKind = "unreadable" | "syntax" | "schema";
48
49
 
49
50
  export interface SettingsIssue {
50
- /** Dotted key path, e.g. `orchestrator.target` or `targets[2].runtime`. */
51
+ /** Dotted key path, e.g. `chat.target` or `targets[2].runtime`. */
51
52
  path: string;
52
53
  message: string;
53
54
  kind?: SettingsIssueKind;
@@ -206,6 +207,20 @@ class Issues {
206
207
  if (!knownSet.has(key)) this.add(path ? `${path}.${key}` : key, "unknown key");
207
208
  }
208
209
  }
210
+
211
+ /**
212
+ * A key that used to be honored and is not any more. `unknown key` is the
213
+ * true answer for a typo and the wrong one here: the operator wrote a real
214
+ * key that a real release read, and the useful thing to tell them is what
215
+ * replaced it, not that the schema has never heard of it. The keys stay
216
+ * listed as known at their {@link unknownKeys} call so this message is the
217
+ * one that lands.
218
+ */
219
+ retiredKeys(path: string, raw: Record<string, unknown>, retired: Readonly<Record<string, string>>): void {
220
+ for (const [key, because] of Object.entries(retired)) {
221
+ if (key in raw) this.add(path ? `${path}.${key}` : key, `retired: ${because}. Remove this key.`);
222
+ }
223
+ }
209
224
  }
210
225
 
211
226
  function expectString(issues: Issues, path: string, value: unknown): string | undefined {
@@ -332,7 +347,13 @@ const THINKING_FORMATS = [
332
347
  ] as const;
333
348
  const STRUCTURED_OUTPUTS = ["json-schema", "gbnf", "xgrammar", "none"] as const;
334
349
  const AUTONOMY_LEVELS = ["read-only", "suggest", "auto-edit", "full-auto"] as const;
335
- const TOOL_GOVERNANCE = ["clio-policy", "agent-managed", "deny-all"] as const;
350
+ const TOOL_GOVERNANCE = ["clio-coder-policy", "agent-managed", "deny-all"] as const;
351
+
352
+ function normalizeLegacyNamingValue(value: unknown, legacy: string, canonical: string): unknown {
353
+ if (value !== legacy) return value;
354
+ warnLegacyNaming(legacy, canonical);
355
+ return canonical;
356
+ }
336
357
 
337
358
  type TargetCapabilities = NonNullable<ClioSettings["targets"][number]["capabilities"]>;
338
359
 
@@ -552,7 +573,12 @@ function validateTarget(issues: Issues, path: string, value: unknown): ClioSetti
552
573
  if (v !== undefined) target.capabilities = v;
553
574
  }
554
575
  if ("lifecycle" in value) {
555
- const v = expectEnum(issues, `${path}.lifecycle`, value.lifecycle, ["user-managed", "clio-managed"] as const);
576
+ const v = expectEnum(
577
+ issues,
578
+ `${path}.lifecycle`,
579
+ normalizeLegacyNamingValue(value.lifecycle, "clio-managed", "clio-coder-managed"),
580
+ ["user-managed", "clio-coder-managed"] as const,
581
+ );
556
582
  if (v !== undefined) target.lifecycle = v;
557
583
  }
558
584
  if ("gateway" in value) {
@@ -581,15 +607,231 @@ function validateTarget(issues: Issues, path: string, value: unknown): ClioSetti
581
607
  * every session that still mentions it. This is referential normalization,
582
608
  * not a legacy reader; the key names themselves are never aliased.
583
609
  */
584
- function validateWorkerTarget(
610
+ type FleetRoute = ClioSettings["fleet"]["default"];
611
+ type ExternalAgentDefaults = ClioSettings["integrations"]["externalAgents"]["defaults"];
612
+ type ExternalAgent = ClioSettings["integrations"]["externalAgents"]["entries"][number];
613
+ type FleetNodeConfig = ClioSettings["fleet"]["nodes"][number];
614
+
615
+ /**
616
+ * Complete durable path manifest for the settings-v1 to settings-v2 rename.
617
+ * `*` denotes a map key and `[]` denotes a list element. Values are copied
618
+ * without reinterpretation unless a separate retired-path entry says they are
619
+ * deliberately dropped.
620
+ */
621
+ export const SETTINGS_V1_PATH_MOVES = Object.freeze([
622
+ ["autonomy", "safety.autonomy"],
623
+ ["orchestrator.target", "chat.target"],
624
+ ["orchestrator.model", "chat.model"],
625
+ ["orchestrator.thinkingLevel", "chat.thinkingLevel"],
626
+ ["scope[]", "chat.modelPicker.cycleSet[]"],
627
+ ["modelSelector.favorites[]", "chat.modelPicker.favorites[]"],
628
+ ["modelSelector.recentLimit", "chat.modelPicker.recentLimit"],
629
+ ["defaults.maxTokens", "chat.maxOutputTokens"],
630
+ ["prewarm.enabled", "chat.prewarm"],
631
+ ["retry.enabled", "chat.retry.enabled"],
632
+ ["retry.maxRetries", "chat.retry.maxRetries"],
633
+ ["retry.baseDelayMs", "chat.retry.baseDelayMs"],
634
+ ["retry.maxDelayMs", "chat.retry.maxDelayMs"],
635
+ ["retry.streamStallMs", "chat.retry.streamStallMs"],
636
+ ["workers.default.target", "fleet.default.target"],
637
+ ["workers.default.model", "fleet.default.model"],
638
+ ["workers.default.thinkingLevel", "fleet.default.thinkingLevel"],
639
+ ["workers.default.node", "fleet.default.node"],
640
+ ["workers.profiles.*.target", "fleet.profiles.*.target"],
641
+ ["workers.profiles.*.model", "fleet.profiles.*.model"],
642
+ ["workers.profiles.*.thinkingLevel", "fleet.profiles.*.thinkingLevel"],
643
+ ["workers.profiles.*.node", "fleet.profiles.*.node"],
644
+ ["workers.rosters.*.members[].label", "fleet.rosters.*.members[].label"],
645
+ ["workers.rosters.*.members[].target", "fleet.rosters.*.members[].target"],
646
+ ["workers.rosters.*.members[].model", "fleet.rosters.*.members[].model"],
647
+ ["workers.rosters.*.members[].thinking", "fleet.rosters.*.members[].thinkingLevel"],
648
+ ["workers.rosters.*.members[].color", "fleet.rosters.*.members[].color"],
649
+ ["workers.agentBindings.*", "fleet.agentProfiles.*"],
650
+ ["routing.activeRoles[]", "fleet.adaptiveRouting.roles[]"],
651
+ ["routing.activePostures[]", "fleet.adaptiveRouting.postures[]"],
652
+ ["routing.agentAutomation.activeAgentRoles[].agentId", "fleet.adaptiveRouting.agentRoles[].agentId"],
653
+ ["routing.agentAutomation.activeAgentRoles[].executionRole", "fleet.adaptiveRouting.agentRoles[].executionRole"],
654
+ ["budget.concurrency", "fleet.concurrency"],
655
+ ["workers.maxRetries", "fleet.retry.maxRetries"],
656
+ ["workers.resilienceCooldownMs", "fleet.retry.routeCooldownMs"],
657
+ ["workers.onPermission", "fleet.permissions.mode"],
658
+ ["workers.escalation.timeoutMs", "fleet.permissions.escalation.timeoutMs"],
659
+ ["workers.escalation.fallback", "fleet.permissions.escalation.fallback"],
660
+ ["guardrails.workerToolCallCap", "fleet.limits.toolCallsPerRun"],
661
+ ["guardrails.internalDispatchTimeoutMs", "fleet.limits.internalRunTimeoutMs"],
662
+ ["guardrails.maxDispatchRuns", "fleet.history.maxRuns"],
663
+ ["compaction.auto", "context.compaction.auto"],
664
+ ["compaction.threshold", "context.compaction.threshold"],
665
+ ["compaction.model", "context.compaction.model"],
666
+ ["compaction.systemPrompt", "context.compaction.systemPrompt"],
667
+ ["background.target", "context.memory.target"],
668
+ ["background.model", "context.memory.model"],
669
+ ["memory.intervention.enabled", "context.memory.enabled"],
670
+ ["memory.intervention.everyNTools", "context.memory.cadenceToolCalls"],
671
+ ["memory.intervention.windowSteps", "context.memory.trajectorySteps"],
672
+ ["memory.intervention.maxTokens", "context.memory.maxOutputTokens"],
673
+ ["memory.intervention.timeoutMs", "context.memory.timeoutMs"],
674
+ ["budget.sessionCeilingUsd", "safety.limits.sessionCostUsd"],
675
+ ["guardrails.turnToolCallBudget", "safety.limits.chatToolCallsPerTurn"],
676
+ ["guardrails.readMaxBytes", "safety.limits.readBytesPerCall"],
677
+ ["guardrails.observationTurnBudgetBytes", "safety.limits.observationBytesPerTurn"],
678
+ ["watchdog.enabled", "safety.review.enabled"],
679
+ ["watchdog.target", "safety.review.target"],
680
+ ["watchdog.cadenceToolCalls", "safety.review.cadenceToolCalls"],
681
+ ["terminal.outputVerbosity", "interface.outputDetail"],
682
+ ["terminal.smoothStreaming", "interface.smoothStreaming"],
683
+ ["terminal.tuiMode", "interface.mode"],
684
+ ["terminal.fullscreenScrollbar", "interface.fullscreenScrollbar"],
685
+ ["terminal.showTerminalProgress", "interface.terminalProgress"],
686
+ ["terminal.notify", "interface.desktopNotifications"],
687
+ // The panes keys shipped for exactly one release (v0.4.0, the day before
688
+ // this rework) but they did ship, and each has a direct v2 successor with
689
+ // an identical value domain, so they move like everything else. The
690
+ // retired agents/keepFailed pair and the `panes` root itself stay in
691
+ // SETTINGS_V1_RETIRED-adjacent handling via V1_ONLY_ROOTS.
692
+ ["panes.enabled", "interface.panes.enabled"],
693
+ ["panes.notifications", "interface.panes.notifications"],
694
+ ["panes.journal", "fleet.history.journal"],
695
+ ["panes.yazi.enabled", "interface.panes.files.enabled"],
696
+ ["panes.yazi.mode", "interface.panes.files.mode"],
697
+ ["panes.yazi.profile", "interface.panes.files.profile"],
698
+ ["panes.yazi.followCwd", "interface.panes.files.followCwd"],
699
+ ["keybindings.*", "interface.keybindings.*"],
700
+ ["skills.trustProjectCompatRoots", "integrations.projectResources.trustProjectImports"],
701
+ ["delegation.defaults.connectTimeoutMs", "integrations.externalAgents.defaults.connectTimeoutMs"],
702
+ ["delegation.defaults.turnTimeoutMs", "integrations.externalAgents.defaults.turnTimeoutMs"],
703
+ ["delegation.defaults.permissionTimeoutMs", "integrations.externalAgents.defaults.permissionTimeoutMs"],
704
+ ["delegation.defaults.toolGovernance", "integrations.externalAgents.defaults.toolGovernance"],
705
+ ["delegation.agents[].id", "integrations.externalAgents.entries[].id"],
706
+ ["delegation.agents[].command", "integrations.externalAgents.entries[].command"],
707
+ ["delegation.agents[].args[]", "integrations.externalAgents.entries[].args[]"],
708
+ ["delegation.agents[].cwd", "integrations.externalAgents.entries[].cwd"],
709
+ ["delegation.agents[].env.*", "integrations.externalAgents.entries[].env.*"],
710
+ ["delegation.agents[].connectTimeoutMs", "integrations.externalAgents.entries[].connectTimeoutMs"],
711
+ ["delegation.agents[].turnTimeoutMs", "integrations.externalAgents.entries[].turnTimeoutMs"],
712
+ ["delegation.agents[].permissionTimeoutMs", "integrations.externalAgents.entries[].permissionTimeoutMs"],
713
+ ["delegation.agents[].stallTimeoutMs", "integrations.externalAgents.entries[].stallTimeoutMs"],
714
+ ["delegation.agents[].toolGovernance", "integrations.externalAgents.entries[].toolGovernance"],
715
+ ["delegation.agents[].projectContext", "integrations.externalAgents.entries[].projectContext"],
716
+ ["delegation.agents[].labels.*", "integrations.externalAgents.entries[].labels.*"],
717
+ ["runtimePlugins[]", "integrations.runtimePlugins[]"],
718
+ ["library.catalog", "integrations.library.catalog"],
719
+ ["library.remote", "integrations.library.remote"],
720
+ ["library.confirmedRemote", "integrations.library.confirmedRemote"],
721
+ ["library.sync", "integrations.library.sync"],
722
+ ["attribution.gitCommits", "integrations.git.commitAttribution"],
723
+ ["fleet.nodes[].clioEntry", "fleet.nodes[].clioCoderEntry"],
724
+ ] as const);
725
+
726
+ export const SETTINGS_V1_RETIRED_PATHS = Object.freeze({
727
+ identity: "it was accepted and ignored; no behavior is lost",
728
+ "background.thinkingLevel": "proactive memory always resolves thinking off",
729
+ theme: "the only registered theme was not read by runtime rendering",
730
+ "compaction.excludeLastTurns": "only the temporary legacy mask used it; context.workingSet.protectLastTurns remains",
731
+ } as const);
732
+
733
+ const V1_ONLY_ROOTS = new Set([
734
+ "identity",
735
+ "autonomy",
736
+ "runtimePlugins",
737
+ "orchestrator",
738
+ "background",
739
+ "memory",
740
+ "watchdog",
741
+ "workers",
742
+ "routing",
743
+ "scope",
744
+ "modelSelector",
745
+ "budget",
746
+ "defaults",
747
+ "theme",
748
+ "terminal",
749
+ "skills",
750
+ "library",
751
+ "attribution",
752
+ "delegation",
753
+ "keybindings",
754
+ "compaction",
755
+ "prewarm",
756
+ "panes",
757
+ "retry",
758
+ "guardrails",
759
+ ]);
760
+
761
+ interface PatternMatch {
762
+ path: string;
763
+ }
764
+
765
+ function pathPatternSegments(pattern: string): string[] {
766
+ return pattern.split(".");
767
+ }
768
+
769
+ function patternMatches(value: unknown, pattern: string): PatternMatch[] {
770
+ const out: PatternMatch[] = [];
771
+ const visit = (current: unknown, segments: ReadonlyArray<string>, path: string): void => {
772
+ const segment = segments[0];
773
+ if (segment === undefined) {
774
+ out.push({ path });
775
+ return;
776
+ }
777
+ const rest = segments.slice(1);
778
+ if (segment === "*") {
779
+ if (!isPlainObject(current)) return;
780
+ for (const [key, child] of Object.entries(current)) visit(child, rest, path ? `${path}.${key}` : key);
781
+ return;
782
+ }
783
+ const list = segment.endsWith("[]");
784
+ const key = list ? segment.slice(0, -2) : segment;
785
+ if (!isPlainObject(current) || !(key in current)) return;
786
+ const child = current[key];
787
+ const nextPath = path ? `${path}.${key}` : key;
788
+ if (!list) {
789
+ visit(child, rest, nextPath);
790
+ return;
791
+ }
792
+ if (!Array.isArray(child)) return;
793
+ for (let index = 0; index < child.length; index += 1) visit(child[index], rest, `${nextPath}[${index}]`);
794
+ };
795
+ visit(value, pathPatternSegments(pattern), "");
796
+ return out;
797
+ }
798
+
799
+ function reportV1Tombstones(issues: Issues, raw: Record<string, unknown>): void {
800
+ const reportedRoots = new Set<string>();
801
+ for (const [from, to] of SETTINGS_V1_PATH_MOVES) {
802
+ for (const match of patternMatches(raw, from)) {
803
+ issues.add(match.path, `retired settings-v1 path; use ${to} in version 2`);
804
+ reportedRoots.add(match.path.split(/[.[]/u)[0] ?? match.path);
805
+ }
806
+ }
807
+ for (const [path, reason] of Object.entries(SETTINGS_V1_RETIRED_PATHS)) {
808
+ for (const match of patternMatches(raw, path)) {
809
+ issues.add(match.path, `retired without replacement: ${reason}. Remove this key`);
810
+ reportedRoots.add(match.path.split(/[.[]/u)[0] ?? match.path);
811
+ }
812
+ }
813
+ for (const root of V1_ONLY_ROOTS) {
814
+ if (root in raw && !reportedRoots.has(root)) {
815
+ const replacements = new Set(
816
+ SETTINGS_V1_PATH_MOVES.filter(([from]) => from.split(/[.[]/u)[0] === root).map(
817
+ ([, to]) => to.split(/[.[]/u)[0] ?? to,
818
+ ),
819
+ );
820
+ const hint = replacements.size > 0 ? `; use ${[...replacements].join(" or ")} in version 2` : "";
821
+ issues.add(root, `retired settings-v1 namespace${hint}; migrate this document with \`clio-coder upgrade\``);
822
+ }
823
+ }
824
+ }
825
+
826
+ function validateRoute(
585
827
  issues: Issues,
586
828
  path: string,
587
829
  value: unknown,
588
- defaults: ClioSettings["orchestrator"],
830
+ defaults: FleetRoute,
589
831
  targets: ReadonlyArray<ClioSettings["targets"][number]>,
590
832
  options?: { nodeIds?: ReadonlySet<string> },
591
- ): ClioSettings["orchestrator"] & { node?: string } {
592
- const out: ClioSettings["orchestrator"] & { node?: string } = cloneValue(defaults);
833
+ ): FleetRoute {
834
+ const out = cloneValue(defaults);
593
835
  if (!isPlainObject(value)) {
594
836
  issues.add(path, `expected a map, got ${describe(value)}`);
595
837
  return out;
@@ -600,80 +842,58 @@ function validateWorkerTarget(
600
842
  value,
601
843
  allowNode ? ["target", "model", "thinkingLevel", "node"] : ["target", "model", "thinkingLevel"],
602
844
  );
603
- if ("target" in value && value.target !== null) {
604
- const v = expectString(issues, `${path}.target`, value.target);
605
- if (v !== undefined) out.target = targets.some((entry) => entry.id === v) ? v : null;
845
+ if ("target" in value) {
846
+ if (value.target === null) out.target = null;
847
+ else {
848
+ const parsed = expectString(issues, `${path}.target`, value.target);
849
+ if (parsed !== undefined) out.target = targets.some((entry) => entry.id === parsed) ? parsed : null;
850
+ }
606
851
  }
607
- if ("thinkingLevel" in value) {
608
- const v = expectEnum(issues, `${path}.thinkingLevel`, value.thinkingLevel, THINKING_LEVELS);
609
- if (v !== undefined) out.thinkingLevel = v;
610
- }
611
- if ("model" in value && value.model !== null) {
612
- const v = expectString(issues, `${path}.model`, value.model);
613
- if (v !== undefined) out.model = v;
614
- }
615
- // Node pins follow the target idiom: a pin naming a node that is not
616
- // configured (and is not the implicit local node) is dropped, not fatal.
617
- if (allowNode && "node" in value && value.node !== null) {
618
- const v = expectString(issues, `${path}.node`, value.node);
619
- if (v !== undefined) {
620
- const id = v.trim();
621
- if (id.length > 0 && (id === "local" || options?.nodeIds?.has(id) === true)) out.node = id;
852
+ if ("model" in value) {
853
+ if (value.model === null) out.model = null;
854
+ else {
855
+ const parsed = expectString(issues, `${path}.model`, value.model);
856
+ if (parsed !== undefined) out.model = parsed;
622
857
  }
623
858
  }
624
- if (!out.target) {
625
- out.model = null;
626
- } else if (out.model === null) {
627
- out.model = targets.find((entry) => entry.id === out.target)?.defaultModel ?? null;
859
+ if ("thinkingLevel" in value) {
860
+ const parsed = expectEnum(issues, `${path}.thinkingLevel`, value.thinkingLevel, THINKING_LEVELS);
861
+ if (parsed !== undefined) out.thinkingLevel = parsed;
862
+ }
863
+ if (allowNode && "node" in value) {
864
+ if (value.node === null) delete out.node;
865
+ else {
866
+ const parsed = expectString(issues, `${path}.node`, value.node);
867
+ if (parsed !== undefined && (parsed === "local" || options.nodeIds?.has(parsed) === true)) out.node = parsed;
868
+ }
628
869
  }
870
+ if (!out.target) out.model = null;
871
+ else if (out.model === null) out.model = targets.find((entry) => entry.id === out.target)?.defaultModel ?? null;
629
872
  return out;
630
873
  }
631
874
 
632
- /** `targetId/wireModelId` refs filtered to configured target ids. */
633
875
  function normalizeModelRefs(
634
876
  refs: ReadonlyArray<string>,
635
877
  targets: ReadonlyArray<ClioSettings["targets"][number]>,
636
878
  ): string[] {
637
- const byId = new Set(targets.map((target) => target.id));
638
- const out: string[] = [];
639
- for (const ref of refs) {
640
- const [targetId, ...modelParts] = ref.split("/");
641
- if (!targetId || !byId.has(targetId) || modelParts.length === 0) continue;
642
- const model = modelParts.join("/").trim();
643
- if (!model) continue;
644
- const normalized = `${targetId}/${model}`;
645
- if (!out.includes(normalized)) out.push(normalized);
646
- }
647
- return out;
879
+ const targetIds = new Set(targets.map((target) => target.id));
880
+ return refs.filter((ref) => {
881
+ const [targetId, ...model] = ref.split("/");
882
+ return Boolean(targetId && targetIds.has(targetId) && model.join("/").trim());
883
+ });
648
884
  }
649
885
 
650
- /**
651
- * Scope entries: `targetId` or `targetId/wireModelId`, deduplicated. A ref
652
- * whose target is not configured is kept, not dropped: a renamed or
653
- * temporarily removed target is the common staleness case, and silently
654
- * deleting the operator's cycle set at load destroys a preference they never
655
- * asked to change. Disclosure belongs to the presentation layer, which lists
656
- * unresolvable refs under "Unavailable"; the consumers that must resolve a ref
657
- * (advanceScopedTarget) filter it themselves.
658
- */
659
- function normalizeScope(refs: ReadonlyArray<string>): string[] {
660
- const out: string[] = [];
661
- for (const ref of refs) {
662
- const trimmed = ref.trim();
663
- const [targetId] = trimmed.split("/");
664
- if (!targetId) continue;
665
- if (!out.includes(trimmed)) out.push(trimmed);
666
- }
667
- return out;
886
+ function normalizeCycleSet(refs: ReadonlyArray<string>): string[] {
887
+ return refs.filter((ref) => Boolean(ref.trim().split("/")[0]));
668
888
  }
669
889
 
670
- function validateDelegationAgent(
890
+ function validateExternalAgent(
671
891
  issues: Issues,
672
892
  path: string,
673
893
  value: unknown,
674
- defaults: ClioSettings["delegation"]["defaults"],
894
+ defaults: ExternalAgentDefaults,
675
895
  seen: Set<string>,
676
- ): ClioSettings["delegation"]["agents"][number] | null {
896
+ ): ExternalAgent | null {
677
897
  if (!isPlainObject(value)) {
678
898
  issues.add(path, `expected a map, got ${describe(value)}`);
679
899
  return null;
@@ -702,11 +922,11 @@ function validateDelegationAgent(
702
922
  return null;
703
923
  }
704
924
  if (seen.has(id)) {
705
- issues.add(`${path}.id`, `duplicate delegation agent id '${id}'`);
925
+ issues.add(`${path}.id`, `duplicate external agent id '${id}'`);
706
926
  return null;
707
927
  }
708
928
  seen.add(id);
709
- const agent: ClioSettings["delegation"]["agents"][number] = {
929
+ const agent: ExternalAgent = {
710
930
  id,
711
931
  command,
712
932
  args: [],
@@ -716,89 +936,44 @@ function validateDelegationAgent(
716
936
  toolGovernance: defaults.toolGovernance,
717
937
  };
718
938
  if ("args" in value) {
719
- const v = expectStringArray(issues, `${path}.args`, value.args);
720
- if (v !== undefined) agent.args = v;
939
+ const parsed = expectStringArray(issues, `${path}.args`, value.args);
940
+ if (parsed !== undefined) agent.args = parsed;
721
941
  }
722
942
  for (const key of ["connectTimeoutMs", "turnTimeoutMs", "permissionTimeoutMs"] as const) {
723
- if (key in value) {
724
- const v = expectInteger(issues, `${path}.${key}`, value[key], { min: 1, max: MAX_TIMER_DELAY_MS });
725
- if (v !== undefined) agent[key] = v;
726
- }
943
+ if (!(key in value)) continue;
944
+ const parsed = expectInteger(issues, `${path}.${key}`, value[key], { min: 1, max: MAX_TIMER_DELAY_MS });
945
+ if (parsed !== undefined) agent[key] = parsed;
727
946
  }
728
947
  if ("stallTimeoutMs" in value) {
729
- const v = expectInteger(issues, `${path}.stallTimeoutMs`, value.stallTimeoutMs);
730
- if (v !== undefined) agent.stallTimeoutMs = v;
948
+ const parsed = expectInteger(issues, `${path}.stallTimeoutMs`, value.stallTimeoutMs);
949
+ if (parsed !== undefined) agent.stallTimeoutMs = parsed;
731
950
  }
732
951
  if ("toolGovernance" in value) {
733
- const v = expectEnum(issues, `${path}.toolGovernance`, value.toolGovernance, TOOL_GOVERNANCE);
734
- if (v !== undefined) agent.toolGovernance = v;
952
+ const parsed = expectEnum(
953
+ issues,
954
+ `${path}.toolGovernance`,
955
+ normalizeLegacyNamingValue(value.toolGovernance, "clio-policy", "clio-coder-policy"),
956
+ TOOL_GOVERNANCE,
957
+ );
958
+ if (parsed !== undefined) agent.toolGovernance = parsed;
735
959
  }
736
960
  if ("projectContext" in value) {
737
- const v = expectEnum(issues, `${path}.projectContext`, value.projectContext, ["none", "bounded"] as const);
738
- if (v !== undefined) agent.projectContext = v;
739
- }
740
- if ("cwd" in value) {
741
- const v = expectString(issues, `${path}.cwd`, value.cwd);
742
- if (v !== undefined) agent.cwd = v;
961
+ const parsed = expectEnum(issues, `${path}.projectContext`, value.projectContext, ["none", "bounded"] as const);
962
+ if (parsed !== undefined) agent.projectContext = parsed;
743
963
  }
744
- if ("env" in value) {
745
- const v = expectStringRecord(issues, `${path}.env`, value.env);
746
- if (v !== undefined && Object.keys(v).length > 0) agent.env = v;
964
+ for (const key of ["cwd"] as const) {
965
+ if (!(key in value)) continue;
966
+ const parsed = expectString(issues, `${path}.${key}`, value[key]);
967
+ if (parsed !== undefined) agent[key] = parsed;
747
968
  }
748
- if ("labels" in value) {
749
- const v = expectStringRecord(issues, `${path}.labels`, value.labels);
750
- if (v !== undefined && Object.keys(v).length > 0) agent.labels = v;
969
+ for (const key of ["env", "labels"] as const) {
970
+ if (!(key in value)) continue;
971
+ const parsed = expectStringRecord(issues, `${path}.${key}`, value[key]);
972
+ if (parsed !== undefined && Object.keys(parsed).length > 0) agent[key] = parsed;
751
973
  }
752
974
  return agent;
753
975
  }
754
976
 
755
- function validateDelegation(issues: Issues, value: unknown): ClioSettings["delegation"] {
756
- const out = cloneValue(DEFAULT_SETTINGS.delegation);
757
- if (!isPlainObject(value)) {
758
- issues.add("delegation", `expected a map, got ${describe(value)}`);
759
- return out;
760
- }
761
- issues.unknownKeys("delegation", value, ["agents", "defaults"]);
762
- if ("defaults" in value) {
763
- if (!isPlainObject(value.defaults)) {
764
- issues.add("delegation.defaults", `expected a map, got ${describe(value.defaults)}`);
765
- } else {
766
- issues.unknownKeys("delegation.defaults", value.defaults, [
767
- "connectTimeoutMs",
768
- "turnTimeoutMs",
769
- "permissionTimeoutMs",
770
- "toolGovernance",
771
- ]);
772
- for (const key of ["connectTimeoutMs", "turnTimeoutMs", "permissionTimeoutMs"] as const) {
773
- if (key in value.defaults) {
774
- const v = expectInteger(issues, `delegation.defaults.${key}`, value.defaults[key], {
775
- min: 1,
776
- max: MAX_TIMER_DELAY_MS,
777
- });
778
- if (v !== undefined) out.defaults[key] = v;
779
- }
780
- }
781
- if ("toolGovernance" in value.defaults) {
782
- const v = expectEnum(issues, "delegation.defaults.toolGovernance", value.defaults.toolGovernance, TOOL_GOVERNANCE);
783
- if (v !== undefined) out.defaults.toolGovernance = v;
784
- }
785
- }
786
- }
787
- if ("agents" in value) {
788
- if (!Array.isArray(value.agents)) {
789
- issues.add("delegation.agents", `expected a list, got ${describe(value.agents)}`);
790
- } else {
791
- const seen = new Set<string>();
792
- out.agents = value.agents
793
- .map((entry, i) => validateDelegationAgent(issues, `delegation.agents[${i}]`, entry, out.defaults, seen))
794
- .filter((entry): entry is NonNullable<typeof entry> => entry !== null);
795
- }
796
- }
797
- return out;
798
- }
799
-
800
- type FleetNodeConfig = ClioSettings["fleet"]["nodes"][number];
801
-
802
977
  function validateFleetNode(issues: Issues, path: string, value: unknown, seen: Set<string>): FleetNodeConfig | null {
803
978
  if (!isPlainObject(value)) {
804
979
  issues.add(path, `expected a map, got ${describe(value)}`);
@@ -811,7 +986,6 @@ function validateFleetNode(issues: Issues, path: string, value: unknown, seen: S
811
986
  "port",
812
987
  "identityFile",
813
988
  "clioCoderEntry",
814
- // Pre-0.3.1 spelling, still read.
815
989
  "clioEntry",
816
990
  "labels",
817
991
  "maxWorkers",
@@ -822,196 +996,79 @@ function validateFleetNode(issues: Issues, path: string, value: unknown, seen: S
822
996
  if (!("id" in value)) issues.add(`${path}.id`, "required");
823
997
  if (!("host" in value)) issues.add(`${path}.host`, "required");
824
998
  if (id === undefined || host === undefined) return null;
825
- const trimmedId = id.trim();
826
- if (trimmedId.length === 0) {
827
- issues.add(`${path}.id`, "must not be blank");
828
- return null;
829
- }
830
- if (trimmedId === "local") {
999
+ if (id === "local") {
831
1000
  issues.add(`${path}.id`, "'local' is reserved for the implicit local node");
832
1001
  return null;
833
1002
  }
834
- if (seen.has(trimmedId)) {
835
- issues.add(`${path}.id`, `duplicate fleet node id '${trimmedId}'`);
1003
+ if (seen.has(id)) {
1004
+ issues.add(`${path}.id`, `duplicate fleet node id '${id}'`);
836
1005
  return null;
837
1006
  }
838
- seen.add(trimmedId);
839
- const node: FleetNodeConfig = { id: trimmedId, host, maxWorkers: 2 };
840
- if ("user" in value) {
841
- const v = expectString(issues, `${path}.user`, value.user);
842
- if (v !== undefined) node.user = v;
1007
+ seen.add(id);
1008
+ const node: FleetNodeConfig = { id, host, maxWorkers: 2 };
1009
+ for (const key of ["user", "identityFile", "clioCoderEntry"] as const) {
1010
+ if (!(key in value)) continue;
1011
+ const parsed = expectString(issues, `${path}.${key}`, value[key]);
1012
+ if (parsed !== undefined) node[key] = parsed;
843
1013
  }
844
1014
  if ("port" in value) {
845
- const v = expectInteger(issues, `${path}.port`, value.port, { min: 1 });
846
- if (v !== undefined) node.port = v;
847
- }
848
- if ("identityFile" in value) {
849
- const v = expectString(issues, `${path}.identityFile`, value.identityFile);
850
- if (v !== undefined) node.identityFile = v;
851
- }
852
- // The key was `clioEntry` before 0.3.1. Both spellings read; the new one
853
- // wins when a node carries both.
854
- if ("clioEntry" in value) {
855
- const v = expectString(issues, `${path}.clioEntry`, value.clioEntry);
856
- if (v !== undefined) node.clioCoderEntry = v;
857
- }
858
- if ("clioCoderEntry" in value) {
859
- const v = expectString(issues, `${path}.clioCoderEntry`, value.clioCoderEntry);
860
- if (v !== undefined) node.clioCoderEntry = v;
1015
+ const parsed = expectInteger(issues, `${path}.port`, value.port, { min: 1 });
1016
+ if (parsed !== undefined) node.port = parsed;
861
1017
  }
862
1018
  if ("labels" in value) {
863
- const v = expectStringArray(issues, `${path}.labels`, value.labels);
864
- if (v !== undefined) node.labels = v;
1019
+ const parsed = expectStringArray(issues, `${path}.labels`, value.labels);
1020
+ if (parsed !== undefined) node.labels = parsed;
865
1021
  }
866
1022
  if ("maxWorkers" in value) {
867
- const v = expectInteger(issues, `${path}.maxWorkers`, value.maxWorkers, { min: 1 });
868
- if (v !== undefined) node.maxWorkers = v;
1023
+ const parsed = expectInteger(issues, `${path}.maxWorkers`, value.maxWorkers, { min: 1 });
1024
+ if (parsed !== undefined) node.maxWorkers = parsed;
869
1025
  }
870
1026
  if ("residency" in value) {
871
- const v = expectEnum(issues, `${path}.residency`, value.residency, ["observe", "manage"] as const);
872
- if (v !== undefined) node.residency = v;
1027
+ const parsed = expectEnum(issues, `${path}.residency`, value.residency, ["observe", "manage"] as const);
1028
+ if (parsed !== undefined) node.residency = parsed;
873
1029
  }
874
1030
  return node;
875
1031
  }
876
1032
 
877
- function validateFleet(issues: Issues, value: unknown): ClioSettings["fleet"] {
878
- const out = cloneValue(DEFAULT_SETTINGS.fleet);
879
- if (!isPlainObject(value)) {
880
- issues.add("fleet", `expected a map, got ${describe(value)}`);
881
- return out;
882
- }
883
- issues.unknownKeys("fleet", value, ["nodes"]);
884
- if ("nodes" in value) {
885
- if (!Array.isArray(value.nodes)) {
886
- issues.add("fleet.nodes", `expected a list, got ${describe(value.nodes)}`);
887
- } else {
888
- const seen = new Set<string>();
889
- out.nodes = value.nodes
890
- .map((entry, i) => validateFleetNode(issues, `fleet.nodes[${i}]`, entry, seen))
891
- .filter((entry): entry is NonNullable<typeof entry> => entry !== null);
892
- }
893
- }
894
- return out;
895
- }
896
-
897
- function validateMemorySettings(issues: Issues, value: unknown): ClioSettings["memory"] {
898
- const out = cloneValue(DEFAULT_SETTINGS.memory);
899
- if (!isPlainObject(value)) {
900
- issues.add("memory", `expected a map, got ${describe(value)}`);
901
- return out;
902
- }
903
- issues.unknownKeys("memory", value, ["intervention"]);
904
- if (!("intervention" in value)) return out;
905
- if (!isPlainObject(value.intervention)) {
906
- issues.add("memory.intervention", `expected a map, got ${describe(value.intervention)}`);
907
- return out;
908
- }
909
- const raw = value.intervention;
910
- issues.unknownKeys("memory.intervention", raw, ["enabled", "everyNTools", "windowSteps", "maxTokens", "timeoutMs"]);
911
- if ("enabled" in raw) {
912
- const enabled = expectBoolean(issues, "memory.intervention.enabled", raw.enabled);
913
- if (enabled !== undefined) out.intervention.enabled = enabled;
914
- }
915
- for (const key of ["everyNTools", "windowSteps", "maxTokens", "timeoutMs"] as const) {
916
- if (!(key in raw)) continue;
917
- const parsed = expectInteger(issues, `memory.intervention.${key}`, raw[key], {
918
- min: key === "everyNTools" ? 2 : 1,
919
- });
920
- if (parsed !== undefined) out.intervention[key] = parsed;
921
- }
922
- return out;
923
- }
924
-
925
- /**
926
- * The watchdog block. `target` and `cadenceToolCalls` stay absent rather than
927
- * taking sentinel values: an absent target means the session's active target,
928
- * and an absent cadence means the watchdog fires only at turn end. A written
929
- * default for either would turn "not configured" into a configuration.
930
- */
931
- function validateWatchdogSettings(issues: Issues, value: unknown): ClioSettings["watchdog"] {
932
- const out = cloneValue(DEFAULT_SETTINGS.watchdog);
933
- if (!isPlainObject(value)) {
934
- issues.add("watchdog", `expected a map, got ${describe(value)}`);
935
- return out;
936
- }
937
- issues.unknownKeys("watchdog", value, ["enabled", "target", "cadenceToolCalls"]);
938
- if ("enabled" in value) {
939
- const enabled = expectBoolean(issues, "watchdog.enabled", value.enabled);
940
- if (enabled !== undefined) out.enabled = enabled;
941
- }
942
- if ("target" in value && value.target !== null) {
943
- const target = expectString(issues, "watchdog.target", value.target);
944
- if (target !== undefined) {
945
- if (target.trim().length === 0) issues.add("watchdog.target", "expected a non-empty target id");
946
- else out.target = target.trim();
947
- }
948
- }
949
- if ("cadenceToolCalls" in value && value.cadenceToolCalls !== null) {
950
- const cadence = expectInteger(issues, "watchdog.cadenceToolCalls", value.cadenceToolCalls, { min: 1 });
951
- if (cadence !== undefined) out.cadenceToolCalls = cadence;
952
- }
953
- return out;
954
- }
955
-
956
- function validateKeybindings(issues: Issues, value: unknown): ClioSettings["keybindings"] {
1033
+ function validateKeybindings(issues: Issues, path: string, value: unknown): Record<string, string | string[]> {
957
1034
  if (!isPlainObject(value)) {
958
- issues.add("keybindings", `expected a map, got ${describe(value)}`);
1035
+ issues.add(path, `expected a map, got ${describe(value)}`);
959
1036
  return {};
960
1037
  }
961
1038
  const next: Record<string, string | string[]> = {};
962
1039
  for (const [rawKey, rawValue] of Object.entries(value)) {
963
- const id = rawKey.trim();
1040
+ const legacyId = rawKey.trim();
1041
+ const id = legacyId.startsWith("clio.") ? `clio-coder.${legacyId.slice("clio.".length)}` : legacyId;
964
1042
  if (!id) {
965
- issues.add("keybindings", "empty keybinding id");
966
- continue;
967
- }
968
- if (typeof rawValue === "string") {
969
- const v = expectString(issues, `keybindings.${id}`, rawValue);
970
- if (v !== undefined) next[id] = v;
1043
+ issues.add(path, "empty keybinding id");
971
1044
  continue;
972
1045
  }
973
- if (Array.isArray(rawValue)) {
974
- const v = expectStringArray(issues, `keybindings.${id}`, rawValue);
975
- if (v !== undefined && v.length > 0) next[id] = v;
976
- continue;
1046
+ if (id !== legacyId) {
1047
+ warnLegacyNaming(legacyId, id);
1048
+ // Canonical values win even when the legacy key occurs later in YAML.
1049
+ if (Object.hasOwn(value, id)) continue;
977
1050
  }
978
- issues.add(`keybindings.${id}`, `expected a string or list of strings, got ${describe(rawValue)}`);
1051
+ if (typeof rawValue === "string") {
1052
+ const parsed = expectString(issues, `${path}.${legacyId}`, rawValue);
1053
+ if (parsed !== undefined) next[id] = parsed;
1054
+ } else if (Array.isArray(rawValue)) {
1055
+ const parsed = expectStringArray(issues, `${path}.${legacyId}`, rawValue);
1056
+ if (parsed !== undefined && parsed.length > 0) next[id] = parsed;
1057
+ } else issues.add(`${path}.${legacyId}`, `expected a string or list of strings, got ${describe(rawValue)}`);
979
1058
  }
980
1059
  return next;
981
1060
  }
982
1061
 
983
1062
  const TOP_LEVEL_KEYS = [
984
1063
  "version",
985
- // Accepted and ignored. Settings files generated before 0.3.1 carry an
986
- // `identity:` line for a control nothing ever read; listing it here keeps
987
- // those files loading without an unknown-key issue.
988
- "identity",
989
- "autonomy",
990
1064
  "targets",
991
- "runtimePlugins",
992
- "orchestrator",
993
- "background",
994
- "memory",
995
- "watchdog",
996
- "workers",
1065
+ "chat",
997
1066
  "fleet",
998
- "routing",
999
- "scope",
1000
- "modelSelector",
1001
- "budget",
1002
- "defaults",
1003
- "theme",
1004
- "terminal",
1005
- "skills",
1006
- "library",
1007
- "attribution",
1008
- "delegation",
1009
- "keybindings",
1010
- "compaction",
1011
1067
  "context",
1012
- "prewarm",
1013
- "retry",
1014
- "guardrails",
1068
+ "safety",
1069
+ "interface",
1070
+ "integrations",
1071
+ ...V1_ONLY_ROOTS,
1015
1072
  ] as const;
1016
1073
 
1017
1074
  export interface SettingsValidationResult {
@@ -1029,529 +1086,412 @@ export interface SettingsValidationResult {
1029
1086
  export function validateSettings(raw: unknown): SettingsValidationResult {
1030
1087
  const issues = new Issues();
1031
1088
  const settings = cloneValue(DEFAULT_SETTINGS);
1032
- // A genuinely missing settings file falls back to defaults before ever
1033
- // reaching here (validateSettingsFile returns early when the file is absent).
1034
- // So a null/undefined raw here means a present-but-empty or `null` document,
1035
- // which is malformed and must be a root-shape issue, not silent defaults.
1036
1089
  if (!isPlainObject(raw)) {
1037
1090
  issues.add("(root)", `expected a map, got ${describe(raw)}`);
1038
1091
  return { settings, issues: issues.list };
1039
1092
  }
1093
+ reportV1Tombstones(issues, raw);
1040
1094
  issues.unknownKeys("", raw, TOP_LEVEL_KEYS);
1041
-
1042
- if ("version" in raw && raw.version !== 1) {
1043
- issues.add("version", `expected 1, got ${describe(raw.version)}`);
1044
- }
1045
- if ("autonomy" in raw) {
1046
- const v = expectEnum(issues, "autonomy", raw.autonomy, AUTONOMY_LEVELS);
1047
- if (v !== undefined) settings.autonomy = v;
1095
+ if ("version" in raw && raw.version !== 2) {
1096
+ issues.add("version", `expected 2, got ${describe(raw.version)}; run \`clio-coder upgrade\` for version 1`);
1048
1097
  }
1049
1098
 
1050
1099
  if ("targets" in raw) {
1051
- if (!Array.isArray(raw.targets)) {
1052
- issues.add("targets", `expected a list, got ${describe(raw.targets)}`);
1053
- } else {
1100
+ if (!Array.isArray(raw.targets)) issues.add("targets", `expected a list, got ${describe(raw.targets)}`);
1101
+ else {
1054
1102
  const seen = new Set<string>();
1055
- const out: ClioSettings["targets"] = [];
1056
- for (let i = 0; i < raw.targets.length; i += 1) {
1057
- const target = validateTarget(issues, `targets[${i}]`, raw.targets[i]);
1103
+ const targets: ClioSettings["targets"] = [];
1104
+ for (let index = 0; index < raw.targets.length; index += 1) {
1105
+ const target = validateTarget(issues, `targets[${index}]`, raw.targets[index]);
1058
1106
  if (!target) continue;
1059
1107
  if (seen.has(target.id)) {
1060
- issues.add(`targets[${i}].id`, `duplicate target id '${target.id}'`);
1108
+ issues.add(`targets[${index}].id`, `duplicate target id '${target.id}'`);
1061
1109
  continue;
1062
1110
  }
1063
1111
  seen.add(target.id);
1064
- out.push(target);
1112
+ targets.push(target);
1065
1113
  }
1066
- settings.targets = out;
1114
+ settings.targets = targets;
1067
1115
  }
1068
1116
  }
1069
1117
 
1070
- if ("runtimePlugins" in raw) {
1071
- const v = expectStringArray(issues, "runtimePlugins", raw.runtimePlugins);
1072
- if (v !== undefined) settings.runtimePlugins = v;
1073
- }
1074
- if ("library" in raw) {
1075
- if (!isPlainObject(raw.library)) {
1076
- issues.add("library", `expected a map, got ${describe(raw.library)}`);
1077
- } else {
1078
- issues.unknownKeys("library", raw.library, ["catalog", "remote", "confirmedRemote", "sync"]);
1079
- for (const key of ["catalog", "remote", "confirmedRemote"] as const) {
1080
- const value = raw.library[key];
1081
- if (value === undefined) continue;
1082
- if (value !== null && typeof value !== "string")
1083
- issues.add(`library.${key}`, `expected a string or null, got ${describe(value)}`);
1084
- else settings.library[key] = value as string | null;
1085
- }
1086
- if (raw.library.sync !== undefined) {
1087
- if (typeof raw.library.sync !== "boolean")
1088
- issues.add("library.sync", `expected a boolean, got ${describe(raw.library.sync)}`);
1089
- else settings.library.sync = raw.library.sync;
1118
+ if ("chat" in raw) {
1119
+ if (!isPlainObject(raw.chat)) issues.add("chat", `expected a map, got ${describe(raw.chat)}`);
1120
+ else {
1121
+ const chat = raw.chat;
1122
+ issues.unknownKeys("chat", chat, [
1123
+ "target",
1124
+ "model",
1125
+ "thinkingLevel",
1126
+ "modelPicker",
1127
+ "maxOutputTokens",
1128
+ "prewarm",
1129
+ "retry",
1130
+ ]);
1131
+ const routeInput: Record<string, unknown> = {};
1132
+ for (const key of ["target", "model", "thinkingLevel"] as const) {
1133
+ if (key in chat) routeInput[key] = chat[key];
1090
1134
  }
1091
- }
1092
- }
1093
-
1094
- if ("orchestrator" in raw) {
1095
- settings.orchestrator = validateWorkerTarget(
1096
- issues,
1097
- "orchestrator",
1098
- raw.orchestrator,
1099
- settings.orchestrator,
1100
- settings.targets,
1101
- );
1102
- }
1103
- if ("background" in raw) {
1104
- settings.background = validateWorkerTarget(
1105
- issues,
1106
- "background",
1107
- raw.background,
1108
- settings.background,
1109
- settings.targets,
1110
- );
1111
- }
1112
- if ("memory" in raw) settings.memory = validateMemorySettings(issues, raw.memory);
1113
- if ("watchdog" in raw) settings.watchdog = validateWatchdogSettings(issues, raw.watchdog);
1114
-
1115
- // Fleet nodes validate before workers so profile node pins can be checked
1116
- // against the configured node ids.
1117
- if ("fleet" in raw) {
1118
- settings.fleet = validateFleet(issues, raw.fleet);
1119
- }
1120
- const fleetNodeIds = new Set(settings.fleet.nodes.map((node) => node.id));
1121
-
1122
- if ("routing" in raw) {
1123
- if (!isPlainObject(raw.routing)) {
1124
- issues.add("routing", `expected a map, got ${describe(raw.routing)}`);
1125
- } else {
1126
- issues.unknownKeys("routing", raw.routing, ["activeRoles", "activePostures", "agentAutomation"]);
1127
- const activeRoles = expectStringArray(issues, "routing.activeRoles", raw.routing.activeRoles);
1128
- if (activeRoles !== undefined) {
1129
- const allowed = new Set<string>(ACTIVE_ROUTING_ROLES);
1130
- const rawRoles = raw.routing.activeRoles as unknown[];
1131
- const stringRoles = rawRoles.filter((role): role is string => typeof role === "string");
1132
- const complete = activeRoles.length === rawRoles.length;
1133
- for (const role of activeRoles) {
1134
- if (!allowed.has(role)) issues.add("routing.activeRoles", `unsupported active role '${role}'`);
1135
- }
1136
- if (new Set(stringRoles).size !== stringRoles.length) {
1137
- issues.add("routing.activeRoles", "expected unique roles");
1138
- }
1139
- if (complete && activeRoles.every((role) => allowed.has(role))) {
1140
- settings.routing.activeRoles = activeRoles as typeof settings.routing.activeRoles;
1135
+ const route = validateRoute(
1136
+ issues,
1137
+ "chat",
1138
+ routeInput,
1139
+ { target: settings.chat.target, model: settings.chat.model, thinkingLevel: settings.chat.thinkingLevel },
1140
+ settings.targets,
1141
+ );
1142
+ settings.chat.target = route.target;
1143
+ settings.chat.model = route.model;
1144
+ settings.chat.thinkingLevel = route.thinkingLevel;
1145
+ if ("modelPicker" in chat) {
1146
+ if (!isPlainObject(chat.modelPicker))
1147
+ issues.add("chat.modelPicker", `expected a map, got ${describe(chat.modelPicker)}`);
1148
+ else {
1149
+ issues.unknownKeys("chat.modelPicker", chat.modelPicker, ["cycleSet", "favorites", "recentLimit"]);
1150
+ if ("cycleSet" in chat.modelPicker) {
1151
+ const parsed = expectStringArray(issues, "chat.modelPicker.cycleSet", chat.modelPicker.cycleSet);
1152
+ if (parsed !== undefined) settings.chat.modelPicker.cycleSet = normalizeCycleSet(parsed);
1153
+ }
1154
+ if ("favorites" in chat.modelPicker) {
1155
+ const parsed = expectStringArray(issues, "chat.modelPicker.favorites", chat.modelPicker.favorites);
1156
+ if (parsed !== undefined) settings.chat.modelPicker.favorites = normalizeModelRefs(parsed, settings.targets);
1157
+ }
1158
+ if ("recentLimit" in chat.modelPicker) {
1159
+ const parsed = expectInteger(issues, "chat.modelPicker.recentLimit", chat.modelPicker.recentLimit, { min: 1 });
1160
+ if (parsed !== undefined) settings.chat.modelPicker.recentLimit = parsed;
1161
+ }
1141
1162
  }
1142
1163
  }
1143
- const activePostures = expectStringArray(issues, "routing.activePostures", raw.routing.activePostures);
1144
- if (activePostures !== undefined) {
1145
- const allowed = new Set<string>(ACTIVE_ROUTING_POSTURES);
1146
- const rawPostures = raw.routing.activePostures as unknown[];
1147
- const stringPostures = rawPostures.filter((posture): posture is string => typeof posture === "string");
1148
- const complete = activePostures.length === rawPostures.length;
1149
- for (const posture of activePostures) {
1150
- if (!allowed.has(posture)) issues.add("routing.activePostures", `unsupported active posture '${posture}'`);
1151
- }
1152
- if (new Set(stringPostures).size !== stringPostures.length) {
1153
- issues.add("routing.activePostures", "expected unique postures");
1154
- }
1155
- if (complete && activePostures.every((posture) => allowed.has(posture))) {
1156
- settings.routing.activePostures = activePostures as typeof settings.routing.activePostures;
1157
- }
1164
+ if ("maxOutputTokens" in chat) {
1165
+ const parsed = expectInteger(issues, "chat.maxOutputTokens", chat.maxOutputTokens, { min: 0 });
1166
+ if (parsed !== undefined) settings.chat.maxOutputTokens = parsed;
1158
1167
  }
1159
- if (!isPlainObject(raw.routing.agentAutomation)) {
1160
- issues.add("routing.agentAutomation", "expected a map");
1161
- } else {
1162
- const automation = raw.routing.agentAutomation;
1163
- issues.unknownKeys("routing.agentAutomation", automation, ["activeAgentRoles"]);
1164
- if (!Array.isArray(automation.activeAgentRoles)) {
1165
- issues.add("routing.agentAutomation.activeAgentRoles", "expected a list");
1166
- } else {
1167
- const pairs: typeof settings.routing.agentAutomation.activeAgentRoles = [];
1168
- const seen = new Set<string>();
1169
- for (const [index, value] of automation.activeAgentRoles.entries()) {
1170
- const path = `routing.agentAutomation.activeAgentRoles[${index}]`;
1171
- if (!isPlainObject(value)) {
1172
- issues.add(path, "expected a map");
1173
- continue;
1174
- }
1175
- issues.unknownKeys(path, value, ["agentId", "executionRole"]);
1176
- const agentId = expectString(issues, `${path}.agentId`, value.agentId);
1177
- const executionRole = expectEnum(
1178
- issues,
1179
- `${path}.executionRole`,
1180
- value.executionRole,
1181
- ACTIVE_AGENT_AUTOMATION_ROLES,
1182
- );
1183
- if (agentId === "auto") {
1184
- issues.add(`${path}.agentId`, "'auto' is reserved for coordinator-owned agent selection");
1185
- }
1186
- if (agentId === undefined || agentId === "auto" || executionRole === undefined) continue;
1187
- const key = `${agentId}\u0000${executionRole}`;
1188
- if (seen.has(key)) {
1189
- issues.add(path, "duplicate agent and execution-role pair");
1190
- continue;
1191
- }
1192
- seen.add(key);
1193
- pairs.push({ agentId, executionRole });
1168
+ if ("prewarm" in chat) {
1169
+ const parsed = expectBoolean(issues, "chat.prewarm", chat.prewarm);
1170
+ if (parsed !== undefined) settings.chat.prewarm = parsed;
1171
+ }
1172
+ if ("retry" in chat) {
1173
+ if (!isPlainObject(chat.retry)) issues.add("chat.retry", `expected a map, got ${describe(chat.retry)}`);
1174
+ else {
1175
+ issues.unknownKeys("chat.retry", chat.retry, [
1176
+ "enabled",
1177
+ "maxRetries",
1178
+ "baseDelayMs",
1179
+ "maxDelayMs",
1180
+ "streamStallMs",
1181
+ ]);
1182
+ if ("enabled" in chat.retry) {
1183
+ const parsed = expectBoolean(issues, "chat.retry.enabled", chat.retry.enabled);
1184
+ if (parsed !== undefined) settings.chat.retry.enabled = parsed;
1194
1185
  }
1195
- if (pairs.length === automation.activeAgentRoles.length) {
1196
- settings.routing.agentAutomation.activeAgentRoles = pairs;
1186
+ for (const key of ["maxRetries", "baseDelayMs", "maxDelayMs", "streamStallMs"] as const) {
1187
+ if (!(key in chat.retry)) continue;
1188
+ const parsed = expectInteger(issues, `chat.retry.${key}`, chat.retry[key], { min: 0 });
1189
+ if (parsed !== undefined) settings.chat.retry[key] = parsed;
1197
1190
  }
1198
1191
  }
1199
1192
  }
1200
1193
  }
1201
1194
  }
1202
1195
 
1203
- if ("workers" in raw) {
1204
- if (!isPlainObject(raw.workers)) {
1205
- issues.add("workers", `expected a map, got ${describe(raw.workers)}`);
1206
- } else {
1207
- issues.unknownKeys("workers", raw.workers, [
1196
+ let rawFleet: Record<string, unknown> | null = null;
1197
+ if ("fleet" in raw) {
1198
+ if (!isPlainObject(raw.fleet)) issues.add("fleet", `expected a map, got ${describe(raw.fleet)}`);
1199
+ else {
1200
+ rawFleet = raw.fleet;
1201
+ issues.unknownKeys("fleet", rawFleet, [
1208
1202
  "default",
1209
1203
  "profiles",
1210
1204
  "rosters",
1211
- "agentBindings",
1212
- "maxRetries",
1213
- "onPermission",
1214
- "escalation",
1215
- "resilienceCooldownMs",
1205
+ "agentProfiles",
1206
+ "adaptiveRouting",
1207
+ "nodes",
1208
+ "permissions",
1209
+ "concurrency",
1210
+ "retry",
1211
+ "limits",
1212
+ "history",
1216
1213
  ]);
1217
- if ("default" in raw.workers) {
1218
- settings.workers.default = validateWorkerTarget(
1219
- issues,
1220
- "workers.default",
1221
- raw.workers.default,
1222
- settings.workers.default,
1223
- settings.targets,
1224
- { nodeIds: fleetNodeIds },
1225
- );
1226
- }
1227
- if ("profiles" in raw.workers) {
1228
- if (!isPlainObject(raw.workers.profiles)) {
1229
- issues.add("workers.profiles", `expected a map, got ${describe(raw.workers.profiles)}`);
1230
- } else {
1231
- const profiles: ClioSettings["workers"]["profiles"] = {};
1232
- for (const [rawName, rawProfile] of Object.entries(raw.workers.profiles)) {
1233
- const name = rawName.trim();
1234
- if (!name) {
1235
- issues.add("workers.profiles", "empty profile name");
1236
- continue;
1237
- }
1238
- const profile = validateWorkerTarget(
1239
- issues,
1240
- `workers.profiles.${name}`,
1241
- rawProfile,
1242
- DEFAULT_SETTINGS.workers.default,
1243
- settings.targets,
1244
- { nodeIds: fleetNodeIds },
1245
- );
1246
- if (!profile.target) continue;
1247
- profiles[name] = profile;
1214
+ }
1215
+ }
1216
+ if (rawFleet && "nodes" in rawFleet) {
1217
+ if (!Array.isArray(rawFleet.nodes)) issues.add("fleet.nodes", `expected a list, got ${describe(rawFleet.nodes)}`);
1218
+ else {
1219
+ const seen = new Set<string>();
1220
+ settings.fleet.nodes = rawFleet.nodes
1221
+ .map((entry, index) => validateFleetNode(issues, `fleet.nodes[${index}]`, entry, seen))
1222
+ .filter((entry): entry is FleetNodeConfig => entry !== null);
1223
+ }
1224
+ }
1225
+ const fleetNodeIds = new Set(settings.fleet.nodes.map((node) => node.id));
1226
+ if (rawFleet) {
1227
+ if ("default" in rawFleet) {
1228
+ settings.fleet.default = validateRoute(
1229
+ issues,
1230
+ "fleet.default",
1231
+ rawFleet.default,
1232
+ settings.fleet.default,
1233
+ settings.targets,
1234
+ {
1235
+ nodeIds: fleetNodeIds,
1236
+ },
1237
+ );
1238
+ }
1239
+ if ("profiles" in rawFleet) {
1240
+ if (!isPlainObject(rawFleet.profiles))
1241
+ issues.add("fleet.profiles", `expected a map, got ${describe(rawFleet.profiles)}`);
1242
+ else {
1243
+ const profiles: ClioSettings["fleet"]["profiles"] = {};
1244
+ for (const [rawName, rawProfile] of Object.entries(rawFleet.profiles)) {
1245
+ const name = rawName.trim();
1246
+ if (!name) {
1247
+ issues.add("fleet.profiles", "empty profile name");
1248
+ continue;
1248
1249
  }
1249
- settings.workers.profiles = profiles;
1250
+ const profile = validateRoute(
1251
+ issues,
1252
+ `fleet.profiles.${name}`,
1253
+ rawProfile,
1254
+ DEFAULT_SETTINGS.fleet.default,
1255
+ settings.targets,
1256
+ {
1257
+ nodeIds: fleetNodeIds,
1258
+ },
1259
+ );
1260
+ if (profile.target) profiles[name] = profile;
1250
1261
  }
1262
+ settings.fleet.profiles = profiles;
1251
1263
  }
1252
- if ("rosters" in raw.workers) {
1253
- if (!isPlainObject(raw.workers.rosters)) {
1254
- issues.add("workers.rosters", `expected a map, got ${describe(raw.workers.rosters)}`);
1255
- } else {
1256
- const rosters: ClioSettings["workers"]["rosters"] = {};
1257
- for (const [rawName, rawRoster] of Object.entries(raw.workers.rosters)) {
1258
- const name = rawName.trim();
1259
- const path = `workers.rosters.${name}`;
1260
- if (!name) {
1261
- issues.add("workers.rosters", "empty roster name");
1262
- continue;
1263
- }
1264
- if (!isPlainObject(rawRoster)) {
1265
- issues.add(path, `expected a map, got ${describe(rawRoster)}`);
1266
- continue;
1267
- }
1268
- issues.unknownKeys(path, rawRoster, ["members"]);
1269
- if (!Array.isArray(rawRoster.members)) {
1270
- issues.add(`${path}.members`, "expected a list");
1271
- continue;
1272
- }
1273
- if (rawRoster.members.length < 2 || rawRoster.members.length > 5) {
1274
- issues.add(`${path}.members`, "expected 2 to 5 members");
1275
- }
1276
- const members: ClioSettings["workers"]["rosters"][string]["members"] = [];
1277
- const labels = new Set<string>();
1278
- for (const [index, rawMember] of rawRoster.members.entries()) {
1279
- const memberPath = `${path}.members[${index}]`;
1280
- if (!isPlainObject(rawMember)) {
1281
- issues.add(memberPath, `expected a map, got ${describe(rawMember)}`);
1282
- continue;
1283
- }
1284
- issues.unknownKeys(memberPath, rawMember, ["label", "target", "model", "thinking", "color"]);
1285
- const label = expectString(issues, `${memberPath}.label`, rawMember.label);
1286
- const target = expectString(issues, `${memberPath}.target`, rawMember.target);
1287
- if (label !== undefined && !COUNCIL_MEMBER_LABEL_PATTERN.test(label)) {
1288
- issues.add(`${memberPath}.label`, "expected [a-z][a-z0-9_-]{0,31}");
1289
- }
1290
- if (label !== undefined && labels.has(label)) issues.add(`${memberPath}.label`, "duplicate label");
1291
- if (label !== undefined) labels.add(label);
1292
- if (target !== undefined && !settings.targets.some((entry) => entry.id === target)) {
1293
- issues.add(`${memberPath}.target`, `unknown target '${target}'`);
1294
- }
1295
- const model =
1296
- rawMember.model === undefined ? undefined : expectString(issues, `${memberPath}.model`, rawMember.model);
1297
- const thinking =
1298
- rawMember.thinking === undefined
1299
- ? undefined
1300
- : expectEnum(issues, `${memberPath}.thinking`, rawMember.thinking, THINKING_LEVELS);
1301
- const color =
1302
- rawMember.color === undefined ? undefined : expectString(issues, `${memberPath}.color`, rawMember.color);
1303
- if (color !== undefined && !THEME_NAMED_COLORS.includes(color as never) && !/^#[0-9a-fA-F]{6}$/u.test(color)) {
1304
- issues.add(`${memberPath}.color`, "expected a theme named color or 6-digit hex color");
1305
- }
1306
- if (label === undefined || target === undefined) continue;
1307
- members.push({
1308
- label,
1309
- target,
1310
- ...(model ? { model } : {}),
1311
- ...(thinking ? { thinking } : {}),
1312
- ...(color ? { color } : {}),
1313
- });
1314
- }
1315
- if (rawRoster.members.length >= 2 && rawRoster.members.length <= 5 && members.length === rawRoster.members.length)
1316
- rosters[name] = { members };
1264
+ }
1265
+ if ("rosters" in rawFleet) {
1266
+ if (!isPlainObject(rawFleet.rosters))
1267
+ issues.add("fleet.rosters", `expected a map, got ${describe(rawFleet.rosters)}`);
1268
+ else {
1269
+ const rosters: ClioSettings["fleet"]["rosters"] = {};
1270
+ for (const [rawName, rawRoster] of Object.entries(rawFleet.rosters)) {
1271
+ const name = rawName.trim();
1272
+ const path = `fleet.rosters.${name}`;
1273
+ if (!name) {
1274
+ issues.add("fleet.rosters", "empty roster name");
1275
+ continue;
1317
1276
  }
1318
- settings.workers.rosters = rosters;
1319
- }
1320
- }
1321
- if ("agentBindings" in raw.workers) {
1322
- if (!isPlainObject(raw.workers.agentBindings)) {
1323
- issues.add("workers.agentBindings", `expected a map, got ${describe(raw.workers.agentBindings)}`);
1324
- } else {
1325
- const agentBindings: ClioSettings["workers"]["agentBindings"] = {};
1326
- for (const [rawAgentId, rawProfileName] of Object.entries(raw.workers.agentBindings)) {
1327
- const agentId = rawAgentId.trim();
1328
- if (!agentId) {
1329
- issues.add("workers.agentBindings", "empty agent id");
1277
+ if (!isPlainObject(rawRoster)) {
1278
+ issues.add(path, `expected a map, got ${describe(rawRoster)}`);
1279
+ continue;
1280
+ }
1281
+ issues.unknownKeys(path, rawRoster, ["members"]);
1282
+ if (!Array.isArray(rawRoster.members)) {
1283
+ issues.add(`${path}.members`, "expected a list");
1284
+ continue;
1285
+ }
1286
+ if (rawRoster.members.length < 2 || rawRoster.members.length > 5)
1287
+ issues.add(`${path}.members`, "expected 2 to 5 members");
1288
+ const members: ClioSettings["fleet"]["rosters"][string]["members"] = [];
1289
+ const labels = new Set<string>();
1290
+ for (const [index, rawMember] of rawRoster.members.entries()) {
1291
+ const memberPath = `${path}.members[${index}]`;
1292
+ if (!isPlainObject(rawMember)) {
1293
+ issues.add(memberPath, `expected a map, got ${describe(rawMember)}`);
1330
1294
  continue;
1331
1295
  }
1332
- if (agentId === "auto") {
1333
- issues.add("workers.agentBindings", "'auto' is reserved for coordinator-owned agent selection");
1334
- continue;
1296
+ issues.unknownKeys(memberPath, rawMember, ["label", "target", "model", "thinkingLevel", "color"]);
1297
+ const label = expectString(issues, `${memberPath}.label`, rawMember.label);
1298
+ const target = expectString(issues, `${memberPath}.target`, rawMember.target);
1299
+ if (label !== undefined && !COUNCIL_MEMBER_LABEL_PATTERN.test(label))
1300
+ issues.add(`${memberPath}.label`, "expected [a-z][a-z0-9_-]{0,31}");
1301
+ if (label !== undefined && labels.has(label)) issues.add(`${memberPath}.label`, "duplicate label");
1302
+ if (label !== undefined) labels.add(label);
1303
+ if (target !== undefined && !settings.targets.some((entry) => entry.id === target))
1304
+ issues.add(`${memberPath}.target`, `unknown target '${target}'`);
1305
+ const model =
1306
+ rawMember.model === undefined ? undefined : expectString(issues, `${memberPath}.model`, rawMember.model);
1307
+ const thinkingLevel =
1308
+ rawMember.thinkingLevel === undefined
1309
+ ? undefined
1310
+ : expectEnum(issues, `${memberPath}.thinkingLevel`, rawMember.thinkingLevel, THINKING_LEVELS);
1311
+ const color =
1312
+ rawMember.color === undefined ? undefined : expectString(issues, `${memberPath}.color`, rawMember.color);
1313
+ if (color !== undefined && !THEME_NAMED_COLORS.includes(color as never) && !/^#[0-9a-fA-F]{6}$/u.test(color)) {
1314
+ issues.add(`${memberPath}.color`, "expected a theme named color or 6-digit hex color");
1335
1315
  }
1336
- const profileName = expectString(issues, `workers.agentBindings.${agentId}`, rawProfileName);
1337
- if (profileName !== undefined) agentBindings[agentId] = profileName;
1316
+ if (label === undefined || target === undefined) continue;
1317
+ members.push({
1318
+ label,
1319
+ target,
1320
+ ...(model ? { model } : {}),
1321
+ ...(thinkingLevel ? { thinkingLevel } : {}),
1322
+ ...(color ? { color } : {}),
1323
+ });
1338
1324
  }
1339
- settings.workers.agentBindings = agentBindings;
1325
+ if (rawRoster.members.length >= 2 && rawRoster.members.length <= 5 && members.length === rawRoster.members.length)
1326
+ rosters[name] = { members };
1340
1327
  }
1328
+ settings.fleet.rosters = rosters;
1341
1329
  }
1342
- if ("maxRetries" in raw.workers) {
1343
- const v = expectInteger(issues, "workers.maxRetries", raw.workers.maxRetries, { min: 0 });
1344
- if (v !== undefined) settings.workers.maxRetries = v;
1345
- }
1346
- if ("onPermission" in raw.workers) {
1347
- const v = expectEnum(issues, "workers.onPermission", raw.workers.onPermission, [
1348
- "deny",
1349
- "fail",
1350
- "escalate",
1351
- ] as const);
1352
- if (v !== undefined) settings.workers.onPermission = v;
1353
- }
1354
- if ("escalation" in raw.workers) {
1355
- if (!isPlainObject(raw.workers.escalation)) {
1356
- issues.add("workers.escalation", `expected a map, got ${describe(raw.workers.escalation)}`);
1357
- } else {
1358
- const base = settings.workers.escalation ?? DEFAULT_SETTINGS.workers.escalation;
1359
- const escalation = { timeoutMs: base?.timeoutMs ?? 120000, fallback: base?.fallback ?? "deny" };
1360
- issues.unknownKeys("workers.escalation", raw.workers.escalation, ["timeoutMs", "fallback"]);
1361
- if ("timeoutMs" in raw.workers.escalation) {
1362
- const v = expectInteger(issues, "workers.escalation.timeoutMs", raw.workers.escalation.timeoutMs, {
1363
- min: 1,
1364
- });
1365
- if (v !== undefined) escalation.timeoutMs = v;
1330
+ }
1331
+ if ("agentProfiles" in rawFleet) {
1332
+ if (!isPlainObject(rawFleet.agentProfiles))
1333
+ issues.add("fleet.agentProfiles", `expected a map, got ${describe(rawFleet.agentProfiles)}`);
1334
+ else {
1335
+ const bindings: ClioSettings["fleet"]["agentProfiles"] = {};
1336
+ for (const [rawAgentId, rawProfileName] of Object.entries(rawFleet.agentProfiles)) {
1337
+ const agentId = rawAgentId.trim();
1338
+ if (!agentId) {
1339
+ issues.add("fleet.agentProfiles", "empty agent id");
1340
+ continue;
1366
1341
  }
1367
- if ("fallback" in raw.workers.escalation) {
1368
- const v = expectEnum(issues, "workers.escalation.fallback", raw.workers.escalation.fallback, [
1369
- "deny",
1370
- "fail",
1371
- ] as const);
1372
- if (v !== undefined) escalation.fallback = v;
1342
+ if (agentId === "auto") {
1343
+ issues.add("fleet.agentProfiles", "'auto' is reserved for coordinator-owned agent selection");
1344
+ continue;
1373
1345
  }
1374
- settings.workers.escalation = escalation;
1346
+ const profile = expectString(issues, `fleet.agentProfiles.${agentId}`, rawProfileName);
1347
+ if (profile !== undefined) bindings[agentId] = profile;
1375
1348
  }
1376
- }
1377
- if ("resilienceCooldownMs" in raw.workers) {
1378
- const v = expectInteger(issues, "workers.resilienceCooldownMs", raw.workers.resilienceCooldownMs, { min: 0 });
1379
- if (v !== undefined) settings.workers.resilienceCooldownMs = v;
1380
- }
1381
- }
1382
- }
1383
-
1384
- if ("scope" in raw) {
1385
- const v = expectStringArray(issues, "scope", raw.scope);
1386
- if (v !== undefined) settings.scope = normalizeScope(v);
1387
- }
1388
-
1389
- if ("modelSelector" in raw) {
1390
- if (!isPlainObject(raw.modelSelector)) {
1391
- issues.add("modelSelector", `expected a map, got ${describe(raw.modelSelector)}`);
1392
- } else {
1393
- issues.unknownKeys("modelSelector", raw.modelSelector, ["favorites", "recentLimit"]);
1394
- if ("favorites" in raw.modelSelector) {
1395
- const v = expectStringArray(issues, "modelSelector.favorites", raw.modelSelector.favorites);
1396
- if (v !== undefined) settings.modelSelector.favorites = normalizeModelRefs(v, settings.targets);
1397
- }
1398
- if ("recentLimit" in raw.modelSelector) {
1399
- const v = expectInteger(issues, "modelSelector.recentLimit", raw.modelSelector.recentLimit, { min: 1 });
1400
- if (v !== undefined) settings.modelSelector.recentLimit = v;
1349
+ settings.fleet.agentProfiles = bindings;
1401
1350
  }
1402
1351
  }
1403
- }
1404
-
1405
- if ("budget" in raw) {
1406
- if (!isPlainObject(raw.budget)) {
1407
- issues.add("budget", `expected a map, got ${describe(raw.budget)}`);
1408
- } else {
1409
- issues.unknownKeys("budget", raw.budget, ["sessionCeilingUsd", "concurrency"]);
1410
- if ("sessionCeilingUsd" in raw.budget) {
1411
- const v = expectNumber(issues, "budget.sessionCeilingUsd", raw.budget.sessionCeilingUsd, { min: 0 });
1412
- if (v !== undefined) settings.budget.sessionCeilingUsd = v;
1413
- }
1414
- if ("concurrency" in raw.budget) {
1415
- if (raw.budget.concurrency === "auto") {
1416
- settings.budget.concurrency = "auto";
1417
- } else {
1418
- const v = expectInteger(issues, "budget.concurrency", raw.budget.concurrency, { min: 1 });
1419
- if (v !== undefined) settings.budget.concurrency = v;
1352
+ if ("adaptiveRouting" in rawFleet) {
1353
+ if (!isPlainObject(rawFleet.adaptiveRouting))
1354
+ issues.add("fleet.adaptiveRouting", `expected a map, got ${describe(rawFleet.adaptiveRouting)}`);
1355
+ else {
1356
+ const adaptive = rawFleet.adaptiveRouting;
1357
+ issues.unknownKeys("fleet.adaptiveRouting", adaptive, ["roles", "postures", "agentRoles"]);
1358
+ for (const [key, allowed] of [
1359
+ ["roles", ACTIVE_ROUTING_ROLES],
1360
+ ["postures", ACTIVE_ROUTING_POSTURES],
1361
+ ] as const) {
1362
+ if (!(key in adaptive)) continue;
1363
+ const parsed = expectStringArray(issues, `fleet.adaptiveRouting.${key}`, adaptive[key]);
1364
+ if (parsed === undefined) continue;
1365
+ const allowedSet = new Set<string>(allowed);
1366
+ for (const value of parsed)
1367
+ if (!allowedSet.has(value))
1368
+ issues.add(`fleet.adaptiveRouting.${key}`, `unsupported ${key === "roles" ? "role" : "posture"} '${value}'`);
1369
+ if (parsed.every((value) => allowedSet.has(value))) settings.fleet.adaptiveRouting[key] = parsed as never;
1370
+ }
1371
+ if ("agentRoles" in adaptive) {
1372
+ if (!Array.isArray(adaptive.agentRoles)) issues.add("fleet.adaptiveRouting.agentRoles", "expected a list");
1373
+ else {
1374
+ const pairs: ClioSettings["fleet"]["adaptiveRouting"]["agentRoles"] = [];
1375
+ const seen = new Set<string>();
1376
+ for (const [index, value] of adaptive.agentRoles.entries()) {
1377
+ const path = `fleet.adaptiveRouting.agentRoles[${index}]`;
1378
+ if (!isPlainObject(value)) {
1379
+ issues.add(path, "expected a map");
1380
+ continue;
1381
+ }
1382
+ issues.unknownKeys(path, value, ["agentId", "executionRole"]);
1383
+ const agentId = expectString(issues, `${path}.agentId`, value.agentId);
1384
+ const executionRole = expectEnum(
1385
+ issues,
1386
+ `${path}.executionRole`,
1387
+ value.executionRole,
1388
+ ACTIVE_AGENT_AUTOMATION_ROLES,
1389
+ );
1390
+ if (agentId === "auto")
1391
+ issues.add(`${path}.agentId`, "'auto' is reserved for coordinator-owned agent selection");
1392
+ if (agentId === undefined || agentId === "auto" || executionRole === undefined) continue;
1393
+ const pairKey = `${agentId}\u0000${executionRole}`;
1394
+ if (seen.has(pairKey)) {
1395
+ issues.add(path, "duplicate agent and execution-role pair");
1396
+ continue;
1397
+ }
1398
+ seen.add(pairKey);
1399
+ pairs.push({ agentId, executionRole });
1400
+ }
1401
+ if (pairs.length === adaptive.agentRoles.length) settings.fleet.adaptiveRouting.agentRoles = pairs;
1402
+ }
1420
1403
  }
1421
1404
  }
1422
1405
  }
1423
- }
1424
-
1425
- if ("defaults" in raw) {
1426
- if (!isPlainObject(raw.defaults)) {
1427
- issues.add("defaults", `expected a map, got ${describe(raw.defaults)}`);
1428
- } else {
1429
- issues.unknownKeys("defaults", raw.defaults, ["maxTokens"]);
1430
- if ("maxTokens" in raw.defaults) {
1431
- const v = expectInteger(issues, "defaults.maxTokens", raw.defaults.maxTokens, { min: 0 });
1432
- if (v !== undefined) settings.defaults.maxTokens = v;
1433
- }
1434
- }
1435
- }
1436
-
1437
- if ("theme" in raw) {
1438
- const v = expectString(issues, "theme", raw.theme);
1439
- if (v !== undefined) settings.theme = v;
1440
- }
1441
-
1442
- if ("terminal" in raw) {
1443
- if (!isPlainObject(raw.terminal)) {
1444
- issues.add("terminal", `expected a map, got ${describe(raw.terminal)}`);
1445
- } else {
1446
- issues.unknownKeys("terminal", raw.terminal, [
1447
- "showTerminalProgress",
1448
- "outputVerbosity",
1449
- "tuiMode",
1450
- "fullscreenScrollbar",
1451
- "smoothStreaming",
1452
- "notify",
1453
- ]);
1454
- if ("showTerminalProgress" in raw.terminal) {
1455
- const v = expectBoolean(issues, "terminal.showTerminalProgress", raw.terminal.showTerminalProgress);
1456
- if (v !== undefined) settings.terminal.showTerminalProgress = v;
1457
- }
1458
- if ("outputVerbosity" in raw.terminal) {
1459
- const v = expectString(issues, "terminal.outputVerbosity", raw.terminal.outputVerbosity);
1460
- if (v === "minimal" || v === "default" || v === "verbose") settings.terminal.outputVerbosity = v;
1461
- else if (v !== undefined) issues.add("terminal.outputVerbosity", "expected minimal, default, or verbose");
1462
- }
1463
- if ("tuiMode" in raw.terminal) {
1464
- const v = expectString(issues, "terminal.tuiMode", raw.terminal.tuiMode);
1465
- if (v === "regular" || v === "fullscreen") settings.terminal.tuiMode = v;
1466
- else if (v !== undefined) issues.add("terminal.tuiMode", "expected regular or fullscreen");
1467
- }
1468
- if ("fullscreenScrollbar" in raw.terminal) {
1469
- const v = expectString(issues, "terminal.fullscreenScrollbar", raw.terminal.fullscreenScrollbar);
1470
- if (v === "hidden" || v === "auto" || v === "always") settings.terminal.fullscreenScrollbar = v;
1471
- else if (v !== undefined) issues.add("terminal.fullscreenScrollbar", "expected hidden, auto, or always");
1472
- }
1473
- if ("smoothStreaming" in raw.terminal) {
1474
- const v = expectString(issues, "terminal.smoothStreaming", raw.terminal.smoothStreaming);
1475
- if (v === "off" || v === "auto" || v === "on") settings.terminal.smoothStreaming = v;
1476
- else if (v !== undefined) issues.add("terminal.smoothStreaming", "expected off, auto, or on");
1477
- }
1478
- if ("notify" in raw.terminal) {
1479
- const v = expectBoolean(issues, "terminal.notify", raw.terminal.notify);
1480
- if (v !== undefined) settings.terminal.notify = v;
1481
- }
1482
- }
1483
- }
1484
-
1485
- if ("skills" in raw) {
1486
- if (!isPlainObject(raw.skills)) {
1487
- issues.add("skills", `expected a map, got ${describe(raw.skills)}`);
1488
- } else {
1489
- issues.unknownKeys("skills", raw.skills, ["trustProjectCompatRoots"]);
1490
- if ("trustProjectCompatRoots" in raw.skills) {
1491
- const v = expectBoolean(issues, "skills.trustProjectCompatRoots", raw.skills.trustProjectCompatRoots);
1492
- if (v !== undefined) settings.skills.trustProjectCompatRoots = v;
1406
+ if ("permissions" in rawFleet) {
1407
+ if (!isPlainObject(rawFleet.permissions))
1408
+ issues.add("fleet.permissions", `expected a map, got ${describe(rawFleet.permissions)}`);
1409
+ else {
1410
+ issues.unknownKeys("fleet.permissions", rawFleet.permissions, ["mode", "escalation"]);
1411
+ if ("mode" in rawFleet.permissions) {
1412
+ const parsed = expectEnum(issues, "fleet.permissions.mode", rawFleet.permissions.mode, [
1413
+ "deny",
1414
+ "fail",
1415
+ "escalate",
1416
+ ] as const);
1417
+ if (parsed !== undefined) settings.fleet.permissions.mode = parsed;
1418
+ }
1419
+ if ("escalation" in rawFleet.permissions) {
1420
+ if (!isPlainObject(rawFleet.permissions.escalation))
1421
+ issues.add("fleet.permissions.escalation", `expected a map, got ${describe(rawFleet.permissions.escalation)}`);
1422
+ else {
1423
+ const escalation = rawFleet.permissions.escalation;
1424
+ issues.unknownKeys("fleet.permissions.escalation", escalation, ["timeoutMs", "fallback"]);
1425
+ if ("timeoutMs" in escalation) {
1426
+ const parsed = expectInteger(issues, "fleet.permissions.escalation.timeoutMs", escalation.timeoutMs, { min: 1 });
1427
+ if (parsed !== undefined) settings.fleet.permissions.escalation.timeoutMs = parsed;
1428
+ }
1429
+ if ("fallback" in escalation) {
1430
+ const parsed = expectEnum(issues, "fleet.permissions.escalation.fallback", escalation.fallback, [
1431
+ "deny",
1432
+ "fail",
1433
+ ] as const);
1434
+ if (parsed !== undefined) settings.fleet.permissions.escalation.fallback = parsed;
1435
+ }
1436
+ }
1437
+ }
1493
1438
  }
1494
1439
  }
1495
- }
1496
-
1497
- if ("attribution" in raw) {
1498
- if (!isPlainObject(raw.attribution)) {
1499
- issues.add("attribution", `expected a map, got ${describe(raw.attribution)}`);
1500
- } else {
1501
- issues.unknownKeys("attribution", raw.attribution, ["gitCommits"]);
1502
- if ("gitCommits" in raw.attribution) {
1503
- const v = expectBoolean(issues, "attribution.gitCommits", raw.attribution.gitCommits);
1504
- if (v !== undefined) settings.attribution.gitCommits = v;
1440
+ if ("concurrency" in rawFleet) {
1441
+ if (rawFleet.concurrency === "auto") settings.fleet.concurrency = "auto";
1442
+ else {
1443
+ const parsed = expectInteger(issues, "fleet.concurrency", rawFleet.concurrency, { min: 1 });
1444
+ if (parsed !== undefined) settings.fleet.concurrency = parsed;
1505
1445
  }
1506
1446
  }
1507
- }
1508
-
1509
- if ("delegation" in raw) {
1510
- settings.delegation = validateDelegation(issues, raw.delegation);
1511
- }
1512
-
1513
- if ("keybindings" in raw) {
1514
- settings.keybindings = validateKeybindings(issues, raw.keybindings);
1515
- }
1516
-
1517
- if ("compaction" in raw) {
1518
- if (!isPlainObject(raw.compaction)) {
1519
- issues.add("compaction", `expected a map, got ${describe(raw.compaction)}`);
1520
- } else {
1521
- issues.unknownKeys("compaction", raw.compaction, ["auto", "threshold", "excludeLastTurns", "model", "systemPrompt"]);
1522
- if ("auto" in raw.compaction) {
1523
- const v = expectBoolean(issues, "compaction.auto", raw.compaction.auto);
1524
- if (v !== undefined) settings.compaction.auto = v;
1525
- }
1526
- if ("threshold" in raw.compaction) {
1527
- const v = expectNumber(issues, "compaction.threshold", raw.compaction.threshold, { min: 0, max: 1 });
1528
- if (v !== undefined) settings.compaction.threshold = v;
1529
- }
1530
- if ("excludeLastTurns" in raw.compaction) {
1531
- const v = expectInteger(issues, "compaction.excludeLastTurns", raw.compaction.excludeLastTurns, { min: 1 });
1532
- if (v !== undefined) settings.compaction.excludeLastTurns = v;
1447
+ for (const [blockName, fields] of [
1448
+ [
1449
+ "retry",
1450
+ [
1451
+ ["maxRetries", 0],
1452
+ ["routeCooldownMs", 0],
1453
+ ],
1454
+ ],
1455
+ [
1456
+ "limits",
1457
+ [
1458
+ ["toolCallsPerRun", 1],
1459
+ ["internalRunTimeoutMs", 1],
1460
+ ],
1461
+ ],
1462
+ ["history", [["maxRuns", 1]]],
1463
+ ] as const) {
1464
+ if (!(blockName in rawFleet)) continue;
1465
+ const block = rawFleet[blockName];
1466
+ if (!isPlainObject(block)) {
1467
+ issues.add(`fleet.${blockName}`, `expected a map, got ${describe(block)}`);
1468
+ continue;
1533
1469
  }
1534
- if ("model" in raw.compaction) {
1535
- const v = expectString(issues, "compaction.model", raw.compaction.model);
1536
- if (v !== undefined) settings.compaction.model = v;
1470
+ const known = fields.map(([key]) => key);
1471
+ if (blockName === "history") known.push("journal" as never);
1472
+ issues.unknownKeys(`fleet.${blockName}`, block, known);
1473
+ for (const [key, min] of fields) {
1474
+ if (!(key in block)) continue;
1475
+ const parsed = expectInteger(issues, `fleet.${blockName}.${key}`, block[key], { min });
1476
+ if (parsed !== undefined) (settings.fleet[blockName] as unknown as Record<string, unknown>)[key] = parsed;
1537
1477
  }
1538
- if ("systemPrompt" in raw.compaction) {
1539
- const v = expectString(issues, "compaction.systemPrompt", raw.compaction.systemPrompt);
1540
- if (v !== undefined) settings.compaction.systemPrompt = v;
1478
+ if (blockName === "history" && "journal" in block) {
1479
+ const parsed = expectBoolean(issues, "fleet.history.journal", block.journal);
1480
+ if (parsed !== undefined) settings.fleet.history.journal = parsed;
1541
1481
  }
1542
1482
  }
1543
1483
  }
1544
1484
 
1545
1485
  if ("context" in raw) {
1546
- if (!isPlainObject(raw.context)) {
1547
- issues.add("context", `expected a map, got ${describe(raw.context)}`);
1548
- } else {
1549
- issues.unknownKeys("context", raw.context, ["workingSet"]);
1550
- if ("workingSet" in raw.context) {
1551
- if (!isPlainObject(raw.context.workingSet)) {
1552
- issues.add("context.workingSet", `expected a map, got ${describe(raw.context.workingSet)}`);
1553
- } else {
1554
- const workingSet = raw.context.workingSet;
1486
+ if (!isPlainObject(raw.context)) issues.add("context", `expected a map, got ${describe(raw.context)}`);
1487
+ else {
1488
+ const context = raw.context;
1489
+ issues.unknownKeys("context", context, ["workingSet", "compaction", "memory"]);
1490
+ if ("workingSet" in context) {
1491
+ if (!isPlainObject(context.workingSet))
1492
+ issues.add("context.workingSet", `expected a map, got ${describe(context.workingSet)}`);
1493
+ else {
1494
+ const workingSet = context.workingSet;
1555
1495
  issues.unknownKeys("context.workingSet", workingSet, [
1556
1496
  "enabled",
1557
1497
  "policy",
@@ -1560,88 +1500,380 @@ export function validateSettings(raw: unknown): SettingsValidationResult {
1560
1500
  "minEvictableTokens",
1561
1501
  ]);
1562
1502
  if ("enabled" in workingSet) {
1563
- const v = expectBoolean(issues, "context.workingSet.enabled", workingSet.enabled);
1564
- if (v !== undefined) settings.context.workingSet.enabled = v;
1503
+ const parsed = expectBoolean(issues, "context.workingSet.enabled", workingSet.enabled);
1504
+ if (parsed !== undefined) settings.context.workingSet.enabled = parsed;
1565
1505
  }
1566
1506
  if ("policy" in workingSet) {
1567
- const v = expectEnum(issues, "context.workingSet.policy", workingSet.policy, [
1507
+ const parsed = expectEnum(issues, "context.workingSet.policy", workingSet.policy, [
1568
1508
  "age-horizon",
1569
1509
  "structural-v1",
1570
1510
  ] as const);
1571
- if (v !== undefined) settings.context.workingSet.policy = v;
1511
+ if (parsed !== undefined) settings.context.workingSet.policy = parsed;
1572
1512
  }
1573
1513
  if ("target" in workingSet) {
1574
- const v = expectNumber(issues, "context.workingSet.target", workingSet.target);
1575
- if (v !== undefined && (v <= 0 || v >= 1)) {
1576
- issues.add("context.workingSet.target", `expected a number > 0 and < 1, got ${v}`);
1577
- } else if (v !== undefined) {
1578
- settings.context.workingSet.target = v;
1514
+ const parsed = expectNumber(issues, "context.workingSet.target", workingSet.target);
1515
+ if (parsed !== undefined && (parsed <= 0 || parsed >= 1))
1516
+ issues.add("context.workingSet.target", `expected a number > 0 and < 1, got ${parsed}`);
1517
+ else if (parsed !== undefined) settings.context.workingSet.target = parsed;
1518
+ }
1519
+ for (const [key, min] of [
1520
+ ["protectLastTurns", 1],
1521
+ ["minEvictableTokens", 0],
1522
+ ] as const) {
1523
+ if (!(key in workingSet)) continue;
1524
+ const parsed = expectInteger(issues, `context.workingSet.${key}`, workingSet[key], { min });
1525
+ if (parsed !== undefined) settings.context.workingSet[key] = parsed;
1526
+ }
1527
+ }
1528
+ }
1529
+ if ("compaction" in context) {
1530
+ if (!isPlainObject(context.compaction))
1531
+ issues.add("context.compaction", `expected a map, got ${describe(context.compaction)}`);
1532
+ else {
1533
+ const compaction = context.compaction;
1534
+ issues.unknownKeys("context.compaction", compaction, [
1535
+ "auto",
1536
+ "threshold",
1537
+ "model",
1538
+ "systemPrompt",
1539
+ "excludeLastTurns",
1540
+ ]);
1541
+ if ("excludeLastTurns" in compaction)
1542
+ issues.add(
1543
+ "context.compaction.excludeLastTurns",
1544
+ "retired without replacement; use context.workingSet.protectLastTurns",
1545
+ );
1546
+ if ("auto" in compaction) {
1547
+ const parsed = expectBoolean(issues, "context.compaction.auto", compaction.auto);
1548
+ if (parsed !== undefined) settings.context.compaction.auto = parsed;
1549
+ }
1550
+ if ("threshold" in compaction) {
1551
+ const parsed = expectNumber(issues, "context.compaction.threshold", compaction.threshold, { min: 0, max: 1 });
1552
+ if (parsed !== undefined) settings.context.compaction.threshold = parsed;
1553
+ }
1554
+ for (const key of ["model", "systemPrompt"] as const) {
1555
+ if (!(key in compaction)) continue;
1556
+ const parsed = expectString(issues, `context.compaction.${key}`, compaction[key]);
1557
+ if (parsed !== undefined) settings.context.compaction[key] = parsed;
1558
+ }
1559
+ }
1560
+ }
1561
+ if ("memory" in context) {
1562
+ if (!isPlainObject(context.memory)) issues.add("context.memory", `expected a map, got ${describe(context.memory)}`);
1563
+ else {
1564
+ const memory = context.memory;
1565
+ issues.unknownKeys("context.memory", memory, [
1566
+ "enabled",
1567
+ "target",
1568
+ "model",
1569
+ "cadenceToolCalls",
1570
+ "trajectorySteps",
1571
+ "maxOutputTokens",
1572
+ "timeoutMs",
1573
+ ]);
1574
+ if ("enabled" in memory) {
1575
+ const parsed = expectBoolean(issues, "context.memory.enabled", memory.enabled);
1576
+ if (parsed !== undefined) settings.context.memory.enabled = parsed;
1577
+ }
1578
+ if ("target" in memory) {
1579
+ if (memory.target === null) settings.context.memory.target = null;
1580
+ else {
1581
+ const parsed = expectString(issues, "context.memory.target", memory.target);
1582
+ if (parsed !== undefined)
1583
+ settings.context.memory.target = settings.targets.some((target) => target.id === parsed) ? parsed : null;
1579
1584
  }
1580
1585
  }
1581
- if ("protectLastTurns" in workingSet) {
1582
- const v = expectInteger(issues, "context.workingSet.protectLastTurns", workingSet.protectLastTurns, {
1583
- min: 1,
1584
- });
1585
- if (v !== undefined) settings.context.workingSet.protectLastTurns = v;
1586
+ if ("model" in memory) {
1587
+ if (memory.model === null) settings.context.memory.model = null;
1588
+ else {
1589
+ const parsed = expectString(issues, "context.memory.model", memory.model);
1590
+ if (parsed !== undefined) settings.context.memory.model = parsed;
1591
+ }
1586
1592
  }
1587
- if ("minEvictableTokens" in workingSet) {
1588
- const v = expectInteger(issues, "context.workingSet.minEvictableTokens", workingSet.minEvictableTokens, {
1589
- min: 0,
1590
- });
1591
- if (v !== undefined) settings.context.workingSet.minEvictableTokens = v;
1593
+ if (!settings.context.memory.target) settings.context.memory.model = null;
1594
+ else if (settings.context.memory.model === null)
1595
+ settings.context.memory.model =
1596
+ settings.targets.find((target) => target.id === settings.context.memory.target)?.defaultModel ?? null;
1597
+ for (const [key, min] of [
1598
+ ["cadenceToolCalls", 2],
1599
+ ["trajectorySteps", 1],
1600
+ ["maxOutputTokens", 1],
1601
+ ["timeoutMs", 1],
1602
+ ] as const) {
1603
+ if (!(key in memory)) continue;
1604
+ const parsed = expectInteger(issues, `context.memory.${key}`, memory[key], { min });
1605
+ if (parsed !== undefined) settings.context.memory[key] = parsed;
1592
1606
  }
1593
1607
  }
1594
1608
  }
1595
1609
  }
1596
1610
  }
1597
1611
 
1598
- if ("prewarm" in raw) {
1599
- if (!isPlainObject(raw.prewarm)) {
1600
- issues.add("prewarm", `expected a map, got ${describe(raw.prewarm)}`);
1601
- } else {
1602
- issues.unknownKeys("prewarm", raw.prewarm, ["enabled"]);
1603
- if ("enabled" in raw.prewarm) {
1604
- const v = expectBoolean(issues, "prewarm.enabled", raw.prewarm.enabled);
1605
- if (v !== undefined) settings.prewarm.enabled = v;
1612
+ if ("safety" in raw) {
1613
+ if (!isPlainObject(raw.safety)) issues.add("safety", `expected a map, got ${describe(raw.safety)}`);
1614
+ else {
1615
+ const safety = raw.safety;
1616
+ issues.unknownKeys("safety", safety, ["autonomy", "limits", "review"]);
1617
+ if ("autonomy" in safety) {
1618
+ const parsed = expectEnum(issues, "safety.autonomy", safety.autonomy, AUTONOMY_LEVELS);
1619
+ if (parsed !== undefined) settings.safety.autonomy = parsed;
1620
+ }
1621
+ if ("limits" in safety) {
1622
+ if (!isPlainObject(safety.limits)) issues.add("safety.limits", `expected a map, got ${describe(safety.limits)}`);
1623
+ else {
1624
+ const limits = safety.limits;
1625
+ issues.unknownKeys("safety.limits", limits, [
1626
+ "sessionCostUsd",
1627
+ "chatToolCallsPerTurn",
1628
+ "readBytesPerCall",
1629
+ "observationBytesPerTurn",
1630
+ ]);
1631
+ if ("sessionCostUsd" in limits) {
1632
+ const parsed = expectNumber(issues, "safety.limits.sessionCostUsd", limits.sessionCostUsd, { min: 0 });
1633
+ if (parsed !== undefined) settings.safety.limits.sessionCostUsd = parsed;
1634
+ }
1635
+ for (const key of ["chatToolCallsPerTurn", "readBytesPerCall", "observationBytesPerTurn"] as const) {
1636
+ if (!(key in limits)) continue;
1637
+ const parsed = expectInteger(issues, `safety.limits.${key}`, limits[key], { min: 1 });
1638
+ if (parsed !== undefined) settings.safety.limits[key] = parsed;
1639
+ }
1640
+ }
1641
+ }
1642
+ if ("review" in safety) {
1643
+ if (!isPlainObject(safety.review)) issues.add("safety.review", `expected a map, got ${describe(safety.review)}`);
1644
+ else {
1645
+ const review = safety.review;
1646
+ issues.unknownKeys("safety.review", review, ["enabled", "target", "cadenceToolCalls"]);
1647
+ if ("enabled" in review) {
1648
+ const parsed = expectBoolean(issues, "safety.review.enabled", review.enabled);
1649
+ if (parsed !== undefined) settings.safety.review.enabled = parsed;
1650
+ }
1651
+ if ("target" in review && review.target !== null) {
1652
+ const parsed = expectString(issues, "safety.review.target", review.target);
1653
+ if (parsed !== undefined && settings.targets.some((target) => target.id === parsed))
1654
+ settings.safety.review.target = parsed;
1655
+ else if (parsed !== undefined) issues.add("safety.review.target", `unknown target '${parsed}'`);
1656
+ }
1657
+ if ("cadenceToolCalls" in review && review.cadenceToolCalls !== null) {
1658
+ const parsed = expectInteger(issues, "safety.review.cadenceToolCalls", review.cadenceToolCalls, { min: 1 });
1659
+ if (parsed !== undefined) settings.safety.review.cadenceToolCalls = parsed;
1660
+ }
1661
+ }
1606
1662
  }
1607
1663
  }
1608
1664
  }
1609
1665
 
1610
- if ("retry" in raw) {
1611
- if (!isPlainObject(raw.retry)) {
1612
- issues.add("retry", `expected a map, got ${describe(raw.retry)}`);
1613
- } else {
1614
- issues.unknownKeys("retry", raw.retry, ["enabled", "maxRetries", "baseDelayMs", "maxDelayMs", "streamStallMs"]);
1615
- if ("enabled" in raw.retry) {
1616
- const v = expectBoolean(issues, "retry.enabled", raw.retry.enabled);
1617
- if (v !== undefined) settings.retry.enabled = v;
1666
+ if ("interface" in raw) {
1667
+ if (!isPlainObject(raw.interface)) issues.add("interface", `expected a map, got ${describe(raw.interface)}`);
1668
+ else {
1669
+ const ui = raw.interface;
1670
+ issues.unknownKeys("interface", ui, [
1671
+ "outputDetail",
1672
+ "smoothStreaming",
1673
+ "mode",
1674
+ "fullscreenScrollbar",
1675
+ "terminalProgress",
1676
+ "desktopNotifications",
1677
+ "panes",
1678
+ "keybindings",
1679
+ ]);
1680
+ for (const key of ["terminalProgress", "desktopNotifications"] as const) {
1681
+ if (!(key in ui)) continue;
1682
+ const parsed = expectBoolean(issues, `interface.${key}`, ui[key]);
1683
+ if (parsed !== undefined) (settings.interface as unknown as Record<string, unknown>)[key] = parsed;
1684
+ }
1685
+ for (const [key, allowed] of [
1686
+ ["outputDetail", ["minimal", "default", "verbose"]],
1687
+ ["smoothStreaming", ["off", "auto", "on"]],
1688
+ ["mode", ["regular", "fullscreen"]],
1689
+ ["fullscreenScrollbar", ["hidden", "auto", "always"]],
1690
+ ] as const) {
1691
+ if (!(key in ui)) continue;
1692
+ const parsed = expectEnum(issues, `interface.${key}`, ui[key], allowed);
1693
+ if (parsed !== undefined) (settings.interface as unknown as Record<string, unknown>)[key] = parsed;
1618
1694
  }
1619
- for (const key of ["maxRetries", "baseDelayMs", "maxDelayMs", "streamStallMs"] as const) {
1620
- if (key in raw.retry) {
1621
- const v = expectInteger(issues, `retry.${key}`, raw.retry[key], { min: 0 });
1622
- if (v !== undefined) settings.retry[key] = v;
1695
+ if ("panes" in ui) {
1696
+ if (!isPlainObject(ui.panes)) issues.add("interface.panes", `expected a map, got ${describe(ui.panes)}`);
1697
+ else {
1698
+ const panes = ui.panes;
1699
+ issues.unknownKeys("interface.panes", panes, ["enabled", "notifications", "layout", "workers", "files"]);
1700
+ if ("enabled" in panes) {
1701
+ const parsed = expectEnum(issues, "interface.panes.enabled", panes.enabled, ["auto", "embedded", "off"] as const);
1702
+ if (parsed !== undefined) settings.interface.panes.enabled = parsed;
1703
+ }
1704
+ if ("notifications" in panes) {
1705
+ const parsed = expectEnum(issues, "interface.panes.notifications", panes.notifications, [
1706
+ "failures",
1707
+ "all",
1708
+ "off",
1709
+ ] as const);
1710
+ if (parsed !== undefined) settings.interface.panes.notifications = parsed;
1711
+ }
1712
+ if ("layout" in panes) {
1713
+ const parsed = expectEnum(issues, "interface.panes.layout", panes.layout, ["off", "workers", "cockpit"] as const);
1714
+ if (parsed !== undefined) settings.interface.panes.layout = parsed;
1715
+ }
1716
+ if ("workers" in panes) {
1717
+ if (!isPlainObject(panes.workers))
1718
+ issues.add("interface.panes.workers", `expected a map, got ${describe(panes.workers)}`);
1719
+ else {
1720
+ const workers = panes.workers;
1721
+ issues.unknownKeys("interface.panes.workers", workers, ["ratio"]);
1722
+ if ("ratio" in workers) {
1723
+ const parsed = expectNumber(issues, "interface.panes.workers.ratio", workers.ratio, {
1724
+ min: 0.05,
1725
+ max: 0.5,
1726
+ });
1727
+ if (parsed !== undefined) settings.interface.panes.workers.ratio = parsed;
1728
+ }
1729
+ }
1730
+ }
1731
+ if ("files" in panes) {
1732
+ if (!isPlainObject(panes.files))
1733
+ issues.add("interface.panes.files", `expected a map, got ${describe(panes.files)}`);
1734
+ else {
1735
+ const files = panes.files;
1736
+ issues.unknownKeys("interface.panes.files", files, ["enabled", "mode", "profile", "followCwd", "ratio"]);
1737
+ for (const key of ["enabled", "followCwd"] as const) {
1738
+ if (!(key in files)) continue;
1739
+ const parsed = expectBoolean(issues, `interface.panes.files.${key}`, files[key]);
1740
+ if (parsed !== undefined) settings.interface.panes.files[key] = parsed;
1741
+ }
1742
+ if ("mode" in files) {
1743
+ const parsed = expectEnum(issues, "interface.panes.files.mode", files.mode, ["companion", "chooser"] as const);
1744
+ if (parsed !== undefined) settings.interface.panes.files.mode = parsed;
1745
+ }
1746
+ if ("profile" in files) {
1747
+ const parsed = expectEnum(issues, "interface.panes.files.profile", files.profile, ["managed", "user"] as const);
1748
+ if (parsed !== undefined) settings.interface.panes.files.profile = parsed;
1749
+ }
1750
+ if ("ratio" in files) {
1751
+ const parsed = expectNumber(issues, "interface.panes.files.ratio", files.ratio, { min: 0.05, max: 0.5 });
1752
+ if (parsed !== undefined) settings.interface.panes.files.ratio = parsed;
1753
+ }
1754
+ }
1755
+ }
1623
1756
  }
1624
1757
  }
1758
+ if ("keybindings" in ui)
1759
+ settings.interface.keybindings = validateKeybindings(issues, "interface.keybindings", ui.keybindings);
1625
1760
  }
1626
1761
  }
1627
1762
 
1628
- if ("guardrails" in raw) {
1629
- if (!isPlainObject(raw.guardrails)) {
1630
- issues.add("guardrails", `expected a map, got ${describe(raw.guardrails)}`);
1631
- } else {
1632
- const keys = [
1633
- "turnToolCallBudget",
1634
- "workerToolCallCap",
1635
- "maxDispatchRuns",
1636
- "readMaxBytes",
1637
- "observationTurnBudgetBytes",
1638
- "internalDispatchTimeoutMs",
1639
- ] as const;
1640
- issues.unknownKeys("guardrails", raw.guardrails, keys);
1641
- for (const key of keys) {
1642
- if (key in raw.guardrails) {
1643
- const v = expectInteger(issues, `guardrails.${key}`, raw.guardrails[key], { min: 1 });
1644
- if (v !== undefined) settings.guardrails[key] = v;
1763
+ if ("integrations" in raw) {
1764
+ if (!isPlainObject(raw.integrations)) issues.add("integrations", `expected a map, got ${describe(raw.integrations)}`);
1765
+ else {
1766
+ const integrations = raw.integrations;
1767
+ issues.unknownKeys("integrations", integrations, [
1768
+ "projectResources",
1769
+ "externalAgents",
1770
+ "runtimePlugins",
1771
+ "library",
1772
+ "git",
1773
+ ]);
1774
+ if ("projectResources" in integrations) {
1775
+ if (!isPlainObject(integrations.projectResources))
1776
+ issues.add("integrations.projectResources", `expected a map, got ${describe(integrations.projectResources)}`);
1777
+ else {
1778
+ issues.unknownKeys("integrations.projectResources", integrations.projectResources, ["trustProjectImports"]);
1779
+ if ("trustProjectImports" in integrations.projectResources) {
1780
+ const parsed = expectBoolean(
1781
+ issues,
1782
+ "integrations.projectResources.trustProjectImports",
1783
+ integrations.projectResources.trustProjectImports,
1784
+ );
1785
+ if (parsed !== undefined) settings.integrations.projectResources.trustProjectImports = parsed;
1786
+ }
1787
+ }
1788
+ }
1789
+ if ("runtimePlugins" in integrations) {
1790
+ const parsed = expectStringArray(issues, "integrations.runtimePlugins", integrations.runtimePlugins);
1791
+ if (parsed !== undefined) settings.integrations.runtimePlugins = parsed;
1792
+ }
1793
+ if ("library" in integrations) {
1794
+ if (!isPlainObject(integrations.library))
1795
+ issues.add("integrations.library", `expected a map, got ${describe(integrations.library)}`);
1796
+ else {
1797
+ const library = integrations.library;
1798
+ issues.unknownKeys("integrations.library", library, ["catalog", "remote", "confirmedRemote", "sync"]);
1799
+ for (const key of ["catalog", "remote", "confirmedRemote"] as const) {
1800
+ if (!(key in library)) continue;
1801
+ const value = library[key];
1802
+ if (value !== null && typeof value !== "string")
1803
+ issues.add(`integrations.library.${key}`, `expected a string or null, got ${describe(value)}`);
1804
+ else settings.integrations.library[key] = value as string | null;
1805
+ }
1806
+ if ("sync" in library) {
1807
+ const parsed = expectBoolean(issues, "integrations.library.sync", library.sync);
1808
+ if (parsed !== undefined) settings.integrations.library.sync = parsed;
1809
+ }
1810
+ }
1811
+ }
1812
+ if ("git" in integrations) {
1813
+ if (!isPlainObject(integrations.git))
1814
+ issues.add("integrations.git", `expected a map, got ${describe(integrations.git)}`);
1815
+ else {
1816
+ issues.unknownKeys("integrations.git", integrations.git, ["commitAttribution"]);
1817
+ if ("commitAttribution" in integrations.git) {
1818
+ const parsed = expectBoolean(issues, "integrations.git.commitAttribution", integrations.git.commitAttribution);
1819
+ if (parsed !== undefined) settings.integrations.git.commitAttribution = parsed;
1820
+ }
1821
+ }
1822
+ }
1823
+ if ("externalAgents" in integrations) {
1824
+ if (!isPlainObject(integrations.externalAgents))
1825
+ issues.add("integrations.externalAgents", `expected a map, got ${describe(integrations.externalAgents)}`);
1826
+ else {
1827
+ const external = integrations.externalAgents;
1828
+ issues.unknownKeys("integrations.externalAgents", external, ["entries", "defaults"]);
1829
+ if ("defaults" in external) {
1830
+ if (!isPlainObject(external.defaults))
1831
+ issues.add("integrations.externalAgents.defaults", `expected a map, got ${describe(external.defaults)}`);
1832
+ else {
1833
+ const defaults = external.defaults;
1834
+ issues.unknownKeys("integrations.externalAgents.defaults", defaults, [
1835
+ "connectTimeoutMs",
1836
+ "turnTimeoutMs",
1837
+ "permissionTimeoutMs",
1838
+ "toolGovernance",
1839
+ ]);
1840
+ for (const key of ["connectTimeoutMs", "turnTimeoutMs", "permissionTimeoutMs"] as const) {
1841
+ if (!(key in defaults)) continue;
1842
+ const parsed = expectInteger(issues, `integrations.externalAgents.defaults.${key}`, defaults[key], {
1843
+ min: 1,
1844
+ max: MAX_TIMER_DELAY_MS,
1845
+ });
1846
+ if (parsed !== undefined) settings.integrations.externalAgents.defaults[key] = parsed;
1847
+ }
1848
+ if ("toolGovernance" in defaults) {
1849
+ const parsed = expectEnum(
1850
+ issues,
1851
+ "integrations.externalAgents.defaults.toolGovernance",
1852
+ normalizeLegacyNamingValue(defaults.toolGovernance, "clio-policy", "clio-coder-policy"),
1853
+ TOOL_GOVERNANCE,
1854
+ );
1855
+ if (parsed !== undefined) settings.integrations.externalAgents.defaults.toolGovernance = parsed;
1856
+ }
1857
+ }
1858
+ }
1859
+ if ("entries" in external) {
1860
+ if (!Array.isArray(external.entries))
1861
+ issues.add("integrations.externalAgents.entries", `expected a list, got ${describe(external.entries)}`);
1862
+ else {
1863
+ const seen = new Set<string>();
1864
+ settings.integrations.externalAgents.entries = external.entries
1865
+ .map((entry, index) =>
1866
+ validateExternalAgent(
1867
+ issues,
1868
+ `integrations.externalAgents.entries[${index}]`,
1869
+ entry,
1870
+ settings.integrations.externalAgents.defaults,
1871
+ seen,
1872
+ ),
1873
+ )
1874
+ .filter((entry): entry is ExternalAgent => entry !== null);
1875
+ }
1876
+ }
1645
1877
  }
1646
1878
  }
1647
1879
  }
@@ -1650,12 +1882,6 @@ export function validateSettings(raw: unknown): SettingsValidationResult {
1650
1882
  return { settings, issues: issues.list };
1651
1883
  }
1652
1884
 
1653
- /**
1654
- * Validate the settings file on disk without throwing. Missing file is valid.
1655
- * Resolves the path without the clioConfigDir mkdir side effect so read-only
1656
- * surfaces (plain `clio-coder doctor`, readSettings on a fresh machine) never
1657
- * create directories.
1658
- */
1659
1885
  export function validateSettingsFile(): SettingsValidationResult {
1660
1886
  const path = join(resolveClioDirs().config, "settings.yaml");
1661
1887
  if (!existsSync(path)) return { settings: cloneValue(DEFAULT_SETTINGS), issues: [] };
@@ -1783,10 +2009,6 @@ export function updateSavedSettingsDocument(
1783
2009
  }, options);
1784
2010
  }
1785
2011
 
1786
- export function settingsLockPath(): string {
1787
- return `${settingsPath()}.lock`;
1788
- }
1789
-
1790
2012
  /**
1791
2013
  * Run `fn` while holding the settings.yaml advisory lock. Factored out of
1792
2014
  * updateSettings so the one other sanctioned settings writer (lifecycle
@@ -1851,14 +2073,14 @@ export function useTargetInSettings(
1851
2073
  : target;
1852
2074
  if (!workerTarget) return null;
1853
2075
  const sharedModel = options.model ?? target.defaultModel ?? null;
1854
- settings.orchestrator.target = target.id;
1855
- settings.orchestrator.model = options.orchestratorModel ?? sharedModel;
1856
- settings.workers.default.target = workerTarget.id;
1857
- settings.workers.default.model =
2076
+ settings.chat.target = target.id;
2077
+ settings.chat.model = options.orchestratorModel ?? sharedModel;
2078
+ settings.fleet.default.target = workerTarget.id;
2079
+ settings.fleet.default.model =
1858
2080
  options.workerModel ?? (workerTarget === target ? sharedModel : (workerTarget.defaultModel ?? null));
1859
2081
  if (options.backgroundModel !== undefined) {
1860
- settings.background.target = target.id;
1861
- settings.background.model = options.backgroundModel;
2082
+ settings.context.memory.target = target.id;
2083
+ settings.context.memory.model = options.backgroundModel;
1862
2084
  }
1863
2085
  return { workerTargetId: workerTarget.id };
1864
2086
  }
@@ -1867,22 +2089,25 @@ export function useTargetInSettings(
1867
2089
  export function removeTargetFromSettings(settings: ClioSettings, id: string): boolean {
1868
2090
  if (!settings.targets.some((entry) => entry.id === id)) return false;
1869
2091
  settings.targets = settings.targets.filter((entry) => entry.id !== id);
1870
- if (settings.orchestrator.target === id) {
1871
- settings.orchestrator.target = null;
1872
- settings.orchestrator.model = null;
2092
+ if (settings.chat.target === id) {
2093
+ settings.chat.target = null;
2094
+ settings.chat.model = null;
1873
2095
  }
1874
- if (settings.background.target === id) {
1875
- settings.background.target = null;
1876
- settings.background.model = null;
2096
+ if (settings.context.memory.target === id) {
2097
+ settings.context.memory.target = null;
2098
+ settings.context.memory.model = null;
1877
2099
  }
1878
- if (settings.workers.default.target === id) {
1879
- settings.workers.default.target = null;
1880
- settings.workers.default.model = null;
2100
+ if (settings.fleet.default.target === id) {
2101
+ settings.fleet.default.target = null;
2102
+ settings.fleet.default.model = null;
1881
2103
  }
1882
- for (const [name, profile] of Object.entries(settings.workers.profiles)) {
1883
- if (profile.target === id) delete settings.workers.profiles[name];
2104
+ for (const [name, profile] of Object.entries(settings.fleet.profiles)) {
2105
+ if (profile.target === id) delete settings.fleet.profiles[name];
1884
2106
  }
1885
- settings.scope = settings.scope.filter((entry) => entry.split("/")[0] !== id);
2107
+ settings.chat.modelPicker.cycleSet = settings.chat.modelPicker.cycleSet.filter((entry) => entry.split("/")[0] !== id);
2108
+ settings.chat.modelPicker.favorites = settings.chat.modelPicker.favorites.filter(
2109
+ (entry) => entry.split("/")[0] !== id,
2110
+ );
1886
2111
  return true;
1887
2112
  }
1888
2113
 
@@ -1895,8 +2120,8 @@ export function setFleetProfileInSettings(
1895
2120
  ): boolean {
1896
2121
  const target = settings.targets.find((entry) => entry.id === targetId);
1897
2122
  if (!target) return false;
1898
- const existing = settings.workers.profiles[name];
1899
- settings.workers.profiles[name] = {
2123
+ const existing = settings.fleet.profiles[name];
2124
+ settings.fleet.profiles[name] = {
1900
2125
  target: target.id,
1901
2126
  model: options.model ?? target.defaultModel ?? null,
1902
2127
  thinkingLevel: options.thinkingLevel ?? existing?.thinkingLevel ?? "off",
@@ -1906,11 +2131,11 @@ export function setFleetProfileInSettings(
1906
2131
 
1907
2132
  /** Remove a fleet profile and its agent bindings; returns how many bindings went with it. */
1908
2133
  export function removeFleetProfileFromSettings(settings: ClioSettings, name: string): number {
1909
- delete settings.workers.profiles[name];
2134
+ delete settings.fleet.profiles[name];
1910
2135
  let removedBindings = 0;
1911
- for (const [agentId, profileName] of Object.entries(settings.workers.agentBindings)) {
2136
+ for (const [agentId, profileName] of Object.entries(settings.fleet.agentProfiles)) {
1912
2137
  if (profileName !== name) continue;
1913
- delete settings.workers.agentBindings[agentId];
2138
+ delete settings.fleet.agentProfiles[agentId];
1914
2139
  removedBindings += 1;
1915
2140
  }
1916
2141
  return removedBindings;
@@ -1918,7 +2143,7 @@ export function removeFleetProfileFromSettings(settings: ClioSettings, name: str
1918
2143
 
1919
2144
  /** Bind a native agent to a profile. False for an ACP delegation agent, which ignores native routing. */
1920
2145
  export function bindAgentProfileInSettings(settings: ClioSettings, agentId: string, profileName: string): boolean {
1921
- if (settings.delegation.agents.some((agent) => agent.id === agentId)) return false;
1922
- settings.workers.agentBindings[agentId] = profileName;
2146
+ if (settings.integrations.externalAgents.entries.some((agent) => agent.id === agentId)) return false;
2147
+ settings.fleet.agentProfiles[agentId] = profileName;
1923
2148
  return true;
1924
2149
  }