@iowarp/clio-coder 0.3.0 → 0.3.2

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 (1187) hide show
  1. package/CHANGELOG.md +123 -2
  2. package/CONTRIBUTING.md +23 -23
  3. package/README.md +295 -627
  4. package/dist/acp-BIYHVZIM.js +128 -0
  5. package/dist/{agents-Q4MYPMUW.js → agents-YT6SSRIT.js} +52 -27
  6. package/dist/assets/codewiki.json +1 -0
  7. package/dist/assets/grammars/notices/@vscode__tree-sitter-wasm/LICENSE +21 -0
  8. package/dist/assets/grammars/notices/@vscode__tree-sitter-wasm/cgmanifest.json +16 -0
  9. package/dist/assets/grammars/notices/tree-sitter-wasms/LICENSE +24 -0
  10. package/dist/assets/grammars/tree-sitter-c-sharp.wasm +0 -0
  11. package/dist/assets/grammars/tree-sitter-c.wasm +0 -0
  12. package/dist/assets/grammars/tree-sitter-cpp.wasm +0 -0
  13. package/dist/assets/grammars/tree-sitter-go.wasm +0 -0
  14. package/dist/assets/grammars/tree-sitter-java.wasm +0 -0
  15. package/dist/assets/grammars/tree-sitter-javascript.wasm +0 -0
  16. package/dist/assets/grammars/tree-sitter-python.wasm +0 -0
  17. package/dist/assets/grammars/tree-sitter-ruby.wasm +0 -0
  18. package/dist/assets/grammars/tree-sitter-rust.wasm +0 -0
  19. package/dist/assets/grammars/tree-sitter-tsx.wasm +0 -0
  20. package/dist/assets/grammars/tree-sitter-typescript.wasm +0 -0
  21. package/dist/assets/grammars/tree-sitter.wasm +0 -0
  22. package/dist/{auth-O6HYIJ6J.js → auth-5TWEIYDN.js} +30 -15
  23. package/dist/chunk-2EHAIA3X.js +481 -0
  24. package/dist/{chunk-ME6DNWIU.js → chunk-2IR2NMPA.js} +12 -4
  25. package/dist/chunk-2SFS6XQE.js +122 -0
  26. package/dist/{chunk-KKKPTZLM.js → chunk-2STGEDFV.js} +8 -1
  27. package/dist/chunk-2VTFPG5O.js +48 -0
  28. package/dist/chunk-3I7MS7N2.js +40 -0
  29. package/dist/chunk-3R73A4XB.js +69 -0
  30. package/dist/{chunk-JPOWPFCU.js → chunk-3ZXDFGR5.js} +52 -23
  31. package/dist/chunk-4BJ5BYCE.js +61 -0
  32. package/dist/{chunk-26BZQOAD.js → chunk-4BPJXDWC.js} +161 -260
  33. package/dist/chunk-4KLWL3UC.js +18 -0
  34. package/dist/{chunk-5LDRNKX2.js → chunk-4VP4KH3K.js} +161 -262
  35. package/dist/chunk-4ZG3XFUR.js +77 -0
  36. package/dist/chunk-5B2AEOW5.js +5407 -0
  37. package/dist/chunk-5TSRNF4G.js +510 -0
  38. package/dist/{chunk-WD6AJM35.js → chunk-5UUP6MWO.js} +48 -69
  39. package/dist/{chunk-X3BR7HWV.js → chunk-5WIGXA4T.js} +7 -1
  40. package/dist/chunk-65DEGPJ6.js +52 -0
  41. package/dist/chunk-6EJMN2Y3.js +17 -0
  42. package/dist/chunk-6EJV5X2W.js +16405 -0
  43. package/dist/chunk-6N5PTWMY.js +136 -0
  44. package/dist/chunk-6XLNIQDB.js +27 -0
  45. package/dist/{chunk-XNISANGE.js → chunk-77VKQEHF.js} +171 -74
  46. package/dist/chunk-7CR24IG7.js +242 -0
  47. package/dist/{chunk-UWIJNAOB.js → chunk-7EYHLWU7.js} +907 -638
  48. package/dist/chunk-7MNJORFF.js +22 -0
  49. package/dist/{chunk-JWHCJDCI.js → chunk-A3CYT5EX.js} +257 -79
  50. package/dist/chunk-AGYYIBLL.js +1069 -0
  51. package/dist/chunk-AO4RKG4M.js +277 -0
  52. package/dist/chunk-APJ265NV.js +7410 -0
  53. package/dist/chunk-ARBGF5F7.js +174 -0
  54. package/dist/{chunk-TFSSEXL6.js → chunk-BMEMKKIT.js} +14 -4
  55. package/dist/{chunk-X3NE4WVW.js → chunk-BNAZZHFG.js} +7 -1
  56. package/dist/chunk-CBCAPZAA.js +229 -0
  57. package/dist/{chunk-QV47YRF4.js → chunk-CGKSTWHD.js} +7 -1
  58. package/dist/chunk-CMZWFGD2.js +352 -0
  59. package/dist/chunk-ECH6PKUQ.js +39 -0
  60. package/dist/{chunk-67MTHP2E.js → chunk-ED4KHGC3.js} +13 -5
  61. package/dist/chunk-EKMEHE4H.js +340 -0
  62. package/dist/{chunk-O4EJEDHO.js → chunk-EPVUXGXG.js} +50 -35
  63. package/dist/chunk-FCSXB6T2.js +338 -0
  64. package/dist/chunk-FJ3H4MN5.js +48 -0
  65. package/dist/chunk-FQ4SKYE4.js +29 -0
  66. package/dist/chunk-G2DE3C7R.js +644 -0
  67. package/dist/chunk-G4BMMOKF.js +182 -0
  68. package/dist/{chunk-7BHLZB3A.js → chunk-GGXXDWE4.js} +190 -1218
  69. package/dist/chunk-HC4CLZ2Y.js +68 -0
  70. package/dist/{chunk-E3NYWENM.js → chunk-HFSBBKSQ.js} +55 -61
  71. package/dist/chunk-HKIYEGME.js +30 -0
  72. package/dist/chunk-I4HZDVNP.js +73 -0
  73. package/dist/chunk-IGLFWIYI.js +161 -0
  74. package/dist/chunk-IHXBNWMM.js +137 -0
  75. package/dist/chunk-IKCO5N3L.js +162 -0
  76. package/dist/chunk-IR4CFBFN.js +56 -0
  77. package/dist/{chunk-DXQNI4PC.js → chunk-IWHMRKLL.js} +8 -1
  78. package/dist/{chunk-VOO7NYPP.js → chunk-J5HN4RYU.js} +27 -9
  79. package/dist/{chunk-4UQA7NCT.js → chunk-J5Q24KAG.js} +7 -2
  80. package/dist/{chunk-KBR4MZZR.js → chunk-JVCV3ICN.js} +7 -1
  81. package/dist/chunk-KJ5LWLOE.js +1077 -0
  82. package/dist/chunk-LBMZMYH2.js +285 -0
  83. package/dist/{chunk-3DANZDGR.js → chunk-LBNRH5WM.js} +100 -161
  84. package/dist/{chunk-TKHQ4DGZ.js → chunk-LZSJBIVT.js} +7358 -7414
  85. package/dist/{chunk-UGYAX5YI.js → chunk-M6SHUN7Q.js} +13 -8
  86. package/dist/chunk-MAW544W2.js +1882 -0
  87. package/dist/chunk-MBS4V7ZP.js +217 -0
  88. package/dist/{chunk-2J63S4SF.js → chunk-MFFY33HR.js} +167 -140
  89. package/dist/chunk-MNA4JGU4.js +255 -0
  90. package/dist/chunk-MQSRRFWA.js +3428 -0
  91. package/dist/{chunk-RQDWMVRB.js → chunk-MVVUPGPW.js} +138 -137
  92. package/dist/chunk-OAO4GE4M.js +619 -0
  93. package/dist/chunk-OHHN2SO4.js +5135 -0
  94. package/dist/chunk-OKGUZO2U.js +34 -0
  95. package/dist/{chunk-BBUH4VAA.js → chunk-OOJYHWRB.js} +60 -361
  96. package/dist/{chunk-D5YD55AR.js → chunk-OQ33BKR3.js} +12 -1
  97. package/dist/chunk-OQE5J4C6.js +73 -0
  98. package/dist/{chunk-AMFR5YA3.js → chunk-ORBHGJC5.js} +26 -12
  99. package/dist/chunk-POHLU5DW.js +1186 -0
  100. package/dist/chunk-QKMUKYO7.js +4961 -0
  101. package/dist/{chunk-7RBKDI66.js → chunk-QTYWRVRA.js} +104 -12
  102. package/dist/{chunk-5N2FG33Q.js → chunk-R346GLFC.js} +7 -1
  103. package/dist/chunk-SRF2PJNW.js +184 -0
  104. package/dist/chunk-SST6Z5JA.js +80 -0
  105. package/dist/{chunk-XV4ZJ6ZM.js → chunk-STBPMHSX.js} +473 -1194
  106. package/dist/{chunk-HBU5MTAM.js → chunk-SVNXH4L2.js} +7 -1
  107. package/dist/chunk-T6YILFSB.js +80 -0
  108. package/dist/chunk-TXOTCRLG.js +15 -0
  109. package/dist/chunk-TZTZS7QK.js +227 -0
  110. package/dist/chunk-UOV2BYIW.js +107 -0
  111. package/dist/{chunk-527KG6XR.js → chunk-V4RXGQ5Q.js} +72 -185
  112. package/dist/chunk-VAKQQHWR.js +434 -0
  113. package/dist/chunk-VG7TBQIY.js +128 -0
  114. package/dist/chunk-VJWL6YS5.js +244 -0
  115. package/dist/chunk-WEH5XRJQ.js +32 -0
  116. package/dist/{chunk-VPAWTYLY.js → chunk-WEPFGWHJ.js} +7 -1
  117. package/dist/chunk-WMSVI4G2.js +2095 -0
  118. package/dist/chunk-WVO7V2QY.js +797 -0
  119. package/dist/chunk-X4RCMKVQ.js +641 -0
  120. package/dist/chunk-X75S7HFS.js +374 -0
  121. package/dist/chunk-XN3L4EYL.js +46 -0
  122. package/dist/{chunk-HMYNFFY4.js → chunk-XPLRXC72.js} +7 -2
  123. package/dist/{chunk-PIDUD6M2.js → chunk-YCWGATWI.js} +12 -5
  124. package/dist/chunk-YHZX5GEU.js +193 -0
  125. package/dist/chunk-YXLYO42X.js +91 -0
  126. package/dist/{chunk-UHTSULZS.js → chunk-ZDOOVTXZ.js} +38 -73
  127. package/dist/chunk-ZI647VB5.js +37 -0
  128. package/dist/{chunk-CLJ5HLUD.js → chunk-ZWLZP4ZT.js} +25 -6
  129. package/dist/cli/index.js +70 -56
  130. package/dist/clio-4LY5K2AC.js +25 -0
  131. package/dist/code-nav-7AX6FYE6.js +600 -0
  132. package/dist/codewiki/build-worker.js +66 -0
  133. package/dist/compile-cache-CVJMMODC.js +18 -0
  134. package/dist/{components-JVHMUBEB.js → components-KELWS457.js} +31 -9
  135. package/dist/{config-ZFCDBMDC.js → config-GTLUW2PR.js} +104 -56
  136. package/dist/configure-R6A64DHX.js +42 -0
  137. package/dist/context-5VKGUVJJ.js +866 -0
  138. package/dist/{context-CDXTP2MP.js → context-JFZEJ7W5.js} +21 -13
  139. package/dist/{context-E3KIFVXI.js → context-RW5HC47S.js} +77 -36
  140. package/dist/{context-clear-3F4PLXOS.js → context-clear-6ZHBAZZT.js} +64 -30
  141. package/dist/{context-index-Q7YSYTR3.js → context-index-BZ4UYMTC.js} +36 -24
  142. package/dist/dispatch-runner-VKBRCWQC.js +1997 -0
  143. package/dist/{docs-YIETIWZI.js → docs-2C2LTVT2.js} +32 -13
  144. package/dist/doctor-KI767GSN.js +87 -0
  145. package/dist/{eval-B3KZZESM.js → eval-XSSNATB4.js} +93 -27
  146. package/dist/{evidence-V67CHM35.js → evidence-UA6AWDQQ.js} +55 -27
  147. package/dist/{evolve-YDZSUQYA.js → evolve-QNTFGV6Z.js} +59 -26
  148. package/dist/{extensions-SRG7XCAH.js → extensions-QVDOHDGJ.js} +23 -8
  149. package/dist/{fleet-CA2CRTVG.js → fleet-Q7UOMUSG.js} +173 -56
  150. package/dist/fleet-preflight-DDN536IT.js +26 -0
  151. package/dist/{init-2OZDJE2D.js → init-WBB65ZHQ.js} +79 -34
  152. package/dist/{memory-3PIQQAKX.js → memory-MD3O64RI.js} +57 -28
  153. package/dist/{models-DY35XI7Y.js → models-BZU34YWD.js} +49 -24
  154. package/dist/monitor-MEQA5C3I.js +661 -0
  155. package/dist/{chunk-PS4PFJQP.js → orchestrator-CGFKEP27.js} +22837 -18564
  156. package/dist/{paths-5OMXW7Z4.js → paths-UXLN5YYZ.js} +17 -6
  157. package/dist/preload-P6DGH2PZ.js +13 -0
  158. package/dist/{reset-PIFYNOS3.js → reset-L2FQEE3E.js} +25 -13
  159. package/dist/{run-3VSPP24F.js → run-IV4Q6RLN.js} +115 -63
  160. package/dist/{share-D36RQCXM.js → share-S5BZQC5I.js} +23 -10
  161. package/dist/{skills-F2MRLELY.js → skills-LQEKRDTN.js} +42 -15
  162. package/dist/{skills-eval-E2ZTW4PL.js → skills-eval-3DC4HEWS.js} +90 -45
  163. package/dist/steer-GGWFUJUD.js +77 -0
  164. package/dist/{targets-DZMEZAH4.js → targets-C4SSGQOB.js} +78 -60
  165. package/dist/terminal-lease-IT5JW2NR.js +395 -0
  166. package/dist/{trace-7NYCUI2J.js → trace-PNCASAXC.js} +48 -17
  167. package/dist/tree-sitter-HGKH6LG4.js +4673 -0
  168. package/dist/{uninstall-AD3JWHBB.js → uninstall-FZCQCDKC.js} +19 -8
  169. package/dist/{upgrade-WYYBKGDY.js → upgrade-7TT7SQ3G.js} +66 -24
  170. package/dist/{usage-ULIDAGFF.js → usage-GV4PKT3M.js} +71 -32
  171. package/dist/verify-G6V4D2G7.js +716 -0
  172. package/dist/version-KGEG7EPO.js +21 -0
  173. package/dist/web-fetch-2YHJ3KTG.js +638 -0
  174. package/dist/{wiki-generate-PKFIX6OB.js → wiki-generate-DQF6Z66B.js} +98 -48
  175. package/dist/worker/entry.js +297 -54
  176. package/dist/workspace-G4ZWUIPR.js +22 -0
  177. package/docs/README.md +29 -21
  178. package/docs/acp.md +191 -16
  179. package/docs/alcf-provider.md +1 -1
  180. package/docs/architecture.md +157 -7
  181. package/docs/artifact-placement.md +101 -0
  182. package/docs/artifact-versions.md +1 -1
  183. package/docs/built-in-agents.md +4 -2
  184. package/docs/capacity-and-scheduling.md +1 -1
  185. package/docs/commands-and-modes.md +224 -97
  186. package/docs/config-knobs-audit.md +1 -3
  187. package/docs/configuration-and-targets.md +236 -38
  188. package/docs/context-engine.md +34 -7
  189. package/docs/development-pipeline.md +102 -0
  190. package/docs/dispatch-architecture-rationale.md +1 -1
  191. package/docs/documentation-coverage.md +4 -4
  192. package/docs/documentation-guide.md +3 -3
  193. package/docs/environment-variables.md +15 -6
  194. package/docs/eval-runner.md +3 -3
  195. package/docs/evals-internal.md +2 -2
  196. package/docs/evidence-and-memory.md +10 -2
  197. package/docs/evolution.md +2 -2
  198. package/docs/exit-codes-and-output.md +15 -9
  199. package/docs/extensions-and-sharing.md +25 -18
  200. package/docs/fleet-demo-runbook.md +22 -23
  201. package/docs/fleet-dispatch.md +117 -16
  202. package/docs/git-commit-provenance.md +120 -0
  203. package/docs/glossary.md +107 -15
  204. package/docs/installation-and-lifecycle.md +75 -17
  205. package/docs/middleware-and-components.md +1 -1
  206. package/docs/model-catalog.md +46 -15
  207. package/docs/observability.md +8 -5
  208. package/docs/performance-methodology.md +491 -0
  209. package/docs/pi-boundary.md +72 -0
  210. package/docs/proactive-memory.md +14 -14
  211. package/docs/prompt-envelope-and-tools.md +25 -4
  212. package/docs/provider-adapter-cookbook.md +57 -4
  213. package/docs/release-cut-checklist.md +129 -111
  214. package/docs/safety-model.md +28 -8
  215. package/docs/scientific-validation.md +3 -3
  216. package/docs/session-lifecycle.md +55 -12
  217. package/docs/skills-marketplace.md +12 -8
  218. package/docs/time-conventions.md +101 -0
  219. package/docs/tool-usage.md +6 -5
  220. package/docs/trace-store.md +1 -1
  221. package/docs/troubleshooting.md +10 -7
  222. package/docs/tui-design.md +121 -28
  223. package/docs/worker-dispatch-mechanics.md +3 -3
  224. package/package.json +33 -36
  225. package/skills/README.md +5 -6
  226. package/skills/coding/ast-grep/SKILL.md +136 -0
  227. package/skills/coding/ast-grep/evals.md +56 -0
  228. package/skills/coding/ast-grep/references/rule_reference.md +297 -0
  229. package/skills/coding/coding-standards/SKILL.md +113 -0
  230. package/skills/coding/coding-standards/evals.md +34 -0
  231. package/skills/coding/prototype/SKILL.md +86 -0
  232. package/skills/coding/prototype/evals.md +42 -0
  233. package/skills/coding/prototype/references/LOGIC.md +67 -0
  234. package/skills/coding/prototype/references/UI.md +112 -0
  235. package/skills/coding/tdd/SKILL.md +101 -0
  236. package/skills/coding/tdd/evals.md +41 -0
  237. package/skills/coding/tdd/references/mocking.md +59 -0
  238. package/skills/coding/tdd/references/tests.md +77 -0
  239. package/skills/context/context-handoff/SKILL.md +126 -0
  240. package/skills/context/context-handoff/evals.md +57 -0
  241. package/skills/context/context-handoff/scripts/new-handoff.sh +26 -0
  242. package/skills/context/context-prime/SKILL.md +95 -0
  243. package/skills/context/context-prime/evals.md +54 -0
  244. package/skills/git/file-ticket/SKILL.md +107 -0
  245. package/skills/git/file-ticket/evals.md +54 -0
  246. package/skills/git/fix-issue/SKILL.md +106 -0
  247. package/skills/git/fix-issue/evals.md +71 -0
  248. package/skills/git/ship/SKILL.md +92 -0
  249. package/skills/git/ship/evals.md +72 -0
  250. package/skills/meta/clio-dev/SKILL.md +91 -0
  251. package/skills/meta/clio-dev/evals.md +45 -0
  252. package/skills/meta/clio-test/SKILL.md +130 -0
  253. package/skills/meta/clio-test/evals.md +43 -0
  254. package/skills/meta/clio-test/references/harness.md +97 -0
  255. package/skills/meta/clio-test/references/test-map.md +59 -0
  256. package/skills/meta/credentials/SKILL.md +125 -0
  257. package/skills/meta/credentials/evals.md +104 -0
  258. package/skills/meta/find-skills/SKILL.md +72 -0
  259. package/skills/meta/find-skills/evals.md +47 -0
  260. package/skills/meta/herdr/SKILL.md +127 -0
  261. package/skills/meta/herdr/evals.md +38 -0
  262. package/skills/meta/skill-craft/SKILL.md +102 -0
  263. package/skills/meta/skill-craft/evals.md +41 -0
  264. package/skills/planning/architecture/SKILL.md +129 -0
  265. package/skills/planning/architecture/evals.md +36 -0
  266. package/skills/planning/backlog/SKILL.md +90 -0
  267. package/skills/planning/backlog/evals.md +43 -0
  268. package/skills/planning/prd/SKILL.md +82 -0
  269. package/skills/planning/prd/evals.md +49 -0
  270. package/skills/planning/product-intent/SKILL.md +112 -0
  271. package/skills/planning/product-intent/evals.md +36 -0
  272. package/skills/planning/tech-spec/SKILL.md +115 -0
  273. package/skills/planning/tech-spec/evals.md +47 -0
  274. package/skills/registry.yaml +136 -0
  275. package/skills/research/arxiv-literature/SKILL.md +104 -0
  276. package/skills/research/arxiv-literature/evals.md +58 -0
  277. package/skills/research/experiment-protocol/SKILL.md +122 -0
  278. package/skills/research/experiment-protocol/evals.md +91 -0
  279. package/skills/research/scientific-debugging/SKILL.md +119 -0
  280. package/skills/research/scientific-debugging/evals.md +138 -0
  281. package/skills/research/scientific-modernization/SKILL.md +138 -0
  282. package/skills/research/scientific-modernization/evals.md +84 -0
  283. package/skills/skill-marketplace.json +12 -20
  284. package/skills/workflow/design-council/SKILL.md +139 -0
  285. package/skills/workflow/design-council/evals.md +97 -0
  286. package/skills/workflow/grill-me/SKILL.md +186 -0
  287. package/skills/workflow/grill-me/evals.md +78 -0
  288. package/skills/workflow/workflow-distiller/SKILL.md +136 -0
  289. package/skills/workflow/workflow-distiller/evals.md +107 -0
  290. package/src/cli/acp.ts +115 -0
  291. package/src/cli/agents.ts +60 -0
  292. package/src/cli/args.ts +280 -0
  293. package/src/cli/argv.ts +170 -0
  294. package/src/cli/ask.ts +34 -0
  295. package/src/cli/auth.ts +468 -0
  296. package/src/cli/bootstrap-generate.ts +498 -0
  297. package/src/cli/clio.ts +96 -0
  298. package/src/cli/components.ts +195 -0
  299. package/src/cli/config-inspect.ts +336 -0
  300. package/src/cli/config.ts +72 -0
  301. package/src/cli/configure-interop.ts +67 -0
  302. package/src/cli/configure-layout.ts +172 -0
  303. package/src/cli/configure.ts +1525 -0
  304. package/src/cli/context-clear.ts +76 -0
  305. package/src/cli/context-index.ts +100 -0
  306. package/src/cli/context.ts +321 -0
  307. package/src/cli/default-target.ts +55 -0
  308. package/src/cli/docs.ts +346 -0
  309. package/src/cli/doctor.ts +54 -0
  310. package/src/cli/eval.ts +315 -0
  311. package/src/cli/evidence.ts +225 -0
  312. package/src/cli/evolve.ts +219 -0
  313. package/src/cli/extensions.ts +214 -0
  314. package/src/cli/fleet.ts +868 -0
  315. package/src/cli/index.ts +338 -0
  316. package/src/cli/init.ts +219 -0
  317. package/src/cli/initial-message.ts +47 -0
  318. package/src/cli/internal-dispatch.ts +55 -0
  319. package/src/cli/memory.ts +182 -0
  320. package/src/cli/models.ts +257 -0
  321. package/src/cli/modes/json-stream.ts +121 -0
  322. package/src/cli/modes/jsonl.ts +9 -0
  323. package/src/cli/modes/print.ts +604 -0
  324. package/src/cli/oauth-manual-input.ts +72 -0
  325. package/src/cli/oauth-select.ts +36 -0
  326. package/src/cli/output-guard.ts +48 -0
  327. package/src/cli/paths.ts +44 -0
  328. package/src/cli/provider-target.ts +113 -0
  329. package/src/cli/removal.ts +72 -0
  330. package/src/cli/reset.ts +253 -0
  331. package/src/cli/run.ts +497 -0
  332. package/src/cli/share.ts +243 -0
  333. package/src/cli/shared.ts +60 -0
  334. package/src/cli/skills-eval.ts +1240 -0
  335. package/src/cli/skills.ts +485 -0
  336. package/src/cli/steer-channel.ts +86 -0
  337. package/src/cli/targets.ts +1154 -0
  338. package/src/cli/text-layout.ts +112 -0
  339. package/src/cli/trace.ts +322 -0
  340. package/src/cli/uninstall.ts +405 -0
  341. package/src/cli/upgrade.ts +286 -0
  342. package/src/cli/usage.ts +876 -0
  343. package/src/cli/validate-model.ts +28 -0
  344. package/src/cli/version.ts +7 -0
  345. package/src/cli/wiki-generate.ts +510 -0
  346. package/src/core/agent-environment.ts +7 -0
  347. package/src/core/artifact-paths.ts +43 -0
  348. package/src/core/bash-exec.ts +313 -0
  349. package/src/core/boot-trace.ts +42 -0
  350. package/src/core/bus-events.ts +744 -0
  351. package/src/core/bus-trace.ts +53 -0
  352. package/src/core/c-header-language.ts +265 -0
  353. package/src/core/clio-repo.ts +78 -0
  354. package/src/core/commit-attribution.ts +157 -0
  355. package/src/core/compile-cache.ts +159 -0
  356. package/src/core/config.ts +1722 -0
  357. package/src/core/context-floor.ts +33 -0
  358. package/src/core/defaults.ts +659 -0
  359. package/src/core/domain-loader.ts +177 -0
  360. package/src/core/event-bus.ts +94 -0
  361. package/src/core/file-references.ts +282 -0
  362. package/src/core/git-commit-attribution.ts +362 -0
  363. package/src/core/guardrails.ts +185 -0
  364. package/src/core/headless-permission.ts +21 -0
  365. package/src/core/incomplete-installation.ts +45 -0
  366. package/src/core/init.ts +223 -0
  367. package/src/core/json-payload.ts +58 -0
  368. package/src/core/package-root.ts +54 -0
  369. package/src/core/path-canonical.ts +37 -0
  370. package/src/core/process-identity.ts +41 -0
  371. package/src/core/recent-models.ts +91 -0
  372. package/src/core/residency-protection.ts +56 -0
  373. package/src/core/resolve-config-value.ts +88 -0
  374. package/src/core/response-schema.ts +279 -0
  375. package/src/core/run-overrides.ts +77 -0
  376. package/src/core/safe-exec.ts +205 -0
  377. package/src/core/safe-id.ts +37 -0
  378. package/src/core/safe-resource-write.ts +162 -0
  379. package/src/core/session-routing.ts +339 -0
  380. package/src/core/settings-layers.ts +354 -0
  381. package/src/core/shared-bus.ts +8 -0
  382. package/src/core/shell-quote.ts +26 -0
  383. package/src/core/skill-activation.ts +190 -0
  384. package/src/core/startup-timer.ts +37 -0
  385. package/src/core/state-file-lock.ts +219 -0
  386. package/src/core/termination.ts +275 -0
  387. package/src/core/timers.ts +38 -0
  388. package/src/core/tool-names.ts +65 -0
  389. package/src/core/verification-scripts.ts +13 -0
  390. package/src/core/workspace-files.ts +492 -0
  391. package/src/core/xdg.ts +177 -0
  392. package/src/domains/agents/builtins/architect.md +4 -4
  393. package/src/domains/agents/builtins/coder.md +3 -2
  394. package/src/domains/agents/builtins/debugger.md +1 -1
  395. package/src/domains/agents/builtins/documenter.md +1 -1
  396. package/src/domains/agents/builtins/git-master.md +3 -3
  397. package/src/domains/agents/builtins/provenance.md +1 -1
  398. package/src/domains/agents/builtins/researcher.md +1 -1
  399. package/src/domains/agents/builtins/scout.md +1 -1
  400. package/src/domains/agents/builtins/tester.md +1 -1
  401. package/src/domains/agents/builtins/verifier.md +1 -1
  402. package/src/domains/agents/builtins/wiki-writer.md +1 -1
  403. package/src/domains/agents/catalog.ts +172 -0
  404. package/src/domains/agents/contract.ts +16 -0
  405. package/src/domains/agents/extension.ts +110 -0
  406. package/src/domains/agents/fleet-commands.ts +154 -0
  407. package/src/domains/agents/fleet-contract.ts +797 -0
  408. package/src/domains/agents/frontmatter.ts +40 -0
  409. package/src/domains/agents/index.ts +102 -0
  410. package/src/domains/agents/manifest.ts +6 -0
  411. package/src/domains/agents/recipe-schema.ts +202 -0
  412. package/src/domains/agents/recipe.ts +125 -0
  413. package/src/domains/agents/registry.ts +111 -0
  414. package/src/domains/agents/result-contract.ts +1265 -0
  415. package/src/domains/agents/spec.ts +371 -0
  416. package/src/domains/agents/write-boundary.ts +109 -0
  417. package/src/domains/components/diff.ts +99 -0
  418. package/src/domains/components/hash.ts +21 -0
  419. package/src/domains/components/index.ts +3 -0
  420. package/src/domains/components/scan.ts +304 -0
  421. package/src/domains/components/snapshot.ts +108 -0
  422. package/src/domains/components/types.ts +82 -0
  423. package/src/domains/config/agent-namespace.ts +13 -0
  424. package/src/domains/config/classify.ts +106 -0
  425. package/src/domains/config/contract.ts +22 -0
  426. package/src/domains/config/extension.ts +142 -0
  427. package/src/domains/config/index.ts +23 -0
  428. package/src/domains/config/keybindings.ts +200 -0
  429. package/src/domains/config/manifest.ts +6 -0
  430. package/src/domains/config/watcher.ts +41 -0
  431. package/src/domains/context/adoption.ts +880 -0
  432. package/src/domains/context/bootstrap-prompt.ts +273 -0
  433. package/src/domains/context/bootstrap.ts +1502 -0
  434. package/src/domains/context/clear.ts +91 -0
  435. package/src/domains/context/clio-md.ts +429 -0
  436. package/src/domains/context/codewiki/artifact.ts +400 -0
  437. package/src/domains/context/codewiki/build-worker-protocol.ts +24 -0
  438. package/src/domains/context/codewiki/build-worker.ts +54 -0
  439. package/src/domains/context/codewiki/cmake.ts +356 -0
  440. package/src/domains/context/codewiki/cooperative.ts +46 -0
  441. package/src/domains/context/codewiki/coordinator.ts +182 -0
  442. package/src/domains/context/codewiki/digest.ts +135 -0
  443. package/src/domains/context/codewiki/grammar-assets.ts +33 -0
  444. package/src/domains/context/codewiki/indexer.ts +1529 -0
  445. package/src/domains/context/codewiki/paths.ts +67 -0
  446. package/src/domains/context/codewiki/schema.ts +80 -0
  447. package/src/domains/context/codewiki/tree-sitter.ts +771 -0
  448. package/src/domains/context/contract.ts +68 -0
  449. package/src/domains/context/excluded-dirs.ts +1 -0
  450. package/src/domains/context/extension.ts +358 -0
  451. package/src/domains/context/fingerprint.ts +198 -0
  452. package/src/domains/context/index.ts +154 -0
  453. package/src/domains/context/init-options.ts +69 -0
  454. package/src/domains/context/manifest.ts +6 -0
  455. package/src/domains/context/operator-profile.ts +157 -0
  456. package/src/domains/context/project-metadata.ts +529 -0
  457. package/src/domains/context/project-rules.ts +227 -0
  458. package/src/domains/context/prompt-context.ts +61 -0
  459. package/src/domains/context/refresh.ts +178 -0
  460. package/src/domains/context/runtime.ts +17 -0
  461. package/src/domains/context/sibling-files.ts +10 -0
  462. package/src/domains/context/state.ts +237 -0
  463. package/src/domains/context/wiki/assemble.ts +303 -0
  464. package/src/domains/context/wiki/frontmatter.ts +210 -0
  465. package/src/domains/context/wiki/generate.ts +595 -0
  466. package/src/domains/context/wiki/layout.ts +111 -0
  467. package/src/domains/context/wiki/meta.ts +221 -0
  468. package/src/domains/context/wiki/plan-store.ts +217 -0
  469. package/src/domains/context/wiki/plan.ts +250 -0
  470. package/src/domains/context/wiki/prompts.ts +283 -0
  471. package/src/domains/context/wiki/staleness.ts +198 -0
  472. package/src/domains/dispatch/active-route-planner.ts +168 -0
  473. package/src/domains/dispatch/admission-queue.ts +150 -0
  474. package/src/domains/dispatch/admission.ts +334 -0
  475. package/src/domains/dispatch/agent-candidates.ts +307 -0
  476. package/src/domains/dispatch/agent-ledger-hub.ts +71 -0
  477. package/src/domains/dispatch/agent-ledger-store.ts +257 -0
  478. package/src/domains/dispatch/agent-ledger.ts +212 -0
  479. package/src/domains/dispatch/agent-plan-adapter.ts +85 -0
  480. package/src/domains/dispatch/assignment-events.ts +132 -0
  481. package/src/domains/dispatch/assignment-reconcile.ts +76 -0
  482. package/src/domains/dispatch/assignment-store.ts +162 -0
  483. package/src/domains/dispatch/assignment.ts +278 -0
  484. package/src/domains/dispatch/backoff.ts +59 -0
  485. package/src/domains/dispatch/batch-store.ts +134 -0
  486. package/src/domains/dispatch/batch-tracker.ts +66 -0
  487. package/src/domains/dispatch/capability-match.ts +155 -0
  488. package/src/domains/dispatch/capacity-lease.ts +413 -0
  489. package/src/domains/dispatch/code-step-store.ts +52 -0
  490. package/src/domains/dispatch/code-step.ts +387 -0
  491. package/src/domains/dispatch/contract.ts +278 -0
  492. package/src/domains/dispatch/dedup.ts +133 -0
  493. package/src/domains/dispatch/event-pump.ts +251 -0
  494. package/src/domains/dispatch/execution-handoff.ts +29 -0
  495. package/src/domains/dispatch/execution-plan.ts +328 -0
  496. package/src/domains/dispatch/execution-role.ts +277 -0
  497. package/src/domains/dispatch/execution-scheduler.ts +583 -0
  498. package/src/domains/dispatch/extension.ts +6006 -0
  499. package/src/domains/dispatch/failure-classification.ts +147 -0
  500. package/src/domains/dispatch/fleet-commit-attribution.ts +56 -0
  501. package/src/domains/dispatch/fleet-plan.ts +225 -0
  502. package/src/domains/dispatch/fleet-preflight.ts +394 -0
  503. package/src/domains/dispatch/gate-decisions.ts +1040 -0
  504. package/src/domains/dispatch/gate-role-prompts.ts +58 -0
  505. package/src/domains/dispatch/heartbeat.ts +27 -0
  506. package/src/domains/dispatch/index.ts +137 -0
  507. package/src/domains/dispatch/joint-route-adapter.ts +114 -0
  508. package/src/domains/dispatch/joint-route-resolver.ts +283 -0
  509. package/src/domains/dispatch/manifest.ts +6 -0
  510. package/src/domains/dispatch/orphan-recovery.ts +266 -0
  511. package/src/domains/dispatch/outcome.ts +109 -0
  512. package/src/domains/dispatch/phase-timing.ts +47 -0
  513. package/src/domains/dispatch/placement.ts +202 -0
  514. package/src/domains/dispatch/receipt-findings.ts +255 -0
  515. package/src/domains/dispatch/receipt-integrity.ts +383 -0
  516. package/src/domains/dispatch/recovery-candidates.ts +74 -0
  517. package/src/domains/dispatch/reproducibility.ts +48 -0
  518. package/src/domains/dispatch/reservation-store.ts +501 -0
  519. package/src/domains/dispatch/route-approval.ts +177 -0
  520. package/src/domains/dispatch/route-candidates.ts +57 -0
  521. package/src/domains/dispatch/route-decision.ts +1079 -0
  522. package/src/domains/dispatch/route-facts.ts +187 -0
  523. package/src/domains/dispatch/route-history.ts +238 -0
  524. package/src/domains/dispatch/route-observer.ts +488 -0
  525. package/src/domains/dispatch/route-policy.ts +327 -0
  526. package/src/domains/dispatch/route-quality.ts +256 -0
  527. package/src/domains/dispatch/route-readiness.ts +120 -0
  528. package/src/domains/dispatch/routing-intent.ts +287 -0
  529. package/src/domains/dispatch/run-identity.ts +56 -0
  530. package/src/domains/dispatch/scout-transition.ts +130 -0
  531. package/src/domains/dispatch/state.ts +302 -0
  532. package/src/domains/dispatch/tool-stats.ts +132 -0
  533. package/src/domains/dispatch/transport.ts +210 -0
  534. package/src/domains/dispatch/types.ts +799 -0
  535. package/src/domains/dispatch/validation-grounding.ts +154 -0
  536. package/src/domains/dispatch/validation.ts +721 -0
  537. package/src/domains/dispatch/worker-protocol.ts +275 -0
  538. package/src/domains/dispatch/worker-spawn.ts +585 -0
  539. package/src/domains/dispatch/write-boundary-enforcer.ts +97 -0
  540. package/src/domains/dispatch/write-boundary.ts +426 -0
  541. package/src/domains/eval/artifacts/redact.ts +42 -0
  542. package/src/domains/eval/artifacts/store.ts +173 -0
  543. package/src/domains/eval/compare/compare.ts +33 -0
  544. package/src/domains/eval/compare/gates.ts +66 -0
  545. package/src/domains/eval/compare/thresholds.ts +92 -0
  546. package/src/domains/eval/harness-metrics.ts +62 -0
  547. package/src/domains/eval/index.ts +66 -0
  548. package/src/domains/eval/metrics/chaos-stream.ts +93 -0
  549. package/src/domains/eval/metrics/context.ts +34 -0
  550. package/src/domains/eval/metrics/coverage.ts +21 -0
  551. package/src/domains/eval/metrics/evidence.ts +182 -0
  552. package/src/domains/eval/metrics/fleet-loop-stream.ts +196 -0
  553. package/src/domains/eval/metrics/invariants.ts +736 -0
  554. package/src/domains/eval/metrics/latency.ts +4 -0
  555. package/src/domains/eval/metrics/token-stream.ts +139 -0
  556. package/src/domains/eval/metrics/tokens.ts +51 -0
  557. package/src/domains/eval/metrics/tool-calls.ts +7 -0
  558. package/src/domains/eval/proactive-memory.ts +279 -0
  559. package/src/domains/eval/provenance.ts +35 -0
  560. package/src/domains/eval/report.ts +234 -0
  561. package/src/domains/eval/reports/json.ts +5 -0
  562. package/src/domains/eval/reports/junit.ts +23 -0
  563. package/src/domains/eval/reports/markdown.ts +20 -0
  564. package/src/domains/eval/reports/swe-jsonl.ts +16 -0
  565. package/src/domains/eval/reports/text.ts +25 -0
  566. package/src/domains/eval/run-compare.ts +274 -0
  567. package/src/domains/eval/runner.ts +244 -0
  568. package/src/domains/eval/runners/clio-run.ts +148 -0
  569. package/src/domains/eval/runners/context-index.ts +56 -0
  570. package/src/domains/eval/runners/context-init.ts +203 -0
  571. package/src/domains/eval/runners/external-command.ts +350 -0
  572. package/src/domains/eval/runtime-paths.ts +171 -0
  573. package/src/domains/eval/schema/artifact.ts +75 -0
  574. package/src/domains/eval/schema/suite.ts +235 -0
  575. package/src/domains/eval/schema/validate.ts +339 -0
  576. package/src/domains/eval/store.ts +392 -0
  577. package/src/domains/eval/suites/load.ts +74 -0
  578. package/src/domains/eval/suites/matrix.ts +19 -0
  579. package/src/domains/eval/suites/resolve.ts +44 -0
  580. package/src/domains/eval/suites/run.ts +325 -0
  581. package/src/domains/eval/task-file.ts +186 -0
  582. package/src/domains/eval/types.ts +140 -0
  583. package/src/domains/eval/verifiers/command.ts +27 -0
  584. package/src/domains/eval/verifiers/file-exists.ts +6 -0
  585. package/src/domains/eval/verifiers/patch.ts +22 -0
  586. package/src/domains/eval/workspaces/git.ts +41 -0
  587. package/src/domains/eval/workspaces/local.ts +17 -0
  588. package/src/domains/eval/workspaces/temp-copy.ts +29 -0
  589. package/src/domains/evidence/build.ts +1754 -0
  590. package/src/domains/evidence/eval.ts +605 -0
  591. package/src/domains/evidence/failure-attribution.ts +54 -0
  592. package/src/domains/evidence/finish-contract-map.ts +42 -0
  593. package/src/domains/evidence/index.ts +55 -0
  594. package/src/domains/evidence/provenance.ts +200 -0
  595. package/src/domains/evidence/redact.ts +81 -0
  596. package/src/domains/evidence/store.ts +291 -0
  597. package/src/domains/evidence/types.ts +325 -0
  598. package/src/domains/evolution/SELF_EDIT_GATE.md +91 -0
  599. package/src/domains/evolution/index.ts +26 -0
  600. package/src/domains/evolution/manifest.ts +154 -0
  601. package/src/domains/evolution/validate.ts +304 -0
  602. package/src/domains/extensions/contract.ts +22 -0
  603. package/src/domains/extensions/discovery.ts +193 -0
  604. package/src/domains/extensions/extension.ts +46 -0
  605. package/src/domains/extensions/index.ts +34 -0
  606. package/src/domains/extensions/manager.ts +30 -0
  607. package/src/domains/extensions/manifest.ts +6 -0
  608. package/src/domains/extensions/resources.ts +38 -0
  609. package/src/domains/extensions/state.ts +224 -0
  610. package/src/domains/extensions/types.ts +83 -0
  611. package/src/domains/interop/consent.ts +185 -0
  612. package/src/domains/interop/contract.ts +29 -0
  613. package/src/domains/interop/detect.ts +173 -0
  614. package/src/domains/interop/extension.ts +59 -0
  615. package/src/domains/interop/index.ts +34 -0
  616. package/src/domains/interop/manifest.ts +6 -0
  617. package/src/domains/interop/registry.ts +139 -0
  618. package/src/domains/interop/state.ts +92 -0
  619. package/src/domains/interop/types.ts +109 -0
  620. package/src/domains/lifecycle/contract.ts +11 -0
  621. package/src/domains/lifecycle/doctor.ts +536 -0
  622. package/src/domains/lifecycle/extension.ts +23 -0
  623. package/src/domains/lifecycle/index.ts +21 -0
  624. package/src/domains/lifecycle/install-method.ts +51 -0
  625. package/src/domains/lifecycle/manifest.ts +6 -0
  626. package/src/domains/lifecycle/migrations/2026-08-18-lmstudio-runtime-id.ts +52 -0
  627. package/src/domains/lifecycle/migrations/index.ts +114 -0
  628. package/src/domains/lifecycle/state.ts +93 -0
  629. package/src/domains/lifecycle/upgrade-notice.ts +35 -0
  630. package/src/domains/lifecycle/version.ts +35 -0
  631. package/src/domains/memory/index.ts +128 -0
  632. package/src/domains/memory/operations.ts +143 -0
  633. package/src/domains/memory/prompt-section.ts +86 -0
  634. package/src/domains/memory/proposal.ts +180 -0
  635. package/src/domains/memory/store.ts +163 -0
  636. package/src/domains/memory/task-bank.ts +229 -0
  637. package/src/domains/memory/task-memory-handoff.ts +222 -0
  638. package/src/domains/memory/task-memory-policy.ts +602 -0
  639. package/src/domains/memory/task-memory-status.ts +58 -0
  640. package/src/domains/memory/task-memory-telemetry.ts +346 -0
  641. package/src/domains/memory/task-memory-trace.ts +89 -0
  642. package/src/domains/memory/types.ts +74 -0
  643. package/src/domains/memory/validate.ts +297 -0
  644. package/src/domains/middleware/budget.ts +216 -0
  645. package/src/domains/middleware/contract.ts +35 -0
  646. package/src/domains/middleware/dispatch-nudge.ts +469 -0
  647. package/src/domains/middleware/extension.ts +129 -0
  648. package/src/domains/middleware/hook-receipts.ts +70 -0
  649. package/src/domains/middleware/hooks-io.ts +186 -0
  650. package/src/domains/middleware/hooks.ts +432 -0
  651. package/src/domains/middleware/index.ts +154 -0
  652. package/src/domains/middleware/manifest.ts +6 -0
  653. package/src/domains/middleware/memory-intervention.ts +701 -0
  654. package/src/domains/middleware/rules.ts +42 -0
  655. package/src/domains/middleware/runtime.ts +333 -0
  656. package/src/domains/middleware/skills-reminder.ts +202 -0
  657. package/src/domains/middleware/snapshot.ts +71 -0
  658. package/src/domains/middleware/stalled-turn.ts +71 -0
  659. package/src/domains/middleware/task-board-reminder.ts +87 -0
  660. package/src/domains/middleware/task-nudge.ts +77 -0
  661. package/src/domains/middleware/tool-choice-control.ts +42 -0
  662. package/src/domains/middleware/types.ts +131 -0
  663. package/src/domains/middleware/validate.ts +329 -0
  664. package/src/domains/observability/accountability.ts +67 -0
  665. package/src/domains/observability/contract.ts +168 -0
  666. package/src/domains/observability/cost.ts +206 -0
  667. package/src/domains/observability/evidence-index.ts +123 -0
  668. package/src/domains/observability/extension.ts +281 -0
  669. package/src/domains/observability/index.ts +78 -0
  670. package/src/domains/observability/manifest.ts +10 -0
  671. package/src/domains/observability/metrics.ts +37 -0
  672. package/src/domains/observability/projection.ts +477 -0
  673. package/src/domains/observability/telemetry.ts +89 -0
  674. package/src/domains/observability/trace-store.ts +1454 -0
  675. package/src/domains/prompts/compiler.ts +614 -0
  676. package/src/domains/prompts/contract.ts +46 -0
  677. package/src/domains/prompts/extension.ts +336 -0
  678. package/src/domains/prompts/fragment-loader.ts +138 -0
  679. package/src/domains/prompts/fragments/identity/clio.md +5 -6
  680. package/src/domains/prompts/fragments/identity/docs-routing.md +10 -0
  681. package/src/domains/prompts/fragments/identity/self-awareness.md +19 -0
  682. package/src/domains/prompts/fragments/operating/contract.md +4 -41
  683. package/src/domains/prompts/fragments/operating/delegation.md +42 -0
  684. package/src/domains/prompts/fragments/operating/skills.md +26 -0
  685. package/src/domains/prompts/fragments/operating/worker.md +16 -0
  686. package/src/domains/prompts/fragments/safety/auto-edit.md +5 -5
  687. package/src/domains/prompts/fragments/safety/full-auto.md +3 -3
  688. package/src/domains/prompts/fragments/safety/read-only.md +4 -4
  689. package/src/domains/prompts/fragments/safety/suggest.md +2 -2
  690. package/src/domains/prompts/fragments/wiki/page.md +10 -0
  691. package/src/domains/prompts/fragments/wiki/plan.md +10 -0
  692. package/src/domains/prompts/hash.ts +73 -0
  693. package/src/domains/prompts/index.ts +32 -0
  694. package/src/domains/prompts/manifest.ts +8 -0
  695. package/src/domains/prompts/memory-intervention.ts +57 -0
  696. package/src/domains/prompts/preload.ts +82 -0
  697. package/src/domains/providers/auth/api-key.ts +47 -0
  698. package/src/domains/providers/auth/backend-file.ts +79 -0
  699. package/src/domains/providers/auth/backend-memory.ts +72 -0
  700. package/src/domains/providers/auth/boot-status.ts +65 -0
  701. package/src/domains/providers/auth/index.ts +37 -0
  702. package/src/domains/providers/auth/oauth.ts +37 -0
  703. package/src/domains/providers/auth/storage.ts +726 -0
  704. package/src/domains/providers/capabilities.ts +58 -0
  705. package/src/domains/providers/catalog.ts +162 -0
  706. package/src/domains/providers/contract.ts +162 -0
  707. package/src/domains/providers/credentials.ts +20 -0
  708. package/src/domains/providers/eligibility.ts +24 -0
  709. package/src/domains/providers/errors.ts +64 -0
  710. package/src/domains/providers/extension.ts +548 -0
  711. package/src/domains/providers/index.ts +142 -0
  712. package/src/domains/providers/knowledge-base-path.ts +102 -0
  713. package/src/domains/providers/manifest.ts +6 -0
  714. package/src/domains/providers/model-capabilities.ts +136 -0
  715. package/src/domains/providers/model-discovery.ts +144 -0
  716. package/src/domains/providers/model-family.ts +32 -0
  717. package/src/domains/providers/model-runtime-capabilities.ts +723 -0
  718. package/src/domains/providers/models/local-models/clio-local-coding-targets.yaml +120 -37
  719. package/src/domains/providers/plugins.ts +61 -0
  720. package/src/domains/providers/probe/fingerprint.ts +64 -0
  721. package/src/domains/providers/probe/http.ts +114 -0
  722. package/src/domains/providers/probe/reasoning.ts +111 -0
  723. package/src/domains/providers/registry.ts +187 -0
  724. package/src/domains/providers/resolver.ts +156 -0
  725. package/src/domains/providers/runtime-resolution.ts +720 -0
  726. package/src/domains/providers/runtimes/antigravity/antigravity-code.ts +90 -0
  727. package/src/domains/providers/runtimes/boot-manifest.ts +55 -0
  728. package/src/domains/providers/runtimes/builtins.ts +78 -0
  729. package/src/domains/providers/runtimes/claude/claude-code.ts +38 -0
  730. package/src/domains/providers/runtimes/claude/claude-sdk.ts +38 -0
  731. package/src/domains/providers/runtimes/claude/common.ts +55 -0
  732. package/src/domains/providers/runtimes/cloud/alcf.ts +171 -0
  733. package/src/domains/providers/runtimes/cloud/anthropic-max.ts +71 -0
  734. package/src/domains/providers/runtimes/cloud/anthropic.ts +43 -0
  735. package/src/domains/providers/runtimes/cloud/bedrock.ts +35 -0
  736. package/src/domains/providers/runtimes/cloud/deepseek.ts +50 -0
  737. package/src/domains/providers/runtimes/cloud/google.ts +41 -0
  738. package/src/domains/providers/runtimes/cloud/groq.ts +46 -0
  739. package/src/domains/providers/runtimes/cloud/mistral.ts +46 -0
  740. package/src/domains/providers/runtimes/cloud/openai-codex.ts +49 -0
  741. package/src/domains/providers/runtimes/cloud/openai.ts +46 -0
  742. package/src/domains/providers/runtimes/cloud/openrouter.ts +111 -0
  743. package/src/domains/providers/runtimes/common/lmstudio-http.ts +423 -0
  744. package/src/domains/providers/runtimes/common/local-synth.ts +164 -0
  745. package/src/domains/providers/runtimes/common/probe-helpers.ts +474 -0
  746. package/src/domains/providers/runtimes/local-native/lemonade-anthropic.ts +27 -0
  747. package/src/domains/providers/runtimes/local-native/lemonade-openai.ts +26 -0
  748. package/src/domains/providers/runtimes/local-native/llamacpp-anthropic.ts +88 -0
  749. package/src/domains/providers/runtimes/local-native/llamacpp-completion.ts +207 -0
  750. package/src/domains/providers/runtimes/local-native/llamacpp-embed.ts +180 -0
  751. package/src/domains/providers/runtimes/local-native/llamacpp-rerank.ts +126 -0
  752. package/src/domains/providers/runtimes/local-native/llamacpp.ts +98 -0
  753. package/src/domains/providers/runtimes/local-native/lmstudio.ts +241 -0
  754. package/src/domains/providers/runtimes/local-native/ollama-native.ts +117 -0
  755. package/src/domains/providers/runtimes/local-native/sglang.ts +26 -0
  756. package/src/domains/providers/runtimes/local-native/vllm.ts +27 -0
  757. package/src/domains/providers/runtimes/protocol/anthropic-compat.ts +104 -0
  758. package/src/domains/providers/runtimes/protocol/anthropic-messages.ts +28 -0
  759. package/src/domains/providers/runtimes/protocol/bedrock.ts +26 -0
  760. package/src/domains/providers/runtimes/protocol/google.ts +27 -0
  761. package/src/domains/providers/runtimes/protocol/openai-compat.ts +145 -0
  762. package/src/domains/providers/support.ts +241 -0
  763. package/src/domains/providers/thinking-control-policy.ts +85 -0
  764. package/src/domains/providers/types/capability-flags.ts +114 -0
  765. package/src/domains/providers/types/cost-provenance.ts +7 -0
  766. package/src/domains/providers/types/inference.ts +41 -0
  767. package/src/domains/providers/types/knowledge-base.ts +156 -0
  768. package/src/domains/providers/types/local-model-quirks.ts +216 -0
  769. package/src/domains/providers/types/runtime-descriptor.ts +181 -0
  770. package/src/domains/providers/types/target-descriptor.ts +60 -0
  771. package/src/domains/resources/collision.ts +92 -0
  772. package/src/domains/resources/common-loader.ts +140 -0
  773. package/src/domains/resources/contract.ts +24 -0
  774. package/src/domains/resources/extension.ts +59 -0
  775. package/src/domains/resources/index.ts +74 -0
  776. package/src/domains/resources/loader.ts +85 -0
  777. package/src/domains/resources/manifest.ts +6 -0
  778. package/src/domains/resources/prompts/loader.ts +246 -0
  779. package/src/domains/resources/prompts/substitute.ts +1 -0
  780. package/src/domains/resources/skills/content-hash.ts +107 -0
  781. package/src/domains/resources/skills/evals.ts +175 -0
  782. package/src/domains/resources/skills/install.ts +427 -0
  783. package/src/domains/resources/skills/loader.ts +985 -0
  784. package/src/domains/resources/skills/marketplace.ts +301 -0
  785. package/src/domains/resources/skills/provenance-pin.ts +115 -0
  786. package/src/domains/safety/action-classifier.ts +270 -0
  787. package/src/domains/safety/approval-axis.ts +23 -0
  788. package/src/domains/safety/audit.ts +493 -0
  789. package/src/domains/safety/autonomy.ts +174 -0
  790. package/src/domains/safety/call-target.ts +59 -0
  791. package/src/domains/safety/contract.ts +65 -0
  792. package/src/domains/safety/damage-control.ts +44 -0
  793. package/src/domains/safety/default-path-policy.ts +117 -0
  794. package/src/domains/safety/extension.ts +383 -0
  795. package/src/domains/safety/finish-contract-registration.ts +128 -0
  796. package/src/domains/safety/finish-contract.ts +554 -0
  797. package/src/domains/safety/index.ts +44 -0
  798. package/src/domains/safety/loop-detector.ts +143 -0
  799. package/src/domains/safety/manifest.ts +6 -0
  800. package/src/domains/safety/path-policy.ts +164 -0
  801. package/src/domains/safety/policy-engine.ts +994 -0
  802. package/src/domains/safety/project-policy.ts +391 -0
  803. package/src/domains/safety/protected-artifacts-registration.ts +189 -0
  804. package/src/domains/safety/protected-artifacts.ts +993 -0
  805. package/src/domains/safety/rejection-feedback.ts +102 -0
  806. package/src/domains/safety/rigor.ts +72 -0
  807. package/src/domains/safety/rule-compiler.ts +48 -0
  808. package/src/domains/safety/rule-pack-loader.ts +92 -0
  809. package/src/domains/safety/run-effects.ts +197 -0
  810. package/src/domains/safety/scope.ts +61 -0
  811. package/src/domains/scheduling/budget.ts +35 -0
  812. package/src/domains/scheduling/cluster.ts +175 -0
  813. package/src/domains/scheduling/contract.ts +30 -0
  814. package/src/domains/scheduling/extension.ts +91 -0
  815. package/src/domains/scheduling/index.ts +12 -0
  816. package/src/domains/scheduling/manifest.ts +6 -0
  817. package/src/domains/session/archive-readers.ts +208 -0
  818. package/src/domains/session/checkpoint.ts +38 -0
  819. package/src/domains/session/compaction/auto.ts +89 -0
  820. package/src/domains/session/compaction/branch-summary.ts +187 -0
  821. package/src/domains/session/compaction/compact.ts +493 -0
  822. package/src/domains/session/compaction/cut-point.ts +172 -0
  823. package/src/domains/session/compaction/defaults.ts +34 -0
  824. package/src/domains/session/compaction/mask-observations.ts +265 -0
  825. package/src/domains/session/compaction/session-entries.ts +18 -0
  826. package/src/domains/session/compaction/tokens.ts +148 -0
  827. package/src/domains/session/context-accounting.ts +583 -0
  828. package/src/domains/session/context-ledger.ts +362 -0
  829. package/src/domains/session/contract.ts +159 -0
  830. package/src/domains/session/cwd-fallback.ts +65 -0
  831. package/src/domains/session/decision-board.ts +190 -0
  832. package/src/domains/session/entries.ts +572 -0
  833. package/src/domains/session/extension.ts +403 -0
  834. package/src/domains/session/history.ts +172 -0
  835. package/src/domains/session/index.ts +84 -0
  836. package/src/domains/session/manager.ts +128 -0
  837. package/src/domains/session/manifest.ts +6 -0
  838. package/src/domains/session/migrations/index.ts +27 -0
  839. package/src/domains/session/prompt-manifest.ts +173 -0
  840. package/src/domains/session/protected-artifact-journal.ts +241 -0
  841. package/src/domains/session/protected-artifacts.ts +90 -0
  842. package/src/domains/session/retry.ts +230 -0
  843. package/src/domains/session/session-artifacts.ts +107 -0
  844. package/src/domains/session/task-board.ts +569 -0
  845. package/src/domains/session/tree/active-path.ts +137 -0
  846. package/src/domains/session/tree/fork.ts +159 -0
  847. package/src/domains/session/tree/manager.ts +128 -0
  848. package/src/domains/session/tree/navigator.ts +177 -0
  849. package/src/domains/session/tree/preview.ts +259 -0
  850. package/src/domains/session/usage.ts +148 -0
  851. package/src/domains/session/workspace/git-probe.ts +167 -0
  852. package/src/domains/session/workspace/index.ts +4 -0
  853. package/src/domains/session/workspace/project-type.ts +217 -0
  854. package/src/domains/session/workspace/snapshot.ts +74 -0
  855. package/src/domains/share/archive.ts +684 -0
  856. package/src/domains/share/contract.ts +9 -0
  857. package/src/domains/share/extension.ts +26 -0
  858. package/src/domains/share/index.ts +28 -0
  859. package/src/domains/share/manifest.ts +6 -0
  860. package/src/domains/user-tasks/store.ts +297 -0
  861. package/src/engine/acp/adapter.ts +397 -0
  862. package/src/engine/acp/errors.ts +128 -0
  863. package/src/engine/acp/event-mapper.ts +154 -0
  864. package/src/engine/acp/server.ts +2238 -0
  865. package/src/engine/acp/tool-mediator.ts +686 -0
  866. package/src/engine/acp/transport.ts +775 -0
  867. package/src/engine/acp/types.ts +196 -0
  868. package/src/engine/agent.ts +29 -0
  869. package/src/engine/ai.ts +207 -0
  870. package/src/engine/alcf-oauth.ts +205 -0
  871. package/src/engine/antigravity/subprocess-runtime.ts +205 -0
  872. package/src/engine/api-registry.ts +223 -0
  873. package/src/engine/apis/degraded-inference.ts +95 -0
  874. package/src/engine/apis/index.ts +16 -0
  875. package/src/engine/apis/llamacpp-residency.ts +292 -0
  876. package/src/engine/apis/lmstudio-residency.ts +76 -0
  877. package/src/engine/apis/lmstudio.ts +243 -0
  878. package/src/engine/apis/ollama-native.ts +528 -0
  879. package/src/engine/apis/openai-completions.ts +795 -0
  880. package/src/engine/apis/output-budget.ts +99 -0
  881. package/src/engine/apis/residency-lock.ts +29 -0
  882. package/src/engine/apis/residency.ts +776 -0
  883. package/src/engine/apis/resident-models.ts +42 -0
  884. package/src/engine/apis/sampling-overrides.ts +38 -0
  885. package/src/engine/apis/types.ts +16 -0
  886. package/src/engine/claude/sdk-runtime.ts +685 -0
  887. package/src/engine/claude/session-id.ts +5 -0
  888. package/src/engine/claude/subprocess-runtime.ts +316 -0
  889. package/src/engine/claude/tool-safety.ts +400 -0
  890. package/src/engine/env-api-keys.ts +98 -0
  891. package/src/engine/external-subprocess.ts +23 -0
  892. package/src/engine/gemma-channel-filter.ts +223 -0
  893. package/src/engine/harmony-response.ts +165 -0
  894. package/src/engine/instrumented-tui.ts +192 -0
  895. package/src/engine/loop-guard.ts +1127 -0
  896. package/src/engine/messages.ts +14 -0
  897. package/src/engine/models.ts +42 -0
  898. package/src/engine/oauth.ts +196 -0
  899. package/src/engine/pi-mono-names.ts +13 -0
  900. package/src/engine/prompt-templates.ts +1 -0
  901. package/src/engine/provider-payload.ts +272 -0
  902. package/src/engine/select-list.ts +53 -0
  903. package/src/engine/session.ts +834 -0
  904. package/src/engine/strip-tokenizer-sentinels.ts +125 -0
  905. package/src/engine/truncate.ts +9 -0
  906. package/src/engine/tui.ts +111 -0
  907. package/src/engine/types.ts +63 -0
  908. package/src/engine/worker-events.ts +100 -0
  909. package/src/engine/worker-runtime-capabilities.ts +5 -0
  910. package/src/engine/worker-runtime.ts +1022 -0
  911. package/src/engine/worker-tools.ts +208 -0
  912. package/src/entry/boot-options.ts +50 -0
  913. package/src/entry/orchestrator.ts +2011 -0
  914. package/src/interactive/application-controller.ts +433 -0
  915. package/src/interactive/bus-notices.ts +265 -0
  916. package/src/interactive/chat-loop-messages.ts +626 -0
  917. package/src/interactive/chat-loop-policy.ts +18 -0
  918. package/src/interactive/chat-loop.ts +1090 -0
  919. package/src/interactive/chat-panel.ts +1820 -0
  920. package/src/interactive/chat-renderer.ts +1228 -0
  921. package/src/interactive/clio-editor.ts +161 -0
  922. package/src/interactive/command-fallbacks.ts +23 -0
  923. package/src/interactive/command-output.ts +85 -0
  924. package/src/interactive/context-activity.ts +203 -0
  925. package/src/interactive/context-meter.ts +223 -0
  926. package/src/interactive/context-overlay.ts +246 -0
  927. package/src/interactive/cost-overlay.ts +279 -0
  928. package/src/interactive/dispatch-board.ts +1319 -0
  929. package/src/interactive/dispatch-steering.ts +86 -0
  930. package/src/interactive/editor-bash.ts +46 -0
  931. package/src/interactive/editor-steer.ts +51 -0
  932. package/src/interactive/editor-submit.ts +507 -0
  933. package/src/interactive/export-html/ansi-to-html.ts +161 -0
  934. package/src/interactive/export-html/index.ts +51 -0
  935. package/src/interactive/export-html/template.ts +45 -0
  936. package/src/interactive/export-html/tool-renderer.ts +54 -0
  937. package/src/interactive/external-editor.ts +67 -0
  938. package/src/interactive/follow-up-queue-panel.ts +75 -0
  939. package/src/interactive/footer/dashboard.ts +538 -0
  940. package/src/interactive/footer/notifications.ts +275 -0
  941. package/src/interactive/footer/widgets.ts +1264 -0
  942. package/src/interactive/footer-panel.ts +187 -0
  943. package/src/interactive/format-time.ts +97 -0
  944. package/src/interactive/index.ts +11 -0
  945. package/src/interactive/interactive-application.ts +939 -0
  946. package/src/interactive/interactive-event-projection.ts +385 -0
  947. package/src/interactive/interactive-input-runtime.ts +307 -0
  948. package/src/interactive/interactive-presentation.ts +584 -0
  949. package/src/interactive/interactive-shell.ts +388 -0
  950. package/src/interactive/interactive-slash-runtime.ts +554 -0
  951. package/src/interactive/interactive-subscriptions.ts +111 -0
  952. package/src/interactive/interactive-tickers.ts +183 -0
  953. package/src/interactive/keybinding-manager.ts +496 -0
  954. package/src/interactive/layout.ts +57 -0
  955. package/src/interactive/leader-key.ts +143 -0
  956. package/src/interactive/loop-guard-interrupt.ts +101 -0
  957. package/src/interactive/memory-overlay.ts +280 -0
  958. package/src/interactive/overlay-ask-user-lifecycle.ts +110 -0
  959. package/src/interactive/overlay-auth-lifecycle.ts +308 -0
  960. package/src/interactive/overlay-frame.ts +502 -0
  961. package/src/interactive/overlay-general-openers.ts +274 -0
  962. package/src/interactive/overlay-key-routing.ts +216 -0
  963. package/src/interactive/overlay-lifecycle.ts +426 -0
  964. package/src/interactive/overlay-model-selectors.ts +127 -0
  965. package/src/interactive/overlay-permission-lifecycle.ts +345 -0
  966. package/src/interactive/overlay-resource-openers.ts +112 -0
  967. package/src/interactive/overlay-session-lifecycle.ts +327 -0
  968. package/src/interactive/overlay-transitions.ts +57 -0
  969. package/src/interactive/overlays/agents.ts +69 -0
  970. package/src/interactive/overlays/ask-user.ts +921 -0
  971. package/src/interactive/overlays/auth-dialog.ts +221 -0
  972. package/src/interactive/overlays/context-reset.ts +108 -0
  973. package/src/interactive/overlays/cwd-fallback.ts +141 -0
  974. package/src/interactive/overlays/decisions.ts +300 -0
  975. package/src/interactive/overlays/extensions.ts +54 -0
  976. package/src/interactive/overlays/help-reference.ts +145 -0
  977. package/src/interactive/overlays/interop.ts +139 -0
  978. package/src/interactive/overlays/keybinding-detail.ts +90 -0
  979. package/src/interactive/overlays/list-overlay.ts +606 -0
  980. package/src/interactive/overlays/message-picker.ts +155 -0
  981. package/src/interactive/overlays/model-selector.ts +1109 -0
  982. package/src/interactive/overlays/prompts.ts +72 -0
  983. package/src/interactive/overlays/session-selector-search.ts +31 -0
  984. package/src/interactive/overlays/session-selector.ts +284 -0
  985. package/src/interactive/overlays/settings.ts +3451 -0
  986. package/src/interactive/overlays/skills-hub.ts +237 -0
  987. package/src/interactive/overlays/tree-selector.ts +422 -0
  988. package/src/interactive/permission-overlay.ts +170 -0
  989. package/src/interactive/render-trace.ts +534 -0
  990. package/src/interactive/renderers/branch-summary.ts +86 -0
  991. package/src/interactive/renderers/code-ink.ts +420 -0
  992. package/src/interactive/renderers/compaction-summary.ts +112 -0
  993. package/src/interactive/renderers/diff.ts +137 -0
  994. package/src/interactive/renderers/mermaid.ts +53 -0
  995. package/src/interactive/renderers/notice.ts +28 -0
  996. package/src/interactive/renderers/retry-status.ts +31 -0
  997. package/src/interactive/renderers/structured.ts +89 -0
  998. package/src/interactive/renderers/tool-execution.ts +1357 -0
  999. package/src/interactive/renderers/worker-entry.ts +280 -0
  1000. package/src/interactive/session-last-turn.ts +186 -0
  1001. package/src/interactive/session-switch-settlement.ts +10 -0
  1002. package/src/interactive/session-transcript.ts +105 -0
  1003. package/src/interactive/session-usage-reseed.ts +74 -0
  1004. package/src/interactive/slash-autocomplete.ts +256 -0
  1005. package/src/interactive/slash-commands.ts +1407 -0
  1006. package/src/interactive/slash-spec.ts +424 -0
  1007. package/src/interactive/status/controller.ts +260 -0
  1008. package/src/interactive/status/index.ts +23 -0
  1009. package/src/interactive/status/state-machine.ts +530 -0
  1010. package/src/interactive/status/summary.ts +170 -0
  1011. package/src/interactive/status/types.ts +121 -0
  1012. package/src/interactive/status/verbs.ts +163 -0
  1013. package/src/interactive/status/watchdog.ts +24 -0
  1014. package/src/interactive/stdout-backpressure.ts +99 -0
  1015. package/src/interactive/stream-pacer.ts +530 -0
  1016. package/src/interactive/stream-pacing-policy.ts +66 -0
  1017. package/src/interactive/tasks-overlay.ts +540 -0
  1018. package/src/interactive/terminal-lease.ts +485 -0
  1019. package/src/interactive/theme/components.ts +55 -0
  1020. package/src/interactive/theme/glyphs.ts +70 -0
  1021. package/src/interactive/theme/index.ts +15 -0
  1022. package/src/interactive/theme/labels.ts +88 -0
  1023. package/src/interactive/theme/rules.ts +138 -0
  1024. package/src/interactive/theme/segments.ts +88 -0
  1025. package/src/interactive/theme/tokens.ts +121 -0
  1026. package/src/interactive/thinking-level-policy.ts +6 -0
  1027. package/src/interactive/tool-prose-loop.ts +122 -0
  1028. package/src/interactive/tool-prose-registration.ts +48 -0
  1029. package/src/interactive/turn-context.ts +855 -0
  1030. package/src/interactive/turn-middleware.ts +283 -0
  1031. package/src/interactive/turn-persistence.ts +458 -0
  1032. package/src/interactive/turn-queues.ts +181 -0
  1033. package/src/interactive/turn-recovery.ts +323 -0
  1034. package/src/interactive/turn-runtime.ts +869 -0
  1035. package/src/interactive/turn-state.ts +133 -0
  1036. package/src/interactive/view/artifacts.ts +1452 -0
  1037. package/src/interactive/view/view-overlay.ts +735 -0
  1038. package/src/interactive/welcome-dashboard.ts +537 -0
  1039. package/src/interactive/worker-receipts.ts +143 -0
  1040. package/src/interactive/worker-replay.ts +111 -0
  1041. package/src/interactive/worker-share.ts +100 -0
  1042. package/src/interactive/worker-stream.ts +446 -0
  1043. package/src/interactive/workspace-facts.ts +141 -0
  1044. package/src/tools/agent-tools.ts +402 -0
  1045. package/src/tools/artifact.ts +103 -0
  1046. package/src/tools/ask-user.ts +666 -0
  1047. package/src/tools/bash.ts +204 -0
  1048. package/src/tools/bootstrap.ts +77 -0
  1049. package/src/tools/builtin-tool-catalog.ts +265 -0
  1050. package/src/tools/codewiki/code-nav-surface.ts +29 -0
  1051. package/src/tools/codewiki/code-nav.ts +549 -0
  1052. package/src/tools/codewiki/shared.ts +118 -0
  1053. package/src/tools/compete-worktrees.ts +898 -0
  1054. package/src/tools/context/docs-engine.ts +630 -0
  1055. package/src/tools/context/index.ts +514 -0
  1056. package/src/tools/context/surface.ts +19 -0
  1057. package/src/tools/core-bootstrap.ts +168 -0
  1058. package/src/tools/credential-present.ts +151 -0
  1059. package/src/tools/dispatch-admission.ts +533 -0
  1060. package/src/tools/dispatch-arguments.ts +228 -0
  1061. package/src/tools/dispatch-background.ts +54 -0
  1062. package/src/tools/dispatch-event-text.ts +6 -0
  1063. package/src/tools/dispatch-plan.ts +746 -0
  1064. package/src/tools/dispatch-run-events.ts +238 -0
  1065. package/src/tools/dispatch-runner.ts +2370 -0
  1066. package/src/tools/dispatch-scout-admission.ts +295 -0
  1067. package/src/tools/dispatch-scout.ts +461 -0
  1068. package/src/tools/dispatch-types.ts +77 -0
  1069. package/src/tools/dispatch.ts +244 -0
  1070. package/src/tools/edit-diff.ts +534 -0
  1071. package/src/tools/edit.ts +128 -0
  1072. package/src/tools/executables.ts +22 -0
  1073. package/src/tools/file-mutation-queue.ts +35 -0
  1074. package/src/tools/find.ts +294 -0
  1075. package/src/tools/grep.ts +495 -0
  1076. package/src/tools/ignore-policy.ts +169 -0
  1077. package/src/tools/lazy-tool.ts +60 -0
  1078. package/src/tools/ledger.ts +206 -0
  1079. package/src/tools/ls.ts +110 -0
  1080. package/src/tools/monitor-surface.ts +36 -0
  1081. package/src/tools/monitor.ts +767 -0
  1082. package/src/tools/network-policy.ts +29 -0
  1083. package/src/tools/observation.ts +421 -0
  1084. package/src/tools/observers.ts +99 -0
  1085. package/src/tools/path-utils.ts +79 -0
  1086. package/src/tools/policy.ts +154 -0
  1087. package/src/tools/profiles.ts +108 -0
  1088. package/src/tools/read.ts +173 -0
  1089. package/src/tools/registry.ts +1169 -0
  1090. package/src/tools/result-shaping.ts +204 -0
  1091. package/src/tools/safe-exec.ts +128 -0
  1092. package/src/tools/spawn-hygiene.ts +182 -0
  1093. package/src/tools/steer-surface.ts +17 -0
  1094. package/src/tools/steer.ts +77 -0
  1095. package/src/tools/tasks.ts +265 -0
  1096. package/src/tools/truncate-utf8.ts +17 -0
  1097. package/src/tools/truncate.ts +43 -0
  1098. package/src/tools/verify/frontend.ts +687 -0
  1099. package/src/tools/verify/index.ts +31 -0
  1100. package/src/tools/verify/scripts.ts +108 -0
  1101. package/src/tools/verify/surface.ts +46 -0
  1102. package/src/tools/web-fetch-surface.ts +23 -0
  1103. package/src/tools/web-fetch.ts +759 -0
  1104. package/src/tools/worker-evidence.ts +215 -0
  1105. package/src/tools/write.ts +57 -0
  1106. package/src/utils/exif-orientation.ts +192 -0
  1107. package/src/utils/git.ts +24 -0
  1108. package/src/utils/image-resize.ts +148 -0
  1109. package/src/utils/photon.ts +103 -0
  1110. package/src/worker/control-lane.ts +22 -0
  1111. package/src/worker/entry.ts +294 -0
  1112. package/src/worker/event-projection.ts +56 -0
  1113. package/src/worker/heartbeat.ts +24 -0
  1114. package/src/worker/ledger-mirror.ts +95 -0
  1115. package/src/worker/ndjson.ts +40 -0
  1116. package/src/worker/protocol.ts +529 -0
  1117. package/src/worker/resource-facts.ts +79 -0
  1118. package/src/worker/runtime-registry.ts +12 -0
  1119. package/src/worker/spec-contract.ts +675 -0
  1120. package/src/worker/stdin-demux.ts +316 -0
  1121. package/dist/acp-UMLFVA3F.js +0 -92
  1122. package/dist/chunk-262G75JS.js +0 -35
  1123. package/dist/chunk-6CWDTGUC.js +0 -20
  1124. package/dist/chunk-BYEU76JP.js +0 -899
  1125. package/dist/chunk-GNGDQYDU.js +0 -34688
  1126. package/dist/chunk-GOTUR54M.js +0 -9
  1127. package/dist/chunk-NI4DEJMC.js +0 -88
  1128. package/dist/chunk-TPOCL34A.js +0 -2876
  1129. package/dist/chunk-UU3R62TT.js +0 -128
  1130. package/dist/clio-KIQ5SNDS.js +0 -53
  1131. package/dist/configure-G4E3A2PG.js +0 -27
  1132. package/dist/doctor-M5HJJZOL.js +0 -61
  1133. package/dist/domains/agents/builtins/architect.md +0 -33
  1134. package/dist/domains/agents/builtins/coder.md +0 -31
  1135. package/dist/domains/agents/builtins/context-bootstrap.md +0 -38
  1136. package/dist/domains/agents/builtins/debugger.md +0 -30
  1137. package/dist/domains/agents/builtins/documenter.md +0 -31
  1138. package/dist/domains/agents/builtins/git-master.md +0 -30
  1139. package/dist/domains/agents/builtins/provenance.md +0 -30
  1140. package/dist/domains/agents/builtins/researcher.md +0 -71
  1141. package/dist/domains/agents/builtins/scout.md +0 -42
  1142. package/dist/domains/agents/builtins/tester.md +0 -31
  1143. package/dist/domains/agents/builtins/verifier.md +0 -30
  1144. package/dist/domains/agents/builtins/wiki-writer.md +0 -41
  1145. package/dist/fleet-preflight-CLIAX7YR.js +0 -21
  1146. package/dist/preload-KZVHET2B.js +0 -11
  1147. package/dist/version-ROZ6CZKH.js +0 -16
  1148. package/docs/html/agents_blueprint.html +0 -936
  1149. package/docs/html/alcf_blueprint.html +0 -324
  1150. package/docs/html/architecture_blueprint.html +0 -850
  1151. package/docs/html/commands_blueprint.html +0 -794
  1152. package/docs/html/config_knobs_audit_blueprint.html +0 -178
  1153. package/docs/html/configuration_blueprint.html +0 -1080
  1154. package/docs/html/context_blueprint.html +0 -603
  1155. package/docs/html/documentation_blueprint.html +0 -832
  1156. package/docs/html/environment_blueprint.html +0 -404
  1157. package/docs/html/eval_blueprint.html +0 -743
  1158. package/docs/html/evals_internal_blueprint.html +0 -190
  1159. package/docs/html/evolution_blueprint.html +0 -674
  1160. package/docs/html/extensions_blueprint.html +0 -2065
  1161. package/docs/html/fleet_dispatch_blueprint.html +0 -286
  1162. package/docs/html/index.html +0 -919
  1163. package/docs/html/lifecycle_blueprint.html +0 -723
  1164. package/docs/html/memory_blueprint.html +0 -699
  1165. package/docs/html/middleware_blueprint.html +0 -664
  1166. package/docs/html/models_blueprint.html +0 -2366
  1167. package/docs/html/observability_blueprint.html +0 -683
  1168. package/docs/html/provider_adapter_blueprint.html +0 -245
  1169. package/docs/html/safety_blueprint.html +0 -1386
  1170. package/docs/html/shared.css +0 -571
  1171. package/docs/html/shared.js +0 -143
  1172. package/docs/html/skills_blueprint.html +0 -671
  1173. package/docs/html/soak_blueprint.html +0 -182
  1174. package/docs/html/tool_usage_blueprint.html +0 -350
  1175. package/docs/html/tools_blueprint.html +0 -2249
  1176. package/docs/html/trace_blueprint.html +0 -235
  1177. package/docs/html/tui_design_blueprint.html +0 -314
  1178. package/docs/html/validation_blueprint.html +0 -961
  1179. package/docs/html/worker_dispatch_blueprint.html +0 -231
  1180. package/skills/git/commit-crafting/SKILL.md +0 -79
  1181. package/skills/git/commit-crafting/evals.md +0 -92
  1182. package/skills/git/create-pr/SKILL.md +0 -116
  1183. package/skills/git/create-pr/evals.md +0 -114
  1184. package/skills/git/investigate-issue/SKILL.md +0 -139
  1185. package/skills/git/investigate-issue/evals.md +0 -94
  1186. package/skills/git/review-changes/SKILL.md +0 -103
  1187. package/skills/git/review-changes/evals.md +0 -85
@@ -0,0 +1,1722 @@
1
+ /**
2
+ * Settings read/validate/write. The config domain wraps this module with
3
+ * watcher, hot-reload, and event emission. Kept in core/ because multiple
4
+ * domains (providers, modes, prompts) need settings access before the domain
5
+ * loader has finished booting.
6
+ *
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
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.
13
+ */
14
+
15
+ import { existsSync, readFileSync } from "node:fs";
16
+ import { join } from "node:path";
17
+ import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
18
+ import {
19
+ ACTIVE_AGENT_AUTOMATION_ROLES,
20
+ ACTIVE_ROUTING_POSTURES,
21
+ ACTIVE_ROUTING_ROLES,
22
+ DEFAULT_SETTINGS,
23
+ THINKING_LEVELS,
24
+ type ThinkingLevel,
25
+ } from "./defaults.js";
26
+ import { safeResourceWrite } from "./safe-resource-write.js";
27
+ import { withStateFileLockSync } from "./state-file-lock.js";
28
+ import { MAX_TIMER_DELAY_MS } from "./timers.js";
29
+ import { clioConfigDir, resolveClioDirs } from "./xdg.js";
30
+
31
+ export type ClioSettings = typeof DEFAULT_SETTINGS;
32
+
33
+ export function settingsPath(): string {
34
+ return join(clioConfigDir(), "settings.yaml");
35
+ }
36
+
37
+ /**
38
+ * What kind of failure produced a {@link SettingsIssue}. `"schema"` (the
39
+ * default when the field is absent) means a key violates the one schema, so
40
+ * the issue path names the key at fault. `"unreadable"` and `"syntax"` are
41
+ * whole-file failures where no key is at fault and remedies that talk about
42
+ * keys do not apply; they are kept apart because a permission error announced
43
+ * as invalid YAML sends the operator after the wrong problem.
44
+ */
45
+ export type SettingsIssueKind = "unreadable" | "syntax" | "schema";
46
+
47
+ export interface SettingsIssue {
48
+ /** Dotted key path, e.g. `orchestrator.target` or `targets[2].runtime`. */
49
+ path: string;
50
+ message: string;
51
+ kind?: SettingsIssueKind;
52
+ }
53
+
54
+ /**
55
+ * The failure kind of an issue list. Only the first issue can be a file-level
56
+ * failure: a read or a parse that fails returns exactly one issue and never
57
+ * reaches the schema walk, so a mixed list is always schema issues.
58
+ */
59
+ function issuesKind(issues: ReadonlyArray<SettingsIssue>): SettingsIssueKind {
60
+ return issues[0]?.kind ?? "schema";
61
+ }
62
+
63
+ /**
64
+ * The one action that repairs each failure kind, followed by the two commands
65
+ * that are true for all of them. `clio-coder doctor --fix` is deliberately not the
66
+ * first line: `--fix` creates missing structure and repairs credential
67
+ * permissions, and initialization never reads or rewrites an existing
68
+ * settings.yaml, so on its own it leaves every one of these failures exactly
69
+ * as it found them.
70
+ */
71
+ function settingsRemedy(kind: SettingsIssueKind, path: string): string {
72
+ if (kind === "unreadable") return `Restore read access to ${path}, then run \`clio-coder doctor\` to re-check.`;
73
+ if (kind === "syntax") return `Fix the YAML in ${path}, then run \`clio-coder doctor\` to re-check.`;
74
+ return `Fix the keys above in ${path}, then run \`clio-coder doctor\` to re-check.`;
75
+ }
76
+
77
+ /** The same repair as {@link settingsRemedy}, phrased to sit inside one line's parentheses. */
78
+ function settingsRemedyInline(kind: SettingsIssueKind, path: string): string {
79
+ if (kind === "unreadable") return `restore read access to ${path}`;
80
+ if (kind === "syntax") return `fix the YAML in ${path}`;
81
+ return `edit the named keys in ${path}`;
82
+ }
83
+
84
+ export class SettingsValidationError extends Error {
85
+ readonly issues: ReadonlyArray<SettingsIssue>;
86
+
87
+ /**
88
+ * The headline and the remedy both follow the failure kind. A file that
89
+ * cannot be read has no key at fault, so telling its reader to "fix the keys
90
+ * above" named a repair that does not exist for the problem they have.
91
+ */
92
+ constructor(issues: ReadonlyArray<SettingsIssue>) {
93
+ const path = join(resolveClioDirs().config, "settings.yaml");
94
+ const kind = issuesKind(issues);
95
+ // File-level issues all carry the same `(root)` path, which says nothing;
96
+ // their message already names what failed.
97
+ const lines =
98
+ kind === "schema"
99
+ ? issues.map((issue) => ` ${issue.path}: ${issue.message}`)
100
+ : issues.map((issue) => ` ${issue.message}`);
101
+ const headline = kind === "schema" ? "settings.yaml failed validation:" : "settings.yaml cannot be loaded:";
102
+ super(
103
+ `${headline}\n${lines.join("\n")}\n\n` +
104
+ `${settingsRemedy(kind, path)}\n` +
105
+ "`clio-coder doctor --fix` repairs directories and credential permissions; it never rewrites settings.\n" +
106
+ "To discard these settings and start from defaults instead, run `clio-coder reset --config --force`.",
107
+ );
108
+ this.name = "SettingsValidationError";
109
+ this.issues = issues;
110
+ }
111
+ }
112
+
113
+ function foldToOneLine(text: string): string {
114
+ return text.replace(/\s*\n\s*/g, " ").trim();
115
+ }
116
+
117
+ /**
118
+ * The summary line of a YAML parse error, without the source excerpt it carries.
119
+ *
120
+ * The `yaml` package formats an error as a summary, then a blank line, then the
121
+ * offending source, then a caret diagram. Folding all of that into one line
122
+ * dragged the raw fragment into the middle of the notice, so a settings file
123
+ * containing `\t\t: : :` reported "at line 1, column 1: : : : ^^", where the
124
+ * tail reads as corruption of the message rather than a quote of the file. The
125
+ * summary already names the line and the column, which is what locates the
126
+ * fault; the excerpt is the operator's own file and they are being sent to it.
127
+ *
128
+ * The summary ends on the colon that introduced the excerpt, so dropping the
129
+ * excerpt leaves it dangling in front of whatever follows.
130
+ */
131
+ function yamlErrorSummary(error: unknown): string {
132
+ const raw = error instanceof Error ? error.message : String(error);
133
+ return (raw.split("\n")[0] ?? raw).trim().replace(/:$/u, "");
134
+ }
135
+
136
+ /** The failure in three words, for a line whose tail may not survive clamping. */
137
+ function settingsFailureHeadline(kind: SettingsIssueKind): string {
138
+ if (kind === "unreadable") return "settings.yaml cannot be read";
139
+ if (kind === "syntax") return "settings.yaml is not valid YAML";
140
+ return "settings.yaml failed validation";
141
+ }
142
+
143
+ /**
144
+ * One operator-facing line for a settings file that will not load, carrying
145
+ * the exact key paths for a schema failure and the remedy that fits whatever
146
+ * actually failed. Doctor renders it as its settings row and the runtime
147
+ * reload path renders it as a TUI notice, so both speak with one voice.
148
+ *
149
+ * Order is load-bearing: kind, then remedy, then detail. Both live surfaces
150
+ * clamp this to the frame width with no way to expand it, so a line that ended
151
+ * with the remedy ended with the only part the operator could act on cut off,
152
+ * and every notice read as a complaint with no fix attached. Detail is last
153
+ * because it is the part a clamp can afford to lose: the remedy already names
154
+ * the file to go and look at.
155
+ */
156
+ export function formatSettingsIssues(issues: ReadonlyArray<SettingsIssue>): string {
157
+ if (issues.length === 0) return "";
158
+ const path = join(resolveClioDirs().config, "settings.yaml");
159
+ const kind = issuesKind(issues);
160
+ // A file-level issue message repeats the kind because the thrown multi-line
161
+ // error has no headline of its own. Here the headline says it already.
162
+ const detail =
163
+ kind === "schema"
164
+ ? issues.map((issue) => `${issue.path}: ${issue.message}`).join("; ")
165
+ : issues.map((issue) => issue.message.replace(/^(?:invalid YAML|unreadable): /u, "")).join("; ");
166
+ const discard = "`clio-coder reset --config --force` to start from defaults";
167
+ return foldToOneLine(
168
+ `${settingsFailureHeadline(kind)}: ${settingsRemedyInline(kind, path)}, or ${discard}. ${detail}`,
169
+ );
170
+ }
171
+
172
+ /**
173
+ * {@link formatSettingsIssues} for a caught error, for callers that hold the
174
+ * throw rather than the issue list.
175
+ *
176
+ * Live surfaces render this instead of the error object. Handing an error to
177
+ * `console.error` inside a running TUI prints a `util.inspect` dump: the
178
+ * `issues` array with its `\n` escapes, a stack trace naming a dist chunk, and
179
+ * a YAML excerpt whose newlines walk straight over the live frame.
180
+ */
181
+ export function formatSettingsFailure(error: unknown): string {
182
+ if (error instanceof SettingsValidationError && error.issues.length > 0) return formatSettingsIssues(error.issues);
183
+ return foldToOneLine(error instanceof Error ? error.message : String(error));
184
+ }
185
+
186
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
187
+ return typeof value === "object" && value !== null && !Array.isArray(value);
188
+ }
189
+
190
+ function cloneValue<T>(value: T): T {
191
+ return structuredClone(value);
192
+ }
193
+
194
+ class Issues {
195
+ readonly list: SettingsIssue[] = [];
196
+
197
+ add(path: string, message: string): void {
198
+ this.list.push({ path, message });
199
+ }
200
+
201
+ unknownKeys(path: string, raw: Record<string, unknown>, known: ReadonlyArray<string>): void {
202
+ const knownSet = new Set(known);
203
+ for (const key of Object.keys(raw)) {
204
+ if (!knownSet.has(key)) this.add(path ? `${path}.${key}` : key, "unknown key");
205
+ }
206
+ }
207
+ }
208
+
209
+ function expectString(issues: Issues, path: string, value: unknown): string | undefined {
210
+ if (typeof value !== "string") {
211
+ issues.add(path, `expected a string, got ${describe(value)}`);
212
+ return undefined;
213
+ }
214
+ const trimmed = value.trim();
215
+ if (trimmed.length === 0) {
216
+ issues.add(path, "expected a non-empty string");
217
+ return undefined;
218
+ }
219
+ return trimmed;
220
+ }
221
+
222
+ function expectBoolean(issues: Issues, path: string, value: unknown): boolean | undefined {
223
+ if (typeof value !== "boolean") {
224
+ issues.add(path, `expected a boolean, got ${describe(value)}`);
225
+ return undefined;
226
+ }
227
+ return value;
228
+ }
229
+
230
+ function expectNumber(
231
+ issues: Issues,
232
+ path: string,
233
+ value: unknown,
234
+ opts?: { min?: number; max?: number },
235
+ ): number | undefined {
236
+ if (typeof value !== "number" || !Number.isFinite(value)) {
237
+ issues.add(path, `expected a number, got ${describe(value)}`);
238
+ return undefined;
239
+ }
240
+ if (opts?.min !== undefined && value < opts.min) {
241
+ issues.add(path, `expected a number >= ${opts.min}, got ${value}`);
242
+ return undefined;
243
+ }
244
+ if (opts?.max !== undefined && value > opts.max) {
245
+ issues.add(path, `expected a number <= ${opts.max}, got ${value}`);
246
+ return undefined;
247
+ }
248
+ return value;
249
+ }
250
+
251
+ function expectInteger(
252
+ issues: Issues,
253
+ path: string,
254
+ value: unknown,
255
+ opts?: { min?: number; max?: number },
256
+ ): number | undefined {
257
+ if (typeof value !== "number" || !Number.isInteger(value)) {
258
+ issues.add(path, `expected an integer, got ${describe(value)}`);
259
+ return undefined;
260
+ }
261
+ if (opts?.min !== undefined && value < opts.min) {
262
+ issues.add(path, `expected an integer >= ${opts.min}, got ${value}`);
263
+ return undefined;
264
+ }
265
+ if (opts?.max !== undefined && value > opts.max) {
266
+ issues.add(path, `expected an integer <= ${opts.max}, got ${value}`);
267
+ return undefined;
268
+ }
269
+ return value;
270
+ }
271
+
272
+ function expectEnum<T extends string>(
273
+ issues: Issues,
274
+ path: string,
275
+ value: unknown,
276
+ allowed: ReadonlyArray<T>,
277
+ ): T | undefined {
278
+ if (typeof value === "string" && (allowed as ReadonlyArray<string>).includes(value)) return value as T;
279
+ issues.add(path, `expected one of ${allowed.join(" | ")}, got ${describe(value)}`);
280
+ return undefined;
281
+ }
282
+
283
+ function expectStringArray(issues: Issues, path: string, value: unknown): string[] | undefined {
284
+ if (!Array.isArray(value)) {
285
+ issues.add(path, `expected a list of strings, got ${describe(value)}`);
286
+ return undefined;
287
+ }
288
+ const seen = new Set<string>();
289
+ const out: string[] = [];
290
+ for (let i = 0; i < value.length; i += 1) {
291
+ const entry = expectString(issues, `${path}[${i}]`, value[i]);
292
+ if (entry === undefined) continue;
293
+ if (seen.has(entry)) continue;
294
+ seen.add(entry);
295
+ out.push(entry);
296
+ }
297
+ return out;
298
+ }
299
+
300
+ function expectStringRecord(issues: Issues, path: string, value: unknown): Record<string, string> | undefined {
301
+ if (!isPlainObject(value)) {
302
+ issues.add(path, `expected a string map, got ${describe(value)}`);
303
+ return undefined;
304
+ }
305
+ const out: Record<string, string> = {};
306
+ for (const [key, raw] of Object.entries(value)) {
307
+ const entry = expectString(issues, `${path}.${key}`, raw);
308
+ if (entry !== undefined) out[key] = entry;
309
+ }
310
+ return out;
311
+ }
312
+
313
+ function describe(value: unknown): string {
314
+ if (value === null) return "null";
315
+ if (Array.isArray(value)) return "a list";
316
+ if (typeof value === "object") return "a map";
317
+ if (typeof value === "string") return value.trim().length === 0 ? "an empty string" : JSON.stringify(value);
318
+ return String(value);
319
+ }
320
+
321
+ const TOOL_CALL_FORMATS = ["openai", "anthropic", "hermes", "llama3-json", "mistral", "qwen", "xml"] as const;
322
+ const THINKING_FORMATS = [
323
+ "qwen-chat-template",
324
+ "openrouter",
325
+ "zai",
326
+ "anthropic-extended",
327
+ "deepseek-r1",
328
+ "openai-codex",
329
+ "harmony",
330
+ ] as const;
331
+ const STRUCTURED_OUTPUTS = ["json-schema", "gbnf", "xgrammar", "none"] as const;
332
+ const AUTONOMY_LEVELS = ["read-only", "suggest", "auto-edit", "full-auto"] as const;
333
+ const TOOL_GOVERNANCE = ["clio-policy", "agent-managed", "deny-all"] as const;
334
+
335
+ type TargetCapabilities = NonNullable<ClioSettings["targets"][number]["capabilities"]>;
336
+
337
+ function validateCapabilities(issues: Issues, path: string, value: unknown): TargetCapabilities | undefined {
338
+ if (!isPlainObject(value)) {
339
+ issues.add(path, `expected a map, got ${describe(value)}`);
340
+ return undefined;
341
+ }
342
+ issues.unknownKeys(path, value, [
343
+ "chat",
344
+ "tools",
345
+ "toolCallFormat",
346
+ "reasoning",
347
+ "thinkingFormat",
348
+ "structuredOutputs",
349
+ "vision",
350
+ "audio",
351
+ "embeddings",
352
+ "rerank",
353
+ "fim",
354
+ "contextWindow",
355
+ "maxTokens",
356
+ ]);
357
+ const out: TargetCapabilities = {};
358
+ for (const key of ["chat", "tools", "reasoning", "vision", "audio", "embeddings", "rerank", "fim"] as const) {
359
+ if (key in value) {
360
+ const v = expectBoolean(issues, `${path}.${key}`, value[key]);
361
+ if (v !== undefined) out[key] = v;
362
+ }
363
+ }
364
+ if ("toolCallFormat" in value) {
365
+ const v = expectEnum(issues, `${path}.toolCallFormat`, value.toolCallFormat, TOOL_CALL_FORMATS);
366
+ if (v !== undefined) out.toolCallFormat = v;
367
+ }
368
+ if ("thinkingFormat" in value) {
369
+ const v = expectEnum(issues, `${path}.thinkingFormat`, value.thinkingFormat, THINKING_FORMATS);
370
+ if (v !== undefined) out.thinkingFormat = v;
371
+ }
372
+ if ("structuredOutputs" in value) {
373
+ const v = expectEnum(issues, `${path}.structuredOutputs`, value.structuredOutputs, STRUCTURED_OUTPUTS);
374
+ if (v !== undefined) out.structuredOutputs = v;
375
+ }
376
+ if ("contextWindow" in value) {
377
+ const v = expectInteger(issues, `${path}.contextWindow`, value.contextWindow, { min: 0 });
378
+ if (v !== undefined) out.contextWindow = v;
379
+ }
380
+ if ("maxTokens" in value) {
381
+ const v = expectInteger(issues, `${path}.maxTokens`, value.maxTokens, { min: 0 });
382
+ if (v !== undefined) out.maxTokens = v;
383
+ }
384
+ return Object.keys(out).length > 0 ? out : undefined;
385
+ }
386
+
387
+ function validateAuth(
388
+ issues: Issues,
389
+ path: string,
390
+ value: unknown,
391
+ ): ClioSettings["targets"][number]["auth"] | undefined {
392
+ if (!isPlainObject(value)) {
393
+ issues.add(path, `expected a map, got ${describe(value)}`);
394
+ return undefined;
395
+ }
396
+ issues.unknownKeys(path, value, ["apiKeyEnvVar", "apiKeyRef", "oauthProfile", "headers"]);
397
+ const out: NonNullable<ClioSettings["targets"][number]["auth"]> = {};
398
+ for (const key of ["apiKeyEnvVar", "apiKeyRef", "oauthProfile"] as const) {
399
+ if (key in value) {
400
+ const v = expectString(issues, `${path}.${key}`, value[key]);
401
+ if (v !== undefined) out[key] = v;
402
+ }
403
+ }
404
+ if ("headers" in value) {
405
+ const v = expectStringRecord(issues, `${path}.headers`, value.headers);
406
+ if (v !== undefined && Object.keys(v).length > 0) out.headers = v;
407
+ }
408
+ return Object.keys(out).length > 0 ? out : undefined;
409
+ }
410
+
411
+ function validatePricing(
412
+ issues: Issues,
413
+ path: string,
414
+ value: unknown,
415
+ ): ClioSettings["targets"][number]["pricing"] | undefined {
416
+ if (!isPlainObject(value)) {
417
+ issues.add(path, `expected a map, got ${describe(value)}`);
418
+ return undefined;
419
+ }
420
+ issues.unknownKeys(path, value, ["input", "output", "cacheRead", "cacheWrite"]);
421
+ const input = expectNumber(issues, `${path}.input`, value.input, { min: 0 });
422
+ const output = expectNumber(issues, `${path}.output`, value.output, { min: 0 });
423
+ if (input === undefined || output === undefined) return undefined;
424
+ const out: NonNullable<ClioSettings["targets"][number]["pricing"]> = { input, output };
425
+ if ("cacheRead" in value) {
426
+ const v = expectNumber(issues, `${path}.cacheRead`, value.cacheRead, { min: 0 });
427
+ if (v !== undefined) out.cacheRead = v;
428
+ }
429
+ if ("cacheWrite" in value) {
430
+ const v = expectNumber(issues, `${path}.cacheWrite`, value.cacheWrite, { min: 0 });
431
+ if (v !== undefined) out.cacheWrite = v;
432
+ }
433
+ return out;
434
+ }
435
+
436
+ function validateLmStudioSettings(
437
+ issues: Issues,
438
+ path: string,
439
+ value: unknown,
440
+ ): ClioSettings["targets"][number]["lmstudio"] | undefined {
441
+ if (!isPlainObject(value)) {
442
+ issues.add(path, `expected a map, got ${describe(value)}`);
443
+ return undefined;
444
+ }
445
+ issues.unknownKeys(path, value, ["load", "request"]);
446
+ const out: NonNullable<ClioSettings["targets"][number]["lmstudio"]> = {};
447
+ if ("load" in value) {
448
+ if (!isPlainObject(value.load)) {
449
+ issues.add(`${path}.load`, `expected a map, got ${describe(value.load)}`);
450
+ } else {
451
+ issues.unknownKeys(`${path}.load`, value.load, [
452
+ "contextLength",
453
+ "flashAttention",
454
+ "evalBatchSize",
455
+ "numExperts",
456
+ "offloadKvCacheToGpu",
457
+ ]);
458
+ const load: NonNullable<typeof out.load> = {};
459
+ for (const key of ["contextLength", "evalBatchSize", "numExperts"] as const) {
460
+ if (key in value.load) {
461
+ const parsed = expectInteger(issues, `${path}.load.${key}`, value.load[key], { min: 1 });
462
+ if (parsed !== undefined) load[key] = parsed;
463
+ }
464
+ }
465
+ for (const key of ["flashAttention", "offloadKvCacheToGpu"] as const) {
466
+ if (key in value.load) {
467
+ const parsed = expectBoolean(issues, `${path}.load.${key}`, value.load[key]);
468
+ if (parsed !== undefined) load[key] = parsed;
469
+ }
470
+ }
471
+ if (Object.keys(load).length > 0) out.load = load;
472
+ }
473
+ }
474
+ if ("request" in value) {
475
+ if (!isPlainObject(value.request)) {
476
+ issues.add(`${path}.request`, `expected a map, got ${describe(value.request)}`);
477
+ } else {
478
+ issues.unknownKeys(`${path}.request`, value.request, ["ttlSeconds", "draftModel", "reasoning"]);
479
+ const request: NonNullable<typeof out.request> = {};
480
+ if ("ttlSeconds" in value.request) {
481
+ const parsed = expectInteger(issues, `${path}.request.ttlSeconds`, value.request.ttlSeconds, { min: 1 });
482
+ if (parsed !== undefined) request.ttlSeconds = parsed;
483
+ }
484
+ if ("draftModel" in value.request) {
485
+ const parsed = expectString(issues, `${path}.request.draftModel`, value.request.draftModel);
486
+ if (parsed !== undefined) request.draftModel = parsed;
487
+ }
488
+ if ("reasoning" in value.request) {
489
+ const parsed = expectEnum(issues, `${path}.request.reasoning`, value.request.reasoning, [
490
+ "auto",
491
+ "off",
492
+ "on",
493
+ "low",
494
+ "medium",
495
+ "high",
496
+ ] as const);
497
+ if (parsed !== undefined) request.reasoning = parsed;
498
+ }
499
+ if (Object.keys(request).length > 0) out.request = request;
500
+ }
501
+ }
502
+ return Object.keys(out).length > 0 ? out : undefined;
503
+ }
504
+
505
+ function validateTarget(issues: Issues, path: string, value: unknown): ClioSettings["targets"][number] | null {
506
+ if (!isPlainObject(value)) {
507
+ issues.add(path, `expected a map, got ${describe(value)}`);
508
+ return null;
509
+ }
510
+ issues.unknownKeys(path, value, [
511
+ "id",
512
+ "runtime",
513
+ "url",
514
+ "auth",
515
+ "defaultModel",
516
+ "wireModels",
517
+ "capabilities",
518
+ "lifecycle",
519
+ "gateway",
520
+ "pricing",
521
+ "lmstudio",
522
+ ]);
523
+ const id = "id" in value ? expectString(issues, `${path}.id`, value.id) : undefined;
524
+ const runtime = "runtime" in value ? expectString(issues, `${path}.runtime`, value.runtime) : undefined;
525
+ if (!("id" in value)) issues.add(`${path}.id`, "required");
526
+ if (!("runtime" in value)) issues.add(`${path}.runtime`, "required");
527
+ if (id === undefined || runtime === undefined) return null;
528
+ const target: ClioSettings["targets"][number] = { id, runtime };
529
+ if ("url" in value) {
530
+ const v = expectString(issues, `${path}.url`, value.url);
531
+ if (v !== undefined) target.url = v;
532
+ }
533
+ if ("auth" in value) {
534
+ const v = validateAuth(issues, `${path}.auth`, value.auth);
535
+ if (v !== undefined) target.auth = v;
536
+ }
537
+ if ("wireModels" in value) {
538
+ const v = expectStringArray(issues, `${path}.wireModels`, value.wireModels);
539
+ if (v !== undefined && v.length > 0) target.wireModels = v;
540
+ }
541
+ if ("defaultModel" in value) {
542
+ const v = expectString(issues, `${path}.defaultModel`, value.defaultModel);
543
+ if (v !== undefined) target.defaultModel = v;
544
+ } else if (target.wireModels?.[0]) {
545
+ target.defaultModel = target.wireModels[0];
546
+ }
547
+ if ("capabilities" in value) {
548
+ const v = validateCapabilities(issues, `${path}.capabilities`, value.capabilities);
549
+ if (v !== undefined) target.capabilities = v;
550
+ }
551
+ if ("lifecycle" in value) {
552
+ const v = expectEnum(issues, `${path}.lifecycle`, value.lifecycle, ["user-managed", "clio-managed"] as const);
553
+ if (v !== undefined) target.lifecycle = v;
554
+ }
555
+ if ("gateway" in value) {
556
+ const v = expectBoolean(issues, `${path}.gateway`, value.gateway);
557
+ if (v !== undefined) target.gateway = v;
558
+ }
559
+ if ("pricing" in value) {
560
+ const v = validatePricing(issues, `${path}.pricing`, value.pricing);
561
+ if (v !== undefined) target.pricing = v;
562
+ }
563
+ if ("lmstudio" in value) {
564
+ const v = validateLmStudioSettings(issues, `${path}.lmstudio`, value.lmstudio);
565
+ if (v !== undefined) target.lmstudio = v;
566
+ }
567
+ return target;
568
+ }
569
+
570
+ /**
571
+ * Routing references (orchestrator/worker targets, scope, favorites) must
572
+ * point at a configured target id. Dangling references are normalized away
573
+ * deterministically rather than rejected: deleting a target must not brick
574
+ * every session that still mentions it. This is referential normalization,
575
+ * not a legacy reader; the key names themselves are never aliased.
576
+ */
577
+ function validateWorkerTarget(
578
+ issues: Issues,
579
+ path: string,
580
+ value: unknown,
581
+ defaults: ClioSettings["orchestrator"],
582
+ targets: ReadonlyArray<ClioSettings["targets"][number]>,
583
+ options?: { nodeIds?: ReadonlySet<string> },
584
+ ): ClioSettings["orchestrator"] & { node?: string } {
585
+ const out: ClioSettings["orchestrator"] & { node?: string } = cloneValue(defaults);
586
+ if (!isPlainObject(value)) {
587
+ issues.add(path, `expected a map, got ${describe(value)}`);
588
+ return out;
589
+ }
590
+ const allowNode = options?.nodeIds !== undefined;
591
+ issues.unknownKeys(
592
+ path,
593
+ value,
594
+ allowNode ? ["target", "model", "thinkingLevel", "node"] : ["target", "model", "thinkingLevel"],
595
+ );
596
+ if ("target" in value && value.target !== null) {
597
+ const v = expectString(issues, `${path}.target`, value.target);
598
+ if (v !== undefined) out.target = targets.some((entry) => entry.id === v) ? v : null;
599
+ }
600
+ if ("thinkingLevel" in value) {
601
+ const v = expectEnum(issues, `${path}.thinkingLevel`, value.thinkingLevel, THINKING_LEVELS);
602
+ if (v !== undefined) out.thinkingLevel = v;
603
+ }
604
+ if ("model" in value && value.model !== null) {
605
+ const v = expectString(issues, `${path}.model`, value.model);
606
+ if (v !== undefined) out.model = v;
607
+ }
608
+ // Node pins follow the target idiom: a pin naming a node that is not
609
+ // configured (and is not the implicit local node) is dropped, not fatal.
610
+ if (allowNode && "node" in value && value.node !== null) {
611
+ const v = expectString(issues, `${path}.node`, value.node);
612
+ if (v !== undefined) {
613
+ const id = v.trim();
614
+ if (id.length > 0 && (id === "local" || options?.nodeIds?.has(id) === true)) out.node = id;
615
+ }
616
+ }
617
+ if (!out.target) {
618
+ out.model = null;
619
+ } else if (out.model === null) {
620
+ out.model = targets.find((entry) => entry.id === out.target)?.defaultModel ?? null;
621
+ }
622
+ return out;
623
+ }
624
+
625
+ /** `targetId/wireModelId` refs filtered to configured target ids. */
626
+ function normalizeModelRefs(
627
+ refs: ReadonlyArray<string>,
628
+ targets: ReadonlyArray<ClioSettings["targets"][number]>,
629
+ ): string[] {
630
+ const byId = new Set(targets.map((target) => target.id));
631
+ const out: string[] = [];
632
+ for (const ref of refs) {
633
+ const [targetId, ...modelParts] = ref.split("/");
634
+ if (!targetId || !byId.has(targetId) || modelParts.length === 0) continue;
635
+ const model = modelParts.join("/").trim();
636
+ if (!model) continue;
637
+ const normalized = `${targetId}/${model}`;
638
+ if (!out.includes(normalized)) out.push(normalized);
639
+ }
640
+ return out;
641
+ }
642
+
643
+ /**
644
+ * Scope entries: `targetId` or `targetId/wireModelId`, deduplicated. A ref
645
+ * whose target is not configured is kept, not dropped: a renamed or
646
+ * temporarily removed target is the common staleness case, and silently
647
+ * deleting the operator's cycle set at load destroys a preference they never
648
+ * asked to change. Disclosure belongs to the presentation layer, which lists
649
+ * unresolvable refs under "Unavailable"; the consumers that must resolve a ref
650
+ * (advanceScopedTarget) filter it themselves.
651
+ */
652
+ function normalizeScope(refs: ReadonlyArray<string>): string[] {
653
+ const out: string[] = [];
654
+ for (const ref of refs) {
655
+ const trimmed = ref.trim();
656
+ const [targetId] = trimmed.split("/");
657
+ if (!targetId) continue;
658
+ if (!out.includes(trimmed)) out.push(trimmed);
659
+ }
660
+ return out;
661
+ }
662
+
663
+ function validateDelegationAgent(
664
+ issues: Issues,
665
+ path: string,
666
+ value: unknown,
667
+ defaults: ClioSettings["delegation"]["defaults"],
668
+ seen: Set<string>,
669
+ ): ClioSettings["delegation"]["agents"][number] | null {
670
+ if (!isPlainObject(value)) {
671
+ issues.add(path, `expected a map, got ${describe(value)}`);
672
+ return null;
673
+ }
674
+ issues.unknownKeys(path, value, [
675
+ "id",
676
+ "command",
677
+ "args",
678
+ "cwd",
679
+ "env",
680
+ "connectTimeoutMs",
681
+ "turnTimeoutMs",
682
+ "permissionTimeoutMs",
683
+ "stallTimeoutMs",
684
+ "toolGovernance",
685
+ "projectContext",
686
+ "labels",
687
+ ]);
688
+ const id = "id" in value ? expectString(issues, `${path}.id`, value.id) : undefined;
689
+ const command = "command" in value ? expectString(issues, `${path}.command`, value.command) : undefined;
690
+ if (!("id" in value)) issues.add(`${path}.id`, "required");
691
+ if (!("command" in value)) issues.add(`${path}.command`, "required");
692
+ if (id === undefined || command === undefined) return null;
693
+ if (id === "auto") {
694
+ issues.add(`${path}.id`, "'auto' is reserved for coordinator-owned agent selection");
695
+ return null;
696
+ }
697
+ if (seen.has(id)) {
698
+ issues.add(`${path}.id`, `duplicate delegation agent id '${id}'`);
699
+ return null;
700
+ }
701
+ seen.add(id);
702
+ const agent: ClioSettings["delegation"]["agents"][number] = {
703
+ id,
704
+ command,
705
+ args: [],
706
+ connectTimeoutMs: defaults.connectTimeoutMs,
707
+ turnTimeoutMs: defaults.turnTimeoutMs,
708
+ permissionTimeoutMs: defaults.permissionTimeoutMs,
709
+ toolGovernance: defaults.toolGovernance,
710
+ };
711
+ if ("args" in value) {
712
+ const v = expectStringArray(issues, `${path}.args`, value.args);
713
+ if (v !== undefined) agent.args = v;
714
+ }
715
+ for (const key of ["connectTimeoutMs", "turnTimeoutMs", "permissionTimeoutMs"] as const) {
716
+ if (key in value) {
717
+ const v = expectInteger(issues, `${path}.${key}`, value[key], { min: 1, max: MAX_TIMER_DELAY_MS });
718
+ if (v !== undefined) agent[key] = v;
719
+ }
720
+ }
721
+ if ("stallTimeoutMs" in value) {
722
+ const v = expectInteger(issues, `${path}.stallTimeoutMs`, value.stallTimeoutMs);
723
+ if (v !== undefined) agent.stallTimeoutMs = v;
724
+ }
725
+ if ("toolGovernance" in value) {
726
+ const v = expectEnum(issues, `${path}.toolGovernance`, value.toolGovernance, TOOL_GOVERNANCE);
727
+ if (v !== undefined) agent.toolGovernance = v;
728
+ }
729
+ if ("projectContext" in value) {
730
+ const v = expectEnum(issues, `${path}.projectContext`, value.projectContext, ["none", "bounded"] as const);
731
+ if (v !== undefined) agent.projectContext = v;
732
+ }
733
+ if ("cwd" in value) {
734
+ const v = expectString(issues, `${path}.cwd`, value.cwd);
735
+ if (v !== undefined) agent.cwd = v;
736
+ }
737
+ if ("env" in value) {
738
+ const v = expectStringRecord(issues, `${path}.env`, value.env);
739
+ if (v !== undefined && Object.keys(v).length > 0) agent.env = v;
740
+ }
741
+ if ("labels" in value) {
742
+ const v = expectStringRecord(issues, `${path}.labels`, value.labels);
743
+ if (v !== undefined && Object.keys(v).length > 0) agent.labels = v;
744
+ }
745
+ return agent;
746
+ }
747
+
748
+ function validateDelegation(issues: Issues, value: unknown): ClioSettings["delegation"] {
749
+ const out = cloneValue(DEFAULT_SETTINGS.delegation);
750
+ if (!isPlainObject(value)) {
751
+ issues.add("delegation", `expected a map, got ${describe(value)}`);
752
+ return out;
753
+ }
754
+ issues.unknownKeys("delegation", value, ["agents", "defaults"]);
755
+ if ("defaults" in value) {
756
+ if (!isPlainObject(value.defaults)) {
757
+ issues.add("delegation.defaults", `expected a map, got ${describe(value.defaults)}`);
758
+ } else {
759
+ issues.unknownKeys("delegation.defaults", value.defaults, [
760
+ "connectTimeoutMs",
761
+ "turnTimeoutMs",
762
+ "permissionTimeoutMs",
763
+ "toolGovernance",
764
+ ]);
765
+ for (const key of ["connectTimeoutMs", "turnTimeoutMs", "permissionTimeoutMs"] as const) {
766
+ if (key in value.defaults) {
767
+ const v = expectInteger(issues, `delegation.defaults.${key}`, value.defaults[key], {
768
+ min: 1,
769
+ max: MAX_TIMER_DELAY_MS,
770
+ });
771
+ if (v !== undefined) out.defaults[key] = v;
772
+ }
773
+ }
774
+ if ("toolGovernance" in value.defaults) {
775
+ const v = expectEnum(issues, "delegation.defaults.toolGovernance", value.defaults.toolGovernance, TOOL_GOVERNANCE);
776
+ if (v !== undefined) out.defaults.toolGovernance = v;
777
+ }
778
+ }
779
+ }
780
+ if ("agents" in value) {
781
+ if (!Array.isArray(value.agents)) {
782
+ issues.add("delegation.agents", `expected a list, got ${describe(value.agents)}`);
783
+ } else {
784
+ const seen = new Set<string>();
785
+ out.agents = value.agents
786
+ .map((entry, i) => validateDelegationAgent(issues, `delegation.agents[${i}]`, entry, out.defaults, seen))
787
+ .filter((entry): entry is NonNullable<typeof entry> => entry !== null);
788
+ }
789
+ }
790
+ return out;
791
+ }
792
+
793
+ type FleetNodeConfig = ClioSettings["fleet"]["nodes"][number];
794
+
795
+ function validateFleetNode(issues: Issues, path: string, value: unknown, seen: Set<string>): FleetNodeConfig | null {
796
+ if (!isPlainObject(value)) {
797
+ issues.add(path, `expected a map, got ${describe(value)}`);
798
+ return null;
799
+ }
800
+ issues.unknownKeys(path, value, [
801
+ "id",
802
+ "host",
803
+ "user",
804
+ "port",
805
+ "identityFile",
806
+ "clioCoderEntry",
807
+ // Pre-0.3.1 spelling, still read.
808
+ "clioEntry",
809
+ "labels",
810
+ "maxWorkers",
811
+ "residency",
812
+ ]);
813
+ const id = "id" in value ? expectString(issues, `${path}.id`, value.id) : undefined;
814
+ const host = "host" in value ? expectString(issues, `${path}.host`, value.host) : undefined;
815
+ if (!("id" in value)) issues.add(`${path}.id`, "required");
816
+ if (!("host" in value)) issues.add(`${path}.host`, "required");
817
+ if (id === undefined || host === undefined) return null;
818
+ const trimmedId = id.trim();
819
+ if (trimmedId.length === 0) {
820
+ issues.add(`${path}.id`, "must not be blank");
821
+ return null;
822
+ }
823
+ if (trimmedId === "local") {
824
+ issues.add(`${path}.id`, "'local' is reserved for the implicit local node");
825
+ return null;
826
+ }
827
+ if (seen.has(trimmedId)) {
828
+ issues.add(`${path}.id`, `duplicate fleet node id '${trimmedId}'`);
829
+ return null;
830
+ }
831
+ seen.add(trimmedId);
832
+ const node: FleetNodeConfig = { id: trimmedId, host, maxWorkers: 2 };
833
+ if ("user" in value) {
834
+ const v = expectString(issues, `${path}.user`, value.user);
835
+ if (v !== undefined) node.user = v;
836
+ }
837
+ if ("port" in value) {
838
+ const v = expectInteger(issues, `${path}.port`, value.port, { min: 1 });
839
+ if (v !== undefined) node.port = v;
840
+ }
841
+ if ("identityFile" in value) {
842
+ const v = expectString(issues, `${path}.identityFile`, value.identityFile);
843
+ if (v !== undefined) node.identityFile = v;
844
+ }
845
+ // The key was `clioEntry` before 0.3.1. Both spellings read; the new one
846
+ // wins when a node carries both.
847
+ if ("clioEntry" in value) {
848
+ const v = expectString(issues, `${path}.clioEntry`, value.clioEntry);
849
+ if (v !== undefined) node.clioCoderEntry = v;
850
+ }
851
+ if ("clioCoderEntry" in value) {
852
+ const v = expectString(issues, `${path}.clioCoderEntry`, value.clioCoderEntry);
853
+ if (v !== undefined) node.clioCoderEntry = v;
854
+ }
855
+ if ("labels" in value) {
856
+ const v = expectStringArray(issues, `${path}.labels`, value.labels);
857
+ if (v !== undefined) node.labels = v;
858
+ }
859
+ if ("maxWorkers" in value) {
860
+ const v = expectInteger(issues, `${path}.maxWorkers`, value.maxWorkers, { min: 1 });
861
+ if (v !== undefined) node.maxWorkers = v;
862
+ }
863
+ if ("residency" in value) {
864
+ const v = expectEnum(issues, `${path}.residency`, value.residency, ["observe", "manage"] as const);
865
+ if (v !== undefined) node.residency = v;
866
+ }
867
+ return node;
868
+ }
869
+
870
+ function validateFleet(issues: Issues, value: unknown): ClioSettings["fleet"] {
871
+ const out = cloneValue(DEFAULT_SETTINGS.fleet);
872
+ if (!isPlainObject(value)) {
873
+ issues.add("fleet", `expected a map, got ${describe(value)}`);
874
+ return out;
875
+ }
876
+ issues.unknownKeys("fleet", value, ["nodes"]);
877
+ if ("nodes" in value) {
878
+ if (!Array.isArray(value.nodes)) {
879
+ issues.add("fleet.nodes", `expected a list, got ${describe(value.nodes)}`);
880
+ } else {
881
+ const seen = new Set<string>();
882
+ out.nodes = value.nodes
883
+ .map((entry, i) => validateFleetNode(issues, `fleet.nodes[${i}]`, entry, seen))
884
+ .filter((entry): entry is NonNullable<typeof entry> => entry !== null);
885
+ }
886
+ }
887
+ return out;
888
+ }
889
+
890
+ function validateMemorySettings(issues: Issues, value: unknown): ClioSettings["memory"] {
891
+ const out = cloneValue(DEFAULT_SETTINGS.memory);
892
+ if (!isPlainObject(value)) {
893
+ issues.add("memory", `expected a map, got ${describe(value)}`);
894
+ return out;
895
+ }
896
+ issues.unknownKeys("memory", value, ["intervention"]);
897
+ if (!("intervention" in value)) return out;
898
+ if (!isPlainObject(value.intervention)) {
899
+ issues.add("memory.intervention", `expected a map, got ${describe(value.intervention)}`);
900
+ return out;
901
+ }
902
+ const raw = value.intervention;
903
+ issues.unknownKeys("memory.intervention", raw, ["enabled", "everyNTools", "windowSteps", "maxTokens", "timeoutMs"]);
904
+ if ("enabled" in raw) {
905
+ const enabled = expectBoolean(issues, "memory.intervention.enabled", raw.enabled);
906
+ if (enabled !== undefined) out.intervention.enabled = enabled;
907
+ }
908
+ for (const key of ["everyNTools", "windowSteps", "maxTokens", "timeoutMs"] as const) {
909
+ if (!(key in raw)) continue;
910
+ const parsed = expectInteger(issues, `memory.intervention.${key}`, raw[key], {
911
+ min: key === "everyNTools" ? 2 : 1,
912
+ });
913
+ if (parsed !== undefined) out.intervention[key] = parsed;
914
+ }
915
+ return out;
916
+ }
917
+
918
+ function validateKeybindings(issues: Issues, value: unknown): ClioSettings["keybindings"] {
919
+ if (!isPlainObject(value)) {
920
+ issues.add("keybindings", `expected a map, got ${describe(value)}`);
921
+ return {};
922
+ }
923
+ const next: Record<string, string | string[]> = {};
924
+ for (const [rawKey, rawValue] of Object.entries(value)) {
925
+ const id = rawKey.trim();
926
+ if (!id) {
927
+ issues.add("keybindings", "empty keybinding id");
928
+ continue;
929
+ }
930
+ if (typeof rawValue === "string") {
931
+ const v = expectString(issues, `keybindings.${id}`, rawValue);
932
+ if (v !== undefined) next[id] = v;
933
+ continue;
934
+ }
935
+ if (Array.isArray(rawValue)) {
936
+ const v = expectStringArray(issues, `keybindings.${id}`, rawValue);
937
+ if (v !== undefined && v.length > 0) next[id] = v;
938
+ continue;
939
+ }
940
+ issues.add(`keybindings.${id}`, `expected a string or list of strings, got ${describe(rawValue)}`);
941
+ }
942
+ return next;
943
+ }
944
+
945
+ const TOP_LEVEL_KEYS = [
946
+ "version",
947
+ // Accepted and ignored. Settings files generated before 0.3.1 carry an
948
+ // `identity:` line for a control nothing ever read; listing it here keeps
949
+ // those files loading without an unknown-key issue.
950
+ "identity",
951
+ "autonomy",
952
+ "targets",
953
+ "runtimePlugins",
954
+ "orchestrator",
955
+ "background",
956
+ "memory",
957
+ "workers",
958
+ "fleet",
959
+ "routing",
960
+ "scope",
961
+ "modelSelector",
962
+ "budget",
963
+ "defaults",
964
+ "theme",
965
+ "terminal",
966
+ "skills",
967
+ "attribution",
968
+ "delegation",
969
+ "keybindings",
970
+ "compaction",
971
+ "retry",
972
+ "guardrails",
973
+ ] as const;
974
+
975
+ export interface SettingsValidationResult {
976
+ settings: ClioSettings;
977
+ issues: SettingsIssue[];
978
+ }
979
+
980
+ /**
981
+ * Validate a parsed settings document against the one schema. Returns the
982
+ * settings built from valid fields (defaults fill missing keys) plus every
983
+ * issue found, each carrying the exact key path. Callers that must not
984
+ * proceed on bad input use `readSettings`, which throws when issues exist;
985
+ * doctor reports the same issues read-only.
986
+ */
987
+ export function validateSettings(raw: unknown): SettingsValidationResult {
988
+ const issues = new Issues();
989
+ const settings = cloneValue(DEFAULT_SETTINGS);
990
+ // A genuinely missing settings file falls back to defaults before ever
991
+ // reaching here (validateSettingsFile returns early when the file is absent).
992
+ // So a null/undefined raw here means a present-but-empty or `null` document,
993
+ // which is malformed and must be a root-shape issue, not silent defaults.
994
+ if (!isPlainObject(raw)) {
995
+ issues.add("(root)", `expected a map, got ${describe(raw)}`);
996
+ return { settings, issues: issues.list };
997
+ }
998
+ issues.unknownKeys("", raw, TOP_LEVEL_KEYS);
999
+
1000
+ if ("version" in raw && raw.version !== 1) {
1001
+ issues.add("version", `expected 1, got ${describe(raw.version)}`);
1002
+ }
1003
+ if ("autonomy" in raw) {
1004
+ const v = expectEnum(issues, "autonomy", raw.autonomy, AUTONOMY_LEVELS);
1005
+ if (v !== undefined) settings.autonomy = v;
1006
+ }
1007
+
1008
+ if ("targets" in raw) {
1009
+ if (!Array.isArray(raw.targets)) {
1010
+ issues.add("targets", `expected a list, got ${describe(raw.targets)}`);
1011
+ } else {
1012
+ const seen = new Set<string>();
1013
+ const out: ClioSettings["targets"] = [];
1014
+ for (let i = 0; i < raw.targets.length; i += 1) {
1015
+ const target = validateTarget(issues, `targets[${i}]`, raw.targets[i]);
1016
+ if (!target) continue;
1017
+ if (seen.has(target.id)) {
1018
+ issues.add(`targets[${i}].id`, `duplicate target id '${target.id}'`);
1019
+ continue;
1020
+ }
1021
+ seen.add(target.id);
1022
+ out.push(target);
1023
+ }
1024
+ settings.targets = out;
1025
+ }
1026
+ }
1027
+
1028
+ if ("runtimePlugins" in raw) {
1029
+ const v = expectStringArray(issues, "runtimePlugins", raw.runtimePlugins);
1030
+ if (v !== undefined) settings.runtimePlugins = v;
1031
+ }
1032
+
1033
+ if ("orchestrator" in raw) {
1034
+ settings.orchestrator = validateWorkerTarget(
1035
+ issues,
1036
+ "orchestrator",
1037
+ raw.orchestrator,
1038
+ settings.orchestrator,
1039
+ settings.targets,
1040
+ );
1041
+ }
1042
+ if ("background" in raw) {
1043
+ settings.background = validateWorkerTarget(
1044
+ issues,
1045
+ "background",
1046
+ raw.background,
1047
+ settings.background,
1048
+ settings.targets,
1049
+ );
1050
+ }
1051
+ if ("memory" in raw) settings.memory = validateMemorySettings(issues, raw.memory);
1052
+
1053
+ // Fleet nodes validate before workers so profile node pins can be checked
1054
+ // against the configured node ids.
1055
+ if ("fleet" in raw) {
1056
+ settings.fleet = validateFleet(issues, raw.fleet);
1057
+ }
1058
+ const fleetNodeIds = new Set(settings.fleet.nodes.map((node) => node.id));
1059
+
1060
+ if ("routing" in raw) {
1061
+ if (!isPlainObject(raw.routing)) {
1062
+ issues.add("routing", `expected a map, got ${describe(raw.routing)}`);
1063
+ } else {
1064
+ issues.unknownKeys("routing", raw.routing, ["activeRoles", "activePostures", "agentAutomation"]);
1065
+ const activeRoles = expectStringArray(issues, "routing.activeRoles", raw.routing.activeRoles);
1066
+ if (activeRoles !== undefined) {
1067
+ const allowed = new Set<string>(ACTIVE_ROUTING_ROLES);
1068
+ const rawRoles = raw.routing.activeRoles as unknown[];
1069
+ const stringRoles = rawRoles.filter((role): role is string => typeof role === "string");
1070
+ const complete = activeRoles.length === rawRoles.length;
1071
+ for (const role of activeRoles) {
1072
+ if (!allowed.has(role)) issues.add("routing.activeRoles", `unsupported active role '${role}'`);
1073
+ }
1074
+ if (new Set(stringRoles).size !== stringRoles.length) {
1075
+ issues.add("routing.activeRoles", "expected unique roles");
1076
+ }
1077
+ if (complete && activeRoles.every((role) => allowed.has(role))) {
1078
+ settings.routing.activeRoles = activeRoles as typeof settings.routing.activeRoles;
1079
+ }
1080
+ }
1081
+ const activePostures = expectStringArray(issues, "routing.activePostures", raw.routing.activePostures);
1082
+ if (activePostures !== undefined) {
1083
+ const allowed = new Set<string>(ACTIVE_ROUTING_POSTURES);
1084
+ const rawPostures = raw.routing.activePostures as unknown[];
1085
+ const stringPostures = rawPostures.filter((posture): posture is string => typeof posture === "string");
1086
+ const complete = activePostures.length === rawPostures.length;
1087
+ for (const posture of activePostures) {
1088
+ if (!allowed.has(posture)) issues.add("routing.activePostures", `unsupported active posture '${posture}'`);
1089
+ }
1090
+ if (new Set(stringPostures).size !== stringPostures.length) {
1091
+ issues.add("routing.activePostures", "expected unique postures");
1092
+ }
1093
+ if (complete && activePostures.every((posture) => allowed.has(posture))) {
1094
+ settings.routing.activePostures = activePostures as typeof settings.routing.activePostures;
1095
+ }
1096
+ }
1097
+ if (!isPlainObject(raw.routing.agentAutomation)) {
1098
+ issues.add("routing.agentAutomation", "expected a map");
1099
+ } else {
1100
+ const automation = raw.routing.agentAutomation;
1101
+ issues.unknownKeys("routing.agentAutomation", automation, ["activeAgentRoles"]);
1102
+ if (!Array.isArray(automation.activeAgentRoles)) {
1103
+ issues.add("routing.agentAutomation.activeAgentRoles", "expected a list");
1104
+ } else {
1105
+ const pairs: typeof settings.routing.agentAutomation.activeAgentRoles = [];
1106
+ const seen = new Set<string>();
1107
+ for (const [index, value] of automation.activeAgentRoles.entries()) {
1108
+ const path = `routing.agentAutomation.activeAgentRoles[${index}]`;
1109
+ if (!isPlainObject(value)) {
1110
+ issues.add(path, "expected a map");
1111
+ continue;
1112
+ }
1113
+ issues.unknownKeys(path, value, ["agentId", "executionRole"]);
1114
+ const agentId = expectString(issues, `${path}.agentId`, value.agentId);
1115
+ const executionRole = expectEnum(
1116
+ issues,
1117
+ `${path}.executionRole`,
1118
+ value.executionRole,
1119
+ ACTIVE_AGENT_AUTOMATION_ROLES,
1120
+ );
1121
+ if (agentId === "auto") {
1122
+ issues.add(`${path}.agentId`, "'auto' is reserved for coordinator-owned agent selection");
1123
+ }
1124
+ if (agentId === undefined || agentId === "auto" || executionRole === undefined) continue;
1125
+ const key = `${agentId}\u0000${executionRole}`;
1126
+ if (seen.has(key)) {
1127
+ issues.add(path, "duplicate agent and execution-role pair");
1128
+ continue;
1129
+ }
1130
+ seen.add(key);
1131
+ pairs.push({ agentId, executionRole });
1132
+ }
1133
+ if (pairs.length === automation.activeAgentRoles.length) {
1134
+ settings.routing.agentAutomation.activeAgentRoles = pairs;
1135
+ }
1136
+ }
1137
+ }
1138
+ }
1139
+ }
1140
+
1141
+ if ("workers" in raw) {
1142
+ if (!isPlainObject(raw.workers)) {
1143
+ issues.add("workers", `expected a map, got ${describe(raw.workers)}`);
1144
+ } else {
1145
+ issues.unknownKeys("workers", raw.workers, [
1146
+ "default",
1147
+ "profiles",
1148
+ "agentBindings",
1149
+ "maxRetries",
1150
+ "onPermission",
1151
+ "escalation",
1152
+ "resilienceCooldownMs",
1153
+ ]);
1154
+ if ("default" in raw.workers) {
1155
+ settings.workers.default = validateWorkerTarget(
1156
+ issues,
1157
+ "workers.default",
1158
+ raw.workers.default,
1159
+ settings.workers.default,
1160
+ settings.targets,
1161
+ { nodeIds: fleetNodeIds },
1162
+ );
1163
+ }
1164
+ if ("profiles" in raw.workers) {
1165
+ if (!isPlainObject(raw.workers.profiles)) {
1166
+ issues.add("workers.profiles", `expected a map, got ${describe(raw.workers.profiles)}`);
1167
+ } else {
1168
+ const profiles: ClioSettings["workers"]["profiles"] = {};
1169
+ for (const [rawName, rawProfile] of Object.entries(raw.workers.profiles)) {
1170
+ const name = rawName.trim();
1171
+ if (!name) {
1172
+ issues.add("workers.profiles", "empty profile name");
1173
+ continue;
1174
+ }
1175
+ const profile = validateWorkerTarget(
1176
+ issues,
1177
+ `workers.profiles.${name}`,
1178
+ rawProfile,
1179
+ DEFAULT_SETTINGS.workers.default,
1180
+ settings.targets,
1181
+ { nodeIds: fleetNodeIds },
1182
+ );
1183
+ if (!profile.target) continue;
1184
+ profiles[name] = profile;
1185
+ }
1186
+ settings.workers.profiles = profiles;
1187
+ }
1188
+ }
1189
+ if ("agentBindings" in raw.workers) {
1190
+ if (!isPlainObject(raw.workers.agentBindings)) {
1191
+ issues.add("workers.agentBindings", `expected a map, got ${describe(raw.workers.agentBindings)}`);
1192
+ } else {
1193
+ const agentBindings: ClioSettings["workers"]["agentBindings"] = {};
1194
+ for (const [rawAgentId, rawProfileName] of Object.entries(raw.workers.agentBindings)) {
1195
+ const agentId = rawAgentId.trim();
1196
+ if (!agentId) {
1197
+ issues.add("workers.agentBindings", "empty agent id");
1198
+ continue;
1199
+ }
1200
+ if (agentId === "auto") {
1201
+ issues.add("workers.agentBindings", "'auto' is reserved for coordinator-owned agent selection");
1202
+ continue;
1203
+ }
1204
+ const profileName = expectString(issues, `workers.agentBindings.${agentId}`, rawProfileName);
1205
+ if (profileName !== undefined) agentBindings[agentId] = profileName;
1206
+ }
1207
+ settings.workers.agentBindings = agentBindings;
1208
+ }
1209
+ }
1210
+ if ("maxRetries" in raw.workers) {
1211
+ const v = expectInteger(issues, "workers.maxRetries", raw.workers.maxRetries, { min: 0 });
1212
+ if (v !== undefined) settings.workers.maxRetries = v;
1213
+ }
1214
+ if ("onPermission" in raw.workers) {
1215
+ const v = expectEnum(issues, "workers.onPermission", raw.workers.onPermission, [
1216
+ "deny",
1217
+ "fail",
1218
+ "escalate",
1219
+ ] as const);
1220
+ if (v !== undefined) settings.workers.onPermission = v;
1221
+ }
1222
+ if ("escalation" in raw.workers) {
1223
+ if (!isPlainObject(raw.workers.escalation)) {
1224
+ issues.add("workers.escalation", `expected a map, got ${describe(raw.workers.escalation)}`);
1225
+ } else {
1226
+ const base = settings.workers.escalation ?? DEFAULT_SETTINGS.workers.escalation;
1227
+ const escalation = { timeoutMs: base?.timeoutMs ?? 120000, fallback: base?.fallback ?? "deny" };
1228
+ issues.unknownKeys("workers.escalation", raw.workers.escalation, ["timeoutMs", "fallback"]);
1229
+ if ("timeoutMs" in raw.workers.escalation) {
1230
+ const v = expectInteger(issues, "workers.escalation.timeoutMs", raw.workers.escalation.timeoutMs, {
1231
+ min: 1,
1232
+ });
1233
+ if (v !== undefined) escalation.timeoutMs = v;
1234
+ }
1235
+ if ("fallback" in raw.workers.escalation) {
1236
+ const v = expectEnum(issues, "workers.escalation.fallback", raw.workers.escalation.fallback, [
1237
+ "deny",
1238
+ "fail",
1239
+ ] as const);
1240
+ if (v !== undefined) escalation.fallback = v;
1241
+ }
1242
+ settings.workers.escalation = escalation;
1243
+ }
1244
+ }
1245
+ if ("resilienceCooldownMs" in raw.workers) {
1246
+ const v = expectInteger(issues, "workers.resilienceCooldownMs", raw.workers.resilienceCooldownMs, { min: 0 });
1247
+ if (v !== undefined) settings.workers.resilienceCooldownMs = v;
1248
+ }
1249
+ }
1250
+ }
1251
+
1252
+ if ("scope" in raw) {
1253
+ const v = expectStringArray(issues, "scope", raw.scope);
1254
+ if (v !== undefined) settings.scope = normalizeScope(v);
1255
+ }
1256
+
1257
+ if ("modelSelector" in raw) {
1258
+ if (!isPlainObject(raw.modelSelector)) {
1259
+ issues.add("modelSelector", `expected a map, got ${describe(raw.modelSelector)}`);
1260
+ } else {
1261
+ issues.unknownKeys("modelSelector", raw.modelSelector, ["favorites", "recentLimit"]);
1262
+ if ("favorites" in raw.modelSelector) {
1263
+ const v = expectStringArray(issues, "modelSelector.favorites", raw.modelSelector.favorites);
1264
+ if (v !== undefined) settings.modelSelector.favorites = normalizeModelRefs(v, settings.targets);
1265
+ }
1266
+ if ("recentLimit" in raw.modelSelector) {
1267
+ const v = expectInteger(issues, "modelSelector.recentLimit", raw.modelSelector.recentLimit, { min: 1 });
1268
+ if (v !== undefined) settings.modelSelector.recentLimit = v;
1269
+ }
1270
+ }
1271
+ }
1272
+
1273
+ if ("budget" in raw) {
1274
+ if (!isPlainObject(raw.budget)) {
1275
+ issues.add("budget", `expected a map, got ${describe(raw.budget)}`);
1276
+ } else {
1277
+ issues.unknownKeys("budget", raw.budget, ["sessionCeilingUsd", "concurrency"]);
1278
+ if ("sessionCeilingUsd" in raw.budget) {
1279
+ const v = expectNumber(issues, "budget.sessionCeilingUsd", raw.budget.sessionCeilingUsd, { min: 0 });
1280
+ if (v !== undefined) settings.budget.sessionCeilingUsd = v;
1281
+ }
1282
+ if ("concurrency" in raw.budget) {
1283
+ if (raw.budget.concurrency === "auto") {
1284
+ settings.budget.concurrency = "auto";
1285
+ } else {
1286
+ const v = expectInteger(issues, "budget.concurrency", raw.budget.concurrency, { min: 1 });
1287
+ if (v !== undefined) settings.budget.concurrency = v;
1288
+ }
1289
+ }
1290
+ }
1291
+ }
1292
+
1293
+ if ("defaults" in raw) {
1294
+ if (!isPlainObject(raw.defaults)) {
1295
+ issues.add("defaults", `expected a map, got ${describe(raw.defaults)}`);
1296
+ } else {
1297
+ issues.unknownKeys("defaults", raw.defaults, ["maxTokens"]);
1298
+ if ("maxTokens" in raw.defaults) {
1299
+ const v = expectInteger(issues, "defaults.maxTokens", raw.defaults.maxTokens, { min: 0 });
1300
+ if (v !== undefined) settings.defaults.maxTokens = v;
1301
+ }
1302
+ }
1303
+ }
1304
+
1305
+ if ("theme" in raw) {
1306
+ const v = expectString(issues, "theme", raw.theme);
1307
+ if (v !== undefined) settings.theme = v;
1308
+ }
1309
+
1310
+ if ("terminal" in raw) {
1311
+ if (!isPlainObject(raw.terminal)) {
1312
+ issues.add("terminal", `expected a map, got ${describe(raw.terminal)}`);
1313
+ } else {
1314
+ issues.unknownKeys("terminal", raw.terminal, [
1315
+ "showTerminalProgress",
1316
+ "outputVerbosity",
1317
+ "tuiMode",
1318
+ "fullscreenScrollbar",
1319
+ "smoothStreaming",
1320
+ ]);
1321
+ if ("showTerminalProgress" in raw.terminal) {
1322
+ const v = expectBoolean(issues, "terminal.showTerminalProgress", raw.terminal.showTerminalProgress);
1323
+ if (v !== undefined) settings.terminal.showTerminalProgress = v;
1324
+ }
1325
+ if ("outputVerbosity" in raw.terminal) {
1326
+ const v = expectString(issues, "terminal.outputVerbosity", raw.terminal.outputVerbosity);
1327
+ if (v === "minimal" || v === "default" || v === "verbose") settings.terminal.outputVerbosity = v;
1328
+ else if (v !== undefined) issues.add("terminal.outputVerbosity", "expected minimal, default, or verbose");
1329
+ }
1330
+ if ("tuiMode" in raw.terminal) {
1331
+ const v = expectString(issues, "terminal.tuiMode", raw.terminal.tuiMode);
1332
+ if (v === "regular" || v === "fullscreen") settings.terminal.tuiMode = v;
1333
+ else if (v !== undefined) issues.add("terminal.tuiMode", "expected regular or fullscreen");
1334
+ }
1335
+ if ("fullscreenScrollbar" in raw.terminal) {
1336
+ const v = expectString(issues, "terminal.fullscreenScrollbar", raw.terminal.fullscreenScrollbar);
1337
+ if (v === "hidden" || v === "auto" || v === "always") settings.terminal.fullscreenScrollbar = v;
1338
+ else if (v !== undefined) issues.add("terminal.fullscreenScrollbar", "expected hidden, auto, or always");
1339
+ }
1340
+ if ("smoothStreaming" in raw.terminal) {
1341
+ const v = expectString(issues, "terminal.smoothStreaming", raw.terminal.smoothStreaming);
1342
+ if (v === "off" || v === "auto" || v === "on") settings.terminal.smoothStreaming = v;
1343
+ else if (v !== undefined) issues.add("terminal.smoothStreaming", "expected off, auto, or on");
1344
+ }
1345
+ }
1346
+ }
1347
+
1348
+ if ("skills" in raw) {
1349
+ if (!isPlainObject(raw.skills)) {
1350
+ issues.add("skills", `expected a map, got ${describe(raw.skills)}`);
1351
+ } else {
1352
+ issues.unknownKeys("skills", raw.skills, ["trustProjectCompatRoots"]);
1353
+ if ("trustProjectCompatRoots" in raw.skills) {
1354
+ const v = expectBoolean(issues, "skills.trustProjectCompatRoots", raw.skills.trustProjectCompatRoots);
1355
+ if (v !== undefined) settings.skills.trustProjectCompatRoots = v;
1356
+ }
1357
+ }
1358
+ }
1359
+
1360
+ if ("attribution" in raw) {
1361
+ if (!isPlainObject(raw.attribution)) {
1362
+ issues.add("attribution", `expected a map, got ${describe(raw.attribution)}`);
1363
+ } else {
1364
+ issues.unknownKeys("attribution", raw.attribution, ["gitCommits"]);
1365
+ if ("gitCommits" in raw.attribution) {
1366
+ const v = expectBoolean(issues, "attribution.gitCommits", raw.attribution.gitCommits);
1367
+ if (v !== undefined) settings.attribution.gitCommits = v;
1368
+ }
1369
+ }
1370
+ }
1371
+
1372
+ if ("delegation" in raw) {
1373
+ settings.delegation = validateDelegation(issues, raw.delegation);
1374
+ }
1375
+
1376
+ if ("keybindings" in raw) {
1377
+ settings.keybindings = validateKeybindings(issues, raw.keybindings);
1378
+ }
1379
+
1380
+ if ("compaction" in raw) {
1381
+ if (!isPlainObject(raw.compaction)) {
1382
+ issues.add("compaction", `expected a map, got ${describe(raw.compaction)}`);
1383
+ } else {
1384
+ issues.unknownKeys("compaction", raw.compaction, ["auto", "threshold", "excludeLastTurns", "model", "systemPrompt"]);
1385
+ if ("auto" in raw.compaction) {
1386
+ const v = expectBoolean(issues, "compaction.auto", raw.compaction.auto);
1387
+ if (v !== undefined) settings.compaction.auto = v;
1388
+ }
1389
+ if ("threshold" in raw.compaction) {
1390
+ const v = expectNumber(issues, "compaction.threshold", raw.compaction.threshold, { min: 0, max: 1 });
1391
+ if (v !== undefined) settings.compaction.threshold = v;
1392
+ }
1393
+ if ("excludeLastTurns" in raw.compaction) {
1394
+ const v = expectInteger(issues, "compaction.excludeLastTurns", raw.compaction.excludeLastTurns, { min: 1 });
1395
+ if (v !== undefined) settings.compaction.excludeLastTurns = v;
1396
+ }
1397
+ if ("model" in raw.compaction) {
1398
+ const v = expectString(issues, "compaction.model", raw.compaction.model);
1399
+ if (v !== undefined) settings.compaction.model = v;
1400
+ }
1401
+ if ("systemPrompt" in raw.compaction) {
1402
+ const v = expectString(issues, "compaction.systemPrompt", raw.compaction.systemPrompt);
1403
+ if (v !== undefined) settings.compaction.systemPrompt = v;
1404
+ }
1405
+ }
1406
+ }
1407
+
1408
+ if ("retry" in raw) {
1409
+ if (!isPlainObject(raw.retry)) {
1410
+ issues.add("retry", `expected a map, got ${describe(raw.retry)}`);
1411
+ } else {
1412
+ issues.unknownKeys("retry", raw.retry, ["enabled", "maxRetries", "baseDelayMs", "maxDelayMs", "streamStallMs"]);
1413
+ if ("enabled" in raw.retry) {
1414
+ const v = expectBoolean(issues, "retry.enabled", raw.retry.enabled);
1415
+ if (v !== undefined) settings.retry.enabled = v;
1416
+ }
1417
+ for (const key of ["maxRetries", "baseDelayMs", "maxDelayMs", "streamStallMs"] as const) {
1418
+ if (key in raw.retry) {
1419
+ const v = expectInteger(issues, `retry.${key}`, raw.retry[key], { min: 0 });
1420
+ if (v !== undefined) settings.retry[key] = v;
1421
+ }
1422
+ }
1423
+ }
1424
+ }
1425
+
1426
+ if ("guardrails" in raw) {
1427
+ if (!isPlainObject(raw.guardrails)) {
1428
+ issues.add("guardrails", `expected a map, got ${describe(raw.guardrails)}`);
1429
+ } else {
1430
+ const keys = [
1431
+ "turnToolCallBudget",
1432
+ "workerToolCallCap",
1433
+ "maxDispatchRuns",
1434
+ "readMaxBytes",
1435
+ "observationTurnBudgetBytes",
1436
+ "internalDispatchTimeoutMs",
1437
+ ] as const;
1438
+ issues.unknownKeys("guardrails", raw.guardrails, keys);
1439
+ for (const key of keys) {
1440
+ if (key in raw.guardrails) {
1441
+ const v = expectInteger(issues, `guardrails.${key}`, raw.guardrails[key], { min: 1 });
1442
+ if (v !== undefined) settings.guardrails[key] = v;
1443
+ }
1444
+ }
1445
+ }
1446
+ }
1447
+
1448
+ return { settings, issues: issues.list };
1449
+ }
1450
+
1451
+ /**
1452
+ * Validate the settings file on disk without throwing. Missing file is valid.
1453
+ * Resolves the path without the clioConfigDir mkdir side effect so read-only
1454
+ * surfaces (plain `clio-coder doctor`, readSettings on a fresh machine) never
1455
+ * create directories.
1456
+ */
1457
+ export function validateSettingsFile(): SettingsValidationResult {
1458
+ const path = join(resolveClioDirs().config, "settings.yaml");
1459
+ if (!existsSync(path)) return { settings: cloneValue(DEFAULT_SETTINGS), issues: [] };
1460
+ // The read and the parse are separate steps because they fail for separate
1461
+ // reasons: folding them into one try reported `chmod 000` as invalid YAML,
1462
+ // which is a false statement about the file and points at the wrong repair.
1463
+ let text: string;
1464
+ try {
1465
+ text = readFileSync(path, "utf8");
1466
+ } catch (err) {
1467
+ return {
1468
+ settings: cloneValue(DEFAULT_SETTINGS),
1469
+ issues: [
1470
+ { path: "(root)", message: `unreadable: ${err instanceof Error ? err.message : String(err)}`, kind: "unreadable" },
1471
+ ],
1472
+ };
1473
+ }
1474
+ let parsed: unknown;
1475
+ try {
1476
+ parsed = parseYaml(text);
1477
+ } catch (err) {
1478
+ return {
1479
+ settings: cloneValue(DEFAULT_SETTINGS),
1480
+ issues: [{ path: "(root)", message: `invalid YAML: ${yamlErrorSummary(err)}`, kind: "syntax" }],
1481
+ };
1482
+ }
1483
+ return validateSettings(parsed);
1484
+ }
1485
+
1486
+ export function readSettings(): ClioSettings {
1487
+ const result = validateSettingsFile();
1488
+ if (result.issues.length > 0) throw new SettingsValidationError(result.issues);
1489
+ return result.settings;
1490
+ }
1491
+
1492
+ /**
1493
+ * Whole-file settings write via temp-file + rename. The rename is atomic on
1494
+ * POSIX, so a concurrent readSettings never observes a partially written
1495
+ * YAML document and readers never need the settings lock. Module-internal:
1496
+ * every mutation goes through updateSettings so there is exactly one writer
1497
+ * path and it always holds the lock.
1498
+ */
1499
+ function persistSettings(document: unknown): void {
1500
+ safeResourceWrite(settingsPath(), stringifyYaml(document), { encoding: "utf8", mode: 0o644 });
1501
+ }
1502
+
1503
+ /**
1504
+ * The saved document exactly as YAML parsed it, before defaults are merged in.
1505
+ * updateSettings patches this instead of the normalized blob, so the file only
1506
+ * ever gains what a mutation actually touched.
1507
+ */
1508
+ function readSavedDocument(): unknown {
1509
+ const path = settingsPath();
1510
+ if (!existsSync(path)) return {};
1511
+ try {
1512
+ return parseYaml(readFileSync(path, "utf8")) ?? {};
1513
+ } catch {
1514
+ return {};
1515
+ }
1516
+ }
1517
+
1518
+ /**
1519
+ * Write the `before` → `after` delta of two normalized settings blobs onto the
1520
+ * raw saved document. Keys the mutation did not change keep their saved form
1521
+ * (or stay absent), so materialized defaults such as `workers.profiles: {}`
1522
+ * never leak into a file that never had them.
1523
+ */
1524
+ export function applySettingsDelta(saved: unknown, before: unknown, after: unknown): unknown {
1525
+ if (!isPlainObject(before) || !isPlainObject(after)) return cloneValue(after);
1526
+ const out: Record<string, unknown> = isPlainObject(saved) ? { ...saved } : {};
1527
+ for (const [key, next] of Object.entries(after)) {
1528
+ // Untouched keys keep whatever the file said, including saying nothing.
1529
+ if (deepEquals(before[key], next)) continue;
1530
+ out[key] = applySettingsDelta(out[key], before[key], next);
1531
+ }
1532
+ // A key the mutation removed from a map (a deleted worker profile) goes too.
1533
+ for (const key of Object.keys(out)) {
1534
+ if (!(key in after)) delete out[key];
1535
+ }
1536
+ return out;
1537
+ }
1538
+
1539
+ function deepEquals(a: unknown, b: unknown): boolean {
1540
+ if (a === b) return true;
1541
+ if (Array.isArray(a) || Array.isArray(b)) {
1542
+ if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
1543
+ return a.every((entry, index) => deepEquals(entry, b[index]));
1544
+ }
1545
+ if (!isPlainObject(a) || !isPlainObject(b)) return false;
1546
+ const keys = Object.keys(a);
1547
+ if (keys.length !== Object.keys(b).length) return false;
1548
+ return keys.every((key) => key in b && deepEquals(a[key], b[key]));
1549
+ }
1550
+
1551
+ /**
1552
+ * Mutation applied to the freshest saved settings under the settings lock.
1553
+ * Mutate in place (return nothing) or return a replacement blob.
1554
+ */
1555
+ // biome-ignore lint/suspicious/noConfusingVoidType: in-place mutators legitimately return nothing, including named functions typed `: void`
1556
+ export type SettingsMutator = (settings: ClioSettings) => ClioSettings | undefined | void;
1557
+
1558
+ export interface SettingsUpdateOptions {
1559
+ /** Total time to wait for the lock before giving up. Default 10s. */
1560
+ timeoutMs?: number;
1561
+ }
1562
+
1563
+ /**
1564
+ * Replace the raw user settings document under the same cross-process lock as
1565
+ * `updateSettings`. Workspace-aware layering owns the only current caller: it
1566
+ * computes and validates the candidate against the project layers before this
1567
+ * helper commits the raw user-layer delta.
1568
+ */
1569
+ export function updateSavedSettingsDocument(
1570
+ mutate: (saved: unknown) => unknown,
1571
+ options: SettingsUpdateOptions = {},
1572
+ ): void {
1573
+ withSettingsLock(() => {
1574
+ // Keep the strict gate updateSettings has always used. readSavedDocument
1575
+ // deliberately degrades parse/read failures to `{}` for recovery callers;
1576
+ // writing through that degraded value would erase a malformed, null, or
1577
+ // transiently unreadable operator document.
1578
+ readSettings();
1579
+ const saved = readSavedDocument();
1580
+ persistSettings(mutate(cloneValue(saved)));
1581
+ }, options);
1582
+ }
1583
+
1584
+ export function settingsLockPath(): string {
1585
+ return `${settingsPath()}.lock`;
1586
+ }
1587
+
1588
+ /**
1589
+ * Run `fn` while holding the settings.yaml advisory lock. Factored out of
1590
+ * updateSettings so the one other sanctioned settings writer (lifecycle
1591
+ * migrations, which rewrite pre-schema files the strict reader would reject)
1592
+ * holds the same lock instead of racing updateSettings.
1593
+ */
1594
+ export function withSettingsLock<T>(fn: () => T, options: SettingsUpdateOptions = {}): T {
1595
+ return withStateFileLockSync(settingsPath(), fn, { timeoutMs: options.timeoutMs ?? 10_000 });
1596
+ }
1597
+
1598
+ /**
1599
+ * Cross-process read-modify-write of settings.yaml under an advisory lock
1600
+ * file. Two processes doing naive read → mutate → write can interleave and
1601
+ * silently drop one of the writes; this helper re-reads the file *inside*
1602
+ * the lock, so the mutation always lands on the freshest saved state.
1603
+ * Readers never touch the lock; they only ever see complete files thanks to
1604
+ * the rename-based writer.
1605
+ *
1606
+ * The mutator may modify the settings in place or return a replacement blob.
1607
+ * The result is re-validated through the schema before it is persisted, so a
1608
+ * mutator cannot write an invalid document. Returns the persisted settings.
1609
+ */
1610
+ export function updateSettings(mutate: SettingsMutator, options: SettingsUpdateOptions = {}): ClioSettings {
1611
+ return withSettingsLock(() => {
1612
+ const saved = readSavedDocument();
1613
+ const current = readSettings();
1614
+ // Snapshot before the mutator runs: in-place mutators alias `current`.
1615
+ const before = cloneValue(current);
1616
+ const next = mutate(current) ?? current;
1617
+ const revalidated = validateSettings(JSON.parse(JSON.stringify(next)));
1618
+ if (revalidated.issues.length > 0) throw new SettingsValidationError(revalidated.issues);
1619
+ persistSettings(applySettingsDelta(saved, before, revalidated.settings));
1620
+ return revalidated.settings;
1621
+ }, options);
1622
+ }
1623
+
1624
+ /**
1625
+ * Settings mutations shared by the `clio-coder targets …` CLI (inside
1626
+ * updateSettings) and the settings overlay's Fleet and Targets rows (on the
1627
+ * effective view, committed per leaf). Registry-backed eligibility checks stay with the caller.
1628
+ */
1629
+ export interface UseTargetOptions {
1630
+ model?: string;
1631
+ orchestratorModel?: string;
1632
+ workerModel?: string;
1633
+ workerTargetId?: string;
1634
+ backgroundModel?: string;
1635
+ }
1636
+
1637
+ /** Route chat (and fleet dispatch, unless split) to `targetId`. Null when a named target is missing. */
1638
+ export function useTargetInSettings(
1639
+ settings: ClioSettings,
1640
+ targetId: string,
1641
+ options: UseTargetOptions = {},
1642
+ ): { workerTargetId: string } | null {
1643
+ const target = settings.targets.find((entry) => entry.id === targetId);
1644
+ if (!target) return null;
1645
+ // A worker target of its own is the split topology; its model defaults to that node's own default.
1646
+ const workerTarget =
1647
+ options.workerTargetId !== undefined && options.workerTargetId !== target.id
1648
+ ? settings.targets.find((entry) => entry.id === options.workerTargetId)
1649
+ : target;
1650
+ if (!workerTarget) return null;
1651
+ const sharedModel = options.model ?? target.defaultModel ?? null;
1652
+ settings.orchestrator.target = target.id;
1653
+ settings.orchestrator.model = options.orchestratorModel ?? sharedModel;
1654
+ settings.workers.default.target = workerTarget.id;
1655
+ settings.workers.default.model =
1656
+ options.workerModel ?? (workerTarget === target ? sharedModel : (workerTarget.defaultModel ?? null));
1657
+ if (options.backgroundModel !== undefined) {
1658
+ settings.background.target = target.id;
1659
+ settings.background.model = options.backgroundModel;
1660
+ }
1661
+ return { workerTargetId: workerTarget.id };
1662
+ }
1663
+
1664
+ /** Drop a target and every routing field, profile, and scope entry that named it. */
1665
+ export function removeTargetFromSettings(settings: ClioSettings, id: string): boolean {
1666
+ if (!settings.targets.some((entry) => entry.id === id)) return false;
1667
+ settings.targets = settings.targets.filter((entry) => entry.id !== id);
1668
+ if (settings.orchestrator.target === id) {
1669
+ settings.orchestrator.target = null;
1670
+ settings.orchestrator.model = null;
1671
+ }
1672
+ if (settings.background.target === id) {
1673
+ settings.background.target = null;
1674
+ settings.background.model = null;
1675
+ }
1676
+ if (settings.workers.default.target === id) {
1677
+ settings.workers.default.target = null;
1678
+ settings.workers.default.model = null;
1679
+ }
1680
+ for (const [name, profile] of Object.entries(settings.workers.profiles)) {
1681
+ if (profile.target === id) delete settings.workers.profiles[name];
1682
+ }
1683
+ settings.scope = settings.scope.filter((entry) => entry.split("/")[0] !== id);
1684
+ return true;
1685
+ }
1686
+
1687
+ /** Create or repoint a fleet profile; the model rebases on the target default unless given. */
1688
+ export function setFleetProfileInSettings(
1689
+ settings: ClioSettings,
1690
+ name: string,
1691
+ targetId: string,
1692
+ options: { model?: string; thinkingLevel?: ThinkingLevel } = {},
1693
+ ): boolean {
1694
+ const target = settings.targets.find((entry) => entry.id === targetId);
1695
+ if (!target) return false;
1696
+ const existing = settings.workers.profiles[name];
1697
+ settings.workers.profiles[name] = {
1698
+ target: target.id,
1699
+ model: options.model ?? target.defaultModel ?? null,
1700
+ thinkingLevel: options.thinkingLevel ?? existing?.thinkingLevel ?? "off",
1701
+ };
1702
+ return true;
1703
+ }
1704
+
1705
+ /** Remove a fleet profile and its agent bindings; returns how many bindings went with it. */
1706
+ export function removeFleetProfileFromSettings(settings: ClioSettings, name: string): number {
1707
+ delete settings.workers.profiles[name];
1708
+ let removedBindings = 0;
1709
+ for (const [agentId, profileName] of Object.entries(settings.workers.agentBindings)) {
1710
+ if (profileName !== name) continue;
1711
+ delete settings.workers.agentBindings[agentId];
1712
+ removedBindings += 1;
1713
+ }
1714
+ return removedBindings;
1715
+ }
1716
+
1717
+ /** Bind a native agent to a profile. False for an ACP delegation agent, which ignores native routing. */
1718
+ export function bindAgentProfileInSettings(settings: ClioSettings, agentId: string, profileName: string): boolean {
1719
+ if (settings.delegation.agents.some((agent) => agent.id === agentId)) return false;
1720
+ settings.workers.agentBindings[agentId] = profileName;
1721
+ return true;
1722
+ }