@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.
- package/CHANGELOG.md +123 -2
- package/CONTRIBUTING.md +23 -23
- package/README.md +295 -627
- package/dist/acp-BIYHVZIM.js +128 -0
- package/dist/{agents-Q4MYPMUW.js → agents-YT6SSRIT.js} +52 -27
- package/dist/assets/codewiki.json +1 -0
- package/dist/assets/grammars/notices/@vscode__tree-sitter-wasm/LICENSE +21 -0
- package/dist/assets/grammars/notices/@vscode__tree-sitter-wasm/cgmanifest.json +16 -0
- package/dist/assets/grammars/notices/tree-sitter-wasms/LICENSE +24 -0
- package/dist/assets/grammars/tree-sitter-c-sharp.wasm +0 -0
- package/dist/assets/grammars/tree-sitter-c.wasm +0 -0
- package/dist/assets/grammars/tree-sitter-cpp.wasm +0 -0
- package/dist/assets/grammars/tree-sitter-go.wasm +0 -0
- package/dist/assets/grammars/tree-sitter-java.wasm +0 -0
- package/dist/assets/grammars/tree-sitter-javascript.wasm +0 -0
- package/dist/assets/grammars/tree-sitter-python.wasm +0 -0
- package/dist/assets/grammars/tree-sitter-ruby.wasm +0 -0
- package/dist/assets/grammars/tree-sitter-rust.wasm +0 -0
- package/dist/assets/grammars/tree-sitter-tsx.wasm +0 -0
- package/dist/assets/grammars/tree-sitter-typescript.wasm +0 -0
- package/dist/assets/grammars/tree-sitter.wasm +0 -0
- package/dist/{auth-O6HYIJ6J.js → auth-5TWEIYDN.js} +30 -15
- package/dist/chunk-2EHAIA3X.js +481 -0
- package/dist/{chunk-ME6DNWIU.js → chunk-2IR2NMPA.js} +12 -4
- package/dist/chunk-2SFS6XQE.js +122 -0
- package/dist/{chunk-KKKPTZLM.js → chunk-2STGEDFV.js} +8 -1
- package/dist/chunk-2VTFPG5O.js +48 -0
- package/dist/chunk-3I7MS7N2.js +40 -0
- package/dist/chunk-3R73A4XB.js +69 -0
- package/dist/{chunk-JPOWPFCU.js → chunk-3ZXDFGR5.js} +52 -23
- package/dist/chunk-4BJ5BYCE.js +61 -0
- package/dist/{chunk-26BZQOAD.js → chunk-4BPJXDWC.js} +161 -260
- package/dist/chunk-4KLWL3UC.js +18 -0
- package/dist/{chunk-5LDRNKX2.js → chunk-4VP4KH3K.js} +161 -262
- package/dist/chunk-4ZG3XFUR.js +77 -0
- package/dist/chunk-5B2AEOW5.js +5407 -0
- package/dist/chunk-5TSRNF4G.js +510 -0
- package/dist/{chunk-WD6AJM35.js → chunk-5UUP6MWO.js} +48 -69
- package/dist/{chunk-X3BR7HWV.js → chunk-5WIGXA4T.js} +7 -1
- package/dist/chunk-65DEGPJ6.js +52 -0
- package/dist/chunk-6EJMN2Y3.js +17 -0
- package/dist/chunk-6EJV5X2W.js +16405 -0
- package/dist/chunk-6N5PTWMY.js +136 -0
- package/dist/chunk-6XLNIQDB.js +27 -0
- package/dist/{chunk-XNISANGE.js → chunk-77VKQEHF.js} +171 -74
- package/dist/chunk-7CR24IG7.js +242 -0
- package/dist/{chunk-UWIJNAOB.js → chunk-7EYHLWU7.js} +907 -638
- package/dist/chunk-7MNJORFF.js +22 -0
- package/dist/{chunk-JWHCJDCI.js → chunk-A3CYT5EX.js} +257 -79
- package/dist/chunk-AGYYIBLL.js +1069 -0
- package/dist/chunk-AO4RKG4M.js +277 -0
- package/dist/chunk-APJ265NV.js +7410 -0
- package/dist/chunk-ARBGF5F7.js +174 -0
- package/dist/{chunk-TFSSEXL6.js → chunk-BMEMKKIT.js} +14 -4
- package/dist/{chunk-X3NE4WVW.js → chunk-BNAZZHFG.js} +7 -1
- package/dist/chunk-CBCAPZAA.js +229 -0
- package/dist/{chunk-QV47YRF4.js → chunk-CGKSTWHD.js} +7 -1
- package/dist/chunk-CMZWFGD2.js +352 -0
- package/dist/chunk-ECH6PKUQ.js +39 -0
- package/dist/{chunk-67MTHP2E.js → chunk-ED4KHGC3.js} +13 -5
- package/dist/chunk-EKMEHE4H.js +340 -0
- package/dist/{chunk-O4EJEDHO.js → chunk-EPVUXGXG.js} +50 -35
- package/dist/chunk-FCSXB6T2.js +338 -0
- package/dist/chunk-FJ3H4MN5.js +48 -0
- package/dist/chunk-FQ4SKYE4.js +29 -0
- package/dist/chunk-G2DE3C7R.js +644 -0
- package/dist/chunk-G4BMMOKF.js +182 -0
- package/dist/{chunk-7BHLZB3A.js → chunk-GGXXDWE4.js} +190 -1218
- package/dist/chunk-HC4CLZ2Y.js +68 -0
- package/dist/{chunk-E3NYWENM.js → chunk-HFSBBKSQ.js} +55 -61
- package/dist/chunk-HKIYEGME.js +30 -0
- package/dist/chunk-I4HZDVNP.js +73 -0
- package/dist/chunk-IGLFWIYI.js +161 -0
- package/dist/chunk-IHXBNWMM.js +137 -0
- package/dist/chunk-IKCO5N3L.js +162 -0
- package/dist/chunk-IR4CFBFN.js +56 -0
- package/dist/{chunk-DXQNI4PC.js → chunk-IWHMRKLL.js} +8 -1
- package/dist/{chunk-VOO7NYPP.js → chunk-J5HN4RYU.js} +27 -9
- package/dist/{chunk-4UQA7NCT.js → chunk-J5Q24KAG.js} +7 -2
- package/dist/{chunk-KBR4MZZR.js → chunk-JVCV3ICN.js} +7 -1
- package/dist/chunk-KJ5LWLOE.js +1077 -0
- package/dist/chunk-LBMZMYH2.js +285 -0
- package/dist/{chunk-3DANZDGR.js → chunk-LBNRH5WM.js} +100 -161
- package/dist/{chunk-TKHQ4DGZ.js → chunk-LZSJBIVT.js} +7358 -7414
- package/dist/{chunk-UGYAX5YI.js → chunk-M6SHUN7Q.js} +13 -8
- package/dist/chunk-MAW544W2.js +1882 -0
- package/dist/chunk-MBS4V7ZP.js +217 -0
- package/dist/{chunk-2J63S4SF.js → chunk-MFFY33HR.js} +167 -140
- package/dist/chunk-MNA4JGU4.js +255 -0
- package/dist/chunk-MQSRRFWA.js +3428 -0
- package/dist/{chunk-RQDWMVRB.js → chunk-MVVUPGPW.js} +138 -137
- package/dist/chunk-OAO4GE4M.js +619 -0
- package/dist/chunk-OHHN2SO4.js +5135 -0
- package/dist/chunk-OKGUZO2U.js +34 -0
- package/dist/{chunk-BBUH4VAA.js → chunk-OOJYHWRB.js} +60 -361
- package/dist/{chunk-D5YD55AR.js → chunk-OQ33BKR3.js} +12 -1
- package/dist/chunk-OQE5J4C6.js +73 -0
- package/dist/{chunk-AMFR5YA3.js → chunk-ORBHGJC5.js} +26 -12
- package/dist/chunk-POHLU5DW.js +1186 -0
- package/dist/chunk-QKMUKYO7.js +4961 -0
- package/dist/{chunk-7RBKDI66.js → chunk-QTYWRVRA.js} +104 -12
- package/dist/{chunk-5N2FG33Q.js → chunk-R346GLFC.js} +7 -1
- package/dist/chunk-SRF2PJNW.js +184 -0
- package/dist/chunk-SST6Z5JA.js +80 -0
- package/dist/{chunk-XV4ZJ6ZM.js → chunk-STBPMHSX.js} +473 -1194
- package/dist/{chunk-HBU5MTAM.js → chunk-SVNXH4L2.js} +7 -1
- package/dist/chunk-T6YILFSB.js +80 -0
- package/dist/chunk-TXOTCRLG.js +15 -0
- package/dist/chunk-TZTZS7QK.js +227 -0
- package/dist/chunk-UOV2BYIW.js +107 -0
- package/dist/{chunk-527KG6XR.js → chunk-V4RXGQ5Q.js} +72 -185
- package/dist/chunk-VAKQQHWR.js +434 -0
- package/dist/chunk-VG7TBQIY.js +128 -0
- package/dist/chunk-VJWL6YS5.js +244 -0
- package/dist/chunk-WEH5XRJQ.js +32 -0
- package/dist/{chunk-VPAWTYLY.js → chunk-WEPFGWHJ.js} +7 -1
- package/dist/chunk-WMSVI4G2.js +2095 -0
- package/dist/chunk-WVO7V2QY.js +797 -0
- package/dist/chunk-X4RCMKVQ.js +641 -0
- package/dist/chunk-X75S7HFS.js +374 -0
- package/dist/chunk-XN3L4EYL.js +46 -0
- package/dist/{chunk-HMYNFFY4.js → chunk-XPLRXC72.js} +7 -2
- package/dist/{chunk-PIDUD6M2.js → chunk-YCWGATWI.js} +12 -5
- package/dist/chunk-YHZX5GEU.js +193 -0
- package/dist/chunk-YXLYO42X.js +91 -0
- package/dist/{chunk-UHTSULZS.js → chunk-ZDOOVTXZ.js} +38 -73
- package/dist/chunk-ZI647VB5.js +37 -0
- package/dist/{chunk-CLJ5HLUD.js → chunk-ZWLZP4ZT.js} +25 -6
- package/dist/cli/index.js +70 -56
- package/dist/clio-4LY5K2AC.js +25 -0
- package/dist/code-nav-7AX6FYE6.js +600 -0
- package/dist/codewiki/build-worker.js +66 -0
- package/dist/compile-cache-CVJMMODC.js +18 -0
- package/dist/{components-JVHMUBEB.js → components-KELWS457.js} +31 -9
- package/dist/{config-ZFCDBMDC.js → config-GTLUW2PR.js} +104 -56
- package/dist/configure-R6A64DHX.js +42 -0
- package/dist/context-5VKGUVJJ.js +866 -0
- package/dist/{context-CDXTP2MP.js → context-JFZEJ7W5.js} +21 -13
- package/dist/{context-E3KIFVXI.js → context-RW5HC47S.js} +77 -36
- package/dist/{context-clear-3F4PLXOS.js → context-clear-6ZHBAZZT.js} +64 -30
- package/dist/{context-index-Q7YSYTR3.js → context-index-BZ4UYMTC.js} +36 -24
- package/dist/dispatch-runner-VKBRCWQC.js +1997 -0
- package/dist/{docs-YIETIWZI.js → docs-2C2LTVT2.js} +32 -13
- package/dist/doctor-KI767GSN.js +87 -0
- package/dist/{eval-B3KZZESM.js → eval-XSSNATB4.js} +93 -27
- package/dist/{evidence-V67CHM35.js → evidence-UA6AWDQQ.js} +55 -27
- package/dist/{evolve-YDZSUQYA.js → evolve-QNTFGV6Z.js} +59 -26
- package/dist/{extensions-SRG7XCAH.js → extensions-QVDOHDGJ.js} +23 -8
- package/dist/{fleet-CA2CRTVG.js → fleet-Q7UOMUSG.js} +173 -56
- package/dist/fleet-preflight-DDN536IT.js +26 -0
- package/dist/{init-2OZDJE2D.js → init-WBB65ZHQ.js} +79 -34
- package/dist/{memory-3PIQQAKX.js → memory-MD3O64RI.js} +57 -28
- package/dist/{models-DY35XI7Y.js → models-BZU34YWD.js} +49 -24
- package/dist/monitor-MEQA5C3I.js +661 -0
- package/dist/{chunk-PS4PFJQP.js → orchestrator-CGFKEP27.js} +22837 -18564
- package/dist/{paths-5OMXW7Z4.js → paths-UXLN5YYZ.js} +17 -6
- package/dist/preload-P6DGH2PZ.js +13 -0
- package/dist/{reset-PIFYNOS3.js → reset-L2FQEE3E.js} +25 -13
- package/dist/{run-3VSPP24F.js → run-IV4Q6RLN.js} +115 -63
- package/dist/{share-D36RQCXM.js → share-S5BZQC5I.js} +23 -10
- package/dist/{skills-F2MRLELY.js → skills-LQEKRDTN.js} +42 -15
- package/dist/{skills-eval-E2ZTW4PL.js → skills-eval-3DC4HEWS.js} +90 -45
- package/dist/steer-GGWFUJUD.js +77 -0
- package/dist/{targets-DZMEZAH4.js → targets-C4SSGQOB.js} +78 -60
- package/dist/terminal-lease-IT5JW2NR.js +395 -0
- package/dist/{trace-7NYCUI2J.js → trace-PNCASAXC.js} +48 -17
- package/dist/tree-sitter-HGKH6LG4.js +4673 -0
- package/dist/{uninstall-AD3JWHBB.js → uninstall-FZCQCDKC.js} +19 -8
- package/dist/{upgrade-WYYBKGDY.js → upgrade-7TT7SQ3G.js} +66 -24
- package/dist/{usage-ULIDAGFF.js → usage-GV4PKT3M.js} +71 -32
- package/dist/verify-G6V4D2G7.js +716 -0
- package/dist/version-KGEG7EPO.js +21 -0
- package/dist/web-fetch-2YHJ3KTG.js +638 -0
- package/dist/{wiki-generate-PKFIX6OB.js → wiki-generate-DQF6Z66B.js} +98 -48
- package/dist/worker/entry.js +297 -54
- package/dist/workspace-G4ZWUIPR.js +22 -0
- package/docs/README.md +29 -21
- package/docs/acp.md +191 -16
- package/docs/alcf-provider.md +1 -1
- package/docs/architecture.md +157 -7
- package/docs/artifact-placement.md +101 -0
- package/docs/artifact-versions.md +1 -1
- package/docs/built-in-agents.md +4 -2
- package/docs/capacity-and-scheduling.md +1 -1
- package/docs/commands-and-modes.md +224 -97
- package/docs/config-knobs-audit.md +1 -3
- package/docs/configuration-and-targets.md +236 -38
- package/docs/context-engine.md +34 -7
- package/docs/development-pipeline.md +102 -0
- package/docs/dispatch-architecture-rationale.md +1 -1
- package/docs/documentation-coverage.md +4 -4
- package/docs/documentation-guide.md +3 -3
- package/docs/environment-variables.md +15 -6
- package/docs/eval-runner.md +3 -3
- package/docs/evals-internal.md +2 -2
- package/docs/evidence-and-memory.md +10 -2
- package/docs/evolution.md +2 -2
- package/docs/exit-codes-and-output.md +15 -9
- package/docs/extensions-and-sharing.md +25 -18
- package/docs/fleet-demo-runbook.md +22 -23
- package/docs/fleet-dispatch.md +117 -16
- package/docs/git-commit-provenance.md +120 -0
- package/docs/glossary.md +107 -15
- package/docs/installation-and-lifecycle.md +75 -17
- package/docs/middleware-and-components.md +1 -1
- package/docs/model-catalog.md +46 -15
- package/docs/observability.md +8 -5
- package/docs/performance-methodology.md +491 -0
- package/docs/pi-boundary.md +72 -0
- package/docs/proactive-memory.md +14 -14
- package/docs/prompt-envelope-and-tools.md +25 -4
- package/docs/provider-adapter-cookbook.md +57 -4
- package/docs/release-cut-checklist.md +129 -111
- package/docs/safety-model.md +28 -8
- package/docs/scientific-validation.md +3 -3
- package/docs/session-lifecycle.md +55 -12
- package/docs/skills-marketplace.md +12 -8
- package/docs/time-conventions.md +101 -0
- package/docs/tool-usage.md +6 -5
- package/docs/trace-store.md +1 -1
- package/docs/troubleshooting.md +10 -7
- package/docs/tui-design.md +121 -28
- package/docs/worker-dispatch-mechanics.md +3 -3
- package/package.json +33 -36
- package/skills/README.md +5 -6
- package/skills/coding/ast-grep/SKILL.md +136 -0
- package/skills/coding/ast-grep/evals.md +56 -0
- package/skills/coding/ast-grep/references/rule_reference.md +297 -0
- package/skills/coding/coding-standards/SKILL.md +113 -0
- package/skills/coding/coding-standards/evals.md +34 -0
- package/skills/coding/prototype/SKILL.md +86 -0
- package/skills/coding/prototype/evals.md +42 -0
- package/skills/coding/prototype/references/LOGIC.md +67 -0
- package/skills/coding/prototype/references/UI.md +112 -0
- package/skills/coding/tdd/SKILL.md +101 -0
- package/skills/coding/tdd/evals.md +41 -0
- package/skills/coding/tdd/references/mocking.md +59 -0
- package/skills/coding/tdd/references/tests.md +77 -0
- package/skills/context/context-handoff/SKILL.md +126 -0
- package/skills/context/context-handoff/evals.md +57 -0
- package/skills/context/context-handoff/scripts/new-handoff.sh +26 -0
- package/skills/context/context-prime/SKILL.md +95 -0
- package/skills/context/context-prime/evals.md +54 -0
- package/skills/git/file-ticket/SKILL.md +107 -0
- package/skills/git/file-ticket/evals.md +54 -0
- package/skills/git/fix-issue/SKILL.md +106 -0
- package/skills/git/fix-issue/evals.md +71 -0
- package/skills/git/ship/SKILL.md +92 -0
- package/skills/git/ship/evals.md +72 -0
- package/skills/meta/clio-dev/SKILL.md +91 -0
- package/skills/meta/clio-dev/evals.md +45 -0
- package/skills/meta/clio-test/SKILL.md +130 -0
- package/skills/meta/clio-test/evals.md +43 -0
- package/skills/meta/clio-test/references/harness.md +97 -0
- package/skills/meta/clio-test/references/test-map.md +59 -0
- package/skills/meta/credentials/SKILL.md +125 -0
- package/skills/meta/credentials/evals.md +104 -0
- package/skills/meta/find-skills/SKILL.md +72 -0
- package/skills/meta/find-skills/evals.md +47 -0
- package/skills/meta/herdr/SKILL.md +127 -0
- package/skills/meta/herdr/evals.md +38 -0
- package/skills/meta/skill-craft/SKILL.md +102 -0
- package/skills/meta/skill-craft/evals.md +41 -0
- package/skills/planning/architecture/SKILL.md +129 -0
- package/skills/planning/architecture/evals.md +36 -0
- package/skills/planning/backlog/SKILL.md +90 -0
- package/skills/planning/backlog/evals.md +43 -0
- package/skills/planning/prd/SKILL.md +82 -0
- package/skills/planning/prd/evals.md +49 -0
- package/skills/planning/product-intent/SKILL.md +112 -0
- package/skills/planning/product-intent/evals.md +36 -0
- package/skills/planning/tech-spec/SKILL.md +115 -0
- package/skills/planning/tech-spec/evals.md +47 -0
- package/skills/registry.yaml +136 -0
- package/skills/research/arxiv-literature/SKILL.md +104 -0
- package/skills/research/arxiv-literature/evals.md +58 -0
- package/skills/research/experiment-protocol/SKILL.md +122 -0
- package/skills/research/experiment-protocol/evals.md +91 -0
- package/skills/research/scientific-debugging/SKILL.md +119 -0
- package/skills/research/scientific-debugging/evals.md +138 -0
- package/skills/research/scientific-modernization/SKILL.md +138 -0
- package/skills/research/scientific-modernization/evals.md +84 -0
- package/skills/skill-marketplace.json +12 -20
- package/skills/workflow/design-council/SKILL.md +139 -0
- package/skills/workflow/design-council/evals.md +97 -0
- package/skills/workflow/grill-me/SKILL.md +186 -0
- package/skills/workflow/grill-me/evals.md +78 -0
- package/skills/workflow/workflow-distiller/SKILL.md +136 -0
- package/skills/workflow/workflow-distiller/evals.md +107 -0
- package/src/cli/acp.ts +115 -0
- package/src/cli/agents.ts +60 -0
- package/src/cli/args.ts +280 -0
- package/src/cli/argv.ts +170 -0
- package/src/cli/ask.ts +34 -0
- package/src/cli/auth.ts +468 -0
- package/src/cli/bootstrap-generate.ts +498 -0
- package/src/cli/clio.ts +96 -0
- package/src/cli/components.ts +195 -0
- package/src/cli/config-inspect.ts +336 -0
- package/src/cli/config.ts +72 -0
- package/src/cli/configure-interop.ts +67 -0
- package/src/cli/configure-layout.ts +172 -0
- package/src/cli/configure.ts +1525 -0
- package/src/cli/context-clear.ts +76 -0
- package/src/cli/context-index.ts +100 -0
- package/src/cli/context.ts +321 -0
- package/src/cli/default-target.ts +55 -0
- package/src/cli/docs.ts +346 -0
- package/src/cli/doctor.ts +54 -0
- package/src/cli/eval.ts +315 -0
- package/src/cli/evidence.ts +225 -0
- package/src/cli/evolve.ts +219 -0
- package/src/cli/extensions.ts +214 -0
- package/src/cli/fleet.ts +868 -0
- package/src/cli/index.ts +338 -0
- package/src/cli/init.ts +219 -0
- package/src/cli/initial-message.ts +47 -0
- package/src/cli/internal-dispatch.ts +55 -0
- package/src/cli/memory.ts +182 -0
- package/src/cli/models.ts +257 -0
- package/src/cli/modes/json-stream.ts +121 -0
- package/src/cli/modes/jsonl.ts +9 -0
- package/src/cli/modes/print.ts +604 -0
- package/src/cli/oauth-manual-input.ts +72 -0
- package/src/cli/oauth-select.ts +36 -0
- package/src/cli/output-guard.ts +48 -0
- package/src/cli/paths.ts +44 -0
- package/src/cli/provider-target.ts +113 -0
- package/src/cli/removal.ts +72 -0
- package/src/cli/reset.ts +253 -0
- package/src/cli/run.ts +497 -0
- package/src/cli/share.ts +243 -0
- package/src/cli/shared.ts +60 -0
- package/src/cli/skills-eval.ts +1240 -0
- package/src/cli/skills.ts +485 -0
- package/src/cli/steer-channel.ts +86 -0
- package/src/cli/targets.ts +1154 -0
- package/src/cli/text-layout.ts +112 -0
- package/src/cli/trace.ts +322 -0
- package/src/cli/uninstall.ts +405 -0
- package/src/cli/upgrade.ts +286 -0
- package/src/cli/usage.ts +876 -0
- package/src/cli/validate-model.ts +28 -0
- package/src/cli/version.ts +7 -0
- package/src/cli/wiki-generate.ts +510 -0
- package/src/core/agent-environment.ts +7 -0
- package/src/core/artifact-paths.ts +43 -0
- package/src/core/bash-exec.ts +313 -0
- package/src/core/boot-trace.ts +42 -0
- package/src/core/bus-events.ts +744 -0
- package/src/core/bus-trace.ts +53 -0
- package/src/core/c-header-language.ts +265 -0
- package/src/core/clio-repo.ts +78 -0
- package/src/core/commit-attribution.ts +157 -0
- package/src/core/compile-cache.ts +159 -0
- package/src/core/config.ts +1722 -0
- package/src/core/context-floor.ts +33 -0
- package/src/core/defaults.ts +659 -0
- package/src/core/domain-loader.ts +177 -0
- package/src/core/event-bus.ts +94 -0
- package/src/core/file-references.ts +282 -0
- package/src/core/git-commit-attribution.ts +362 -0
- package/src/core/guardrails.ts +185 -0
- package/src/core/headless-permission.ts +21 -0
- package/src/core/incomplete-installation.ts +45 -0
- package/src/core/init.ts +223 -0
- package/src/core/json-payload.ts +58 -0
- package/src/core/package-root.ts +54 -0
- package/src/core/path-canonical.ts +37 -0
- package/src/core/process-identity.ts +41 -0
- package/src/core/recent-models.ts +91 -0
- package/src/core/residency-protection.ts +56 -0
- package/src/core/resolve-config-value.ts +88 -0
- package/src/core/response-schema.ts +279 -0
- package/src/core/run-overrides.ts +77 -0
- package/src/core/safe-exec.ts +205 -0
- package/src/core/safe-id.ts +37 -0
- package/src/core/safe-resource-write.ts +162 -0
- package/src/core/session-routing.ts +339 -0
- package/src/core/settings-layers.ts +354 -0
- package/src/core/shared-bus.ts +8 -0
- package/src/core/shell-quote.ts +26 -0
- package/src/core/skill-activation.ts +190 -0
- package/src/core/startup-timer.ts +37 -0
- package/src/core/state-file-lock.ts +219 -0
- package/src/core/termination.ts +275 -0
- package/src/core/timers.ts +38 -0
- package/src/core/tool-names.ts +65 -0
- package/src/core/verification-scripts.ts +13 -0
- package/src/core/workspace-files.ts +492 -0
- package/src/core/xdg.ts +177 -0
- package/src/domains/agents/builtins/architect.md +4 -4
- package/src/domains/agents/builtins/coder.md +3 -2
- package/src/domains/agents/builtins/debugger.md +1 -1
- package/src/domains/agents/builtins/documenter.md +1 -1
- package/src/domains/agents/builtins/git-master.md +3 -3
- package/src/domains/agents/builtins/provenance.md +1 -1
- package/src/domains/agents/builtins/researcher.md +1 -1
- package/src/domains/agents/builtins/scout.md +1 -1
- package/src/domains/agents/builtins/tester.md +1 -1
- package/src/domains/agents/builtins/verifier.md +1 -1
- package/src/domains/agents/builtins/wiki-writer.md +1 -1
- package/src/domains/agents/catalog.ts +172 -0
- package/src/domains/agents/contract.ts +16 -0
- package/src/domains/agents/extension.ts +110 -0
- package/src/domains/agents/fleet-commands.ts +154 -0
- package/src/domains/agents/fleet-contract.ts +797 -0
- package/src/domains/agents/frontmatter.ts +40 -0
- package/src/domains/agents/index.ts +102 -0
- package/src/domains/agents/manifest.ts +6 -0
- package/src/domains/agents/recipe-schema.ts +202 -0
- package/src/domains/agents/recipe.ts +125 -0
- package/src/domains/agents/registry.ts +111 -0
- package/src/domains/agents/result-contract.ts +1265 -0
- package/src/domains/agents/spec.ts +371 -0
- package/src/domains/agents/write-boundary.ts +109 -0
- package/src/domains/components/diff.ts +99 -0
- package/src/domains/components/hash.ts +21 -0
- package/src/domains/components/index.ts +3 -0
- package/src/domains/components/scan.ts +304 -0
- package/src/domains/components/snapshot.ts +108 -0
- package/src/domains/components/types.ts +82 -0
- package/src/domains/config/agent-namespace.ts +13 -0
- package/src/domains/config/classify.ts +106 -0
- package/src/domains/config/contract.ts +22 -0
- package/src/domains/config/extension.ts +142 -0
- package/src/domains/config/index.ts +23 -0
- package/src/domains/config/keybindings.ts +200 -0
- package/src/domains/config/manifest.ts +6 -0
- package/src/domains/config/watcher.ts +41 -0
- package/src/domains/context/adoption.ts +880 -0
- package/src/domains/context/bootstrap-prompt.ts +273 -0
- package/src/domains/context/bootstrap.ts +1502 -0
- package/src/domains/context/clear.ts +91 -0
- package/src/domains/context/clio-md.ts +429 -0
- package/src/domains/context/codewiki/artifact.ts +400 -0
- package/src/domains/context/codewiki/build-worker-protocol.ts +24 -0
- package/src/domains/context/codewiki/build-worker.ts +54 -0
- package/src/domains/context/codewiki/cmake.ts +356 -0
- package/src/domains/context/codewiki/cooperative.ts +46 -0
- package/src/domains/context/codewiki/coordinator.ts +182 -0
- package/src/domains/context/codewiki/digest.ts +135 -0
- package/src/domains/context/codewiki/grammar-assets.ts +33 -0
- package/src/domains/context/codewiki/indexer.ts +1529 -0
- package/src/domains/context/codewiki/paths.ts +67 -0
- package/src/domains/context/codewiki/schema.ts +80 -0
- package/src/domains/context/codewiki/tree-sitter.ts +771 -0
- package/src/domains/context/contract.ts +68 -0
- package/src/domains/context/excluded-dirs.ts +1 -0
- package/src/domains/context/extension.ts +358 -0
- package/src/domains/context/fingerprint.ts +198 -0
- package/src/domains/context/index.ts +154 -0
- package/src/domains/context/init-options.ts +69 -0
- package/src/domains/context/manifest.ts +6 -0
- package/src/domains/context/operator-profile.ts +157 -0
- package/src/domains/context/project-metadata.ts +529 -0
- package/src/domains/context/project-rules.ts +227 -0
- package/src/domains/context/prompt-context.ts +61 -0
- package/src/domains/context/refresh.ts +178 -0
- package/src/domains/context/runtime.ts +17 -0
- package/src/domains/context/sibling-files.ts +10 -0
- package/src/domains/context/state.ts +237 -0
- package/src/domains/context/wiki/assemble.ts +303 -0
- package/src/domains/context/wiki/frontmatter.ts +210 -0
- package/src/domains/context/wiki/generate.ts +595 -0
- package/src/domains/context/wiki/layout.ts +111 -0
- package/src/domains/context/wiki/meta.ts +221 -0
- package/src/domains/context/wiki/plan-store.ts +217 -0
- package/src/domains/context/wiki/plan.ts +250 -0
- package/src/domains/context/wiki/prompts.ts +283 -0
- package/src/domains/context/wiki/staleness.ts +198 -0
- package/src/domains/dispatch/active-route-planner.ts +168 -0
- package/src/domains/dispatch/admission-queue.ts +150 -0
- package/src/domains/dispatch/admission.ts +334 -0
- package/src/domains/dispatch/agent-candidates.ts +307 -0
- package/src/domains/dispatch/agent-ledger-hub.ts +71 -0
- package/src/domains/dispatch/agent-ledger-store.ts +257 -0
- package/src/domains/dispatch/agent-ledger.ts +212 -0
- package/src/domains/dispatch/agent-plan-adapter.ts +85 -0
- package/src/domains/dispatch/assignment-events.ts +132 -0
- package/src/domains/dispatch/assignment-reconcile.ts +76 -0
- package/src/domains/dispatch/assignment-store.ts +162 -0
- package/src/domains/dispatch/assignment.ts +278 -0
- package/src/domains/dispatch/backoff.ts +59 -0
- package/src/domains/dispatch/batch-store.ts +134 -0
- package/src/domains/dispatch/batch-tracker.ts +66 -0
- package/src/domains/dispatch/capability-match.ts +155 -0
- package/src/domains/dispatch/capacity-lease.ts +413 -0
- package/src/domains/dispatch/code-step-store.ts +52 -0
- package/src/domains/dispatch/code-step.ts +387 -0
- package/src/domains/dispatch/contract.ts +278 -0
- package/src/domains/dispatch/dedup.ts +133 -0
- package/src/domains/dispatch/event-pump.ts +251 -0
- package/src/domains/dispatch/execution-handoff.ts +29 -0
- package/src/domains/dispatch/execution-plan.ts +328 -0
- package/src/domains/dispatch/execution-role.ts +277 -0
- package/src/domains/dispatch/execution-scheduler.ts +583 -0
- package/src/domains/dispatch/extension.ts +6006 -0
- package/src/domains/dispatch/failure-classification.ts +147 -0
- package/src/domains/dispatch/fleet-commit-attribution.ts +56 -0
- package/src/domains/dispatch/fleet-plan.ts +225 -0
- package/src/domains/dispatch/fleet-preflight.ts +394 -0
- package/src/domains/dispatch/gate-decisions.ts +1040 -0
- package/src/domains/dispatch/gate-role-prompts.ts +58 -0
- package/src/domains/dispatch/heartbeat.ts +27 -0
- package/src/domains/dispatch/index.ts +137 -0
- package/src/domains/dispatch/joint-route-adapter.ts +114 -0
- package/src/domains/dispatch/joint-route-resolver.ts +283 -0
- package/src/domains/dispatch/manifest.ts +6 -0
- package/src/domains/dispatch/orphan-recovery.ts +266 -0
- package/src/domains/dispatch/outcome.ts +109 -0
- package/src/domains/dispatch/phase-timing.ts +47 -0
- package/src/domains/dispatch/placement.ts +202 -0
- package/src/domains/dispatch/receipt-findings.ts +255 -0
- package/src/domains/dispatch/receipt-integrity.ts +383 -0
- package/src/domains/dispatch/recovery-candidates.ts +74 -0
- package/src/domains/dispatch/reproducibility.ts +48 -0
- package/src/domains/dispatch/reservation-store.ts +501 -0
- package/src/domains/dispatch/route-approval.ts +177 -0
- package/src/domains/dispatch/route-candidates.ts +57 -0
- package/src/domains/dispatch/route-decision.ts +1079 -0
- package/src/domains/dispatch/route-facts.ts +187 -0
- package/src/domains/dispatch/route-history.ts +238 -0
- package/src/domains/dispatch/route-observer.ts +488 -0
- package/src/domains/dispatch/route-policy.ts +327 -0
- package/src/domains/dispatch/route-quality.ts +256 -0
- package/src/domains/dispatch/route-readiness.ts +120 -0
- package/src/domains/dispatch/routing-intent.ts +287 -0
- package/src/domains/dispatch/run-identity.ts +56 -0
- package/src/domains/dispatch/scout-transition.ts +130 -0
- package/src/domains/dispatch/state.ts +302 -0
- package/src/domains/dispatch/tool-stats.ts +132 -0
- package/src/domains/dispatch/transport.ts +210 -0
- package/src/domains/dispatch/types.ts +799 -0
- package/src/domains/dispatch/validation-grounding.ts +154 -0
- package/src/domains/dispatch/validation.ts +721 -0
- package/src/domains/dispatch/worker-protocol.ts +275 -0
- package/src/domains/dispatch/worker-spawn.ts +585 -0
- package/src/domains/dispatch/write-boundary-enforcer.ts +97 -0
- package/src/domains/dispatch/write-boundary.ts +426 -0
- package/src/domains/eval/artifacts/redact.ts +42 -0
- package/src/domains/eval/artifacts/store.ts +173 -0
- package/src/domains/eval/compare/compare.ts +33 -0
- package/src/domains/eval/compare/gates.ts +66 -0
- package/src/domains/eval/compare/thresholds.ts +92 -0
- package/src/domains/eval/harness-metrics.ts +62 -0
- package/src/domains/eval/index.ts +66 -0
- package/src/domains/eval/metrics/chaos-stream.ts +93 -0
- package/src/domains/eval/metrics/context.ts +34 -0
- package/src/domains/eval/metrics/coverage.ts +21 -0
- package/src/domains/eval/metrics/evidence.ts +182 -0
- package/src/domains/eval/metrics/fleet-loop-stream.ts +196 -0
- package/src/domains/eval/metrics/invariants.ts +736 -0
- package/src/domains/eval/metrics/latency.ts +4 -0
- package/src/domains/eval/metrics/token-stream.ts +139 -0
- package/src/domains/eval/metrics/tokens.ts +51 -0
- package/src/domains/eval/metrics/tool-calls.ts +7 -0
- package/src/domains/eval/proactive-memory.ts +279 -0
- package/src/domains/eval/provenance.ts +35 -0
- package/src/domains/eval/report.ts +234 -0
- package/src/domains/eval/reports/json.ts +5 -0
- package/src/domains/eval/reports/junit.ts +23 -0
- package/src/domains/eval/reports/markdown.ts +20 -0
- package/src/domains/eval/reports/swe-jsonl.ts +16 -0
- package/src/domains/eval/reports/text.ts +25 -0
- package/src/domains/eval/run-compare.ts +274 -0
- package/src/domains/eval/runner.ts +244 -0
- package/src/domains/eval/runners/clio-run.ts +148 -0
- package/src/domains/eval/runners/context-index.ts +56 -0
- package/src/domains/eval/runners/context-init.ts +203 -0
- package/src/domains/eval/runners/external-command.ts +350 -0
- package/src/domains/eval/runtime-paths.ts +171 -0
- package/src/domains/eval/schema/artifact.ts +75 -0
- package/src/domains/eval/schema/suite.ts +235 -0
- package/src/domains/eval/schema/validate.ts +339 -0
- package/src/domains/eval/store.ts +392 -0
- package/src/domains/eval/suites/load.ts +74 -0
- package/src/domains/eval/suites/matrix.ts +19 -0
- package/src/domains/eval/suites/resolve.ts +44 -0
- package/src/domains/eval/suites/run.ts +325 -0
- package/src/domains/eval/task-file.ts +186 -0
- package/src/domains/eval/types.ts +140 -0
- package/src/domains/eval/verifiers/command.ts +27 -0
- package/src/domains/eval/verifiers/file-exists.ts +6 -0
- package/src/domains/eval/verifiers/patch.ts +22 -0
- package/src/domains/eval/workspaces/git.ts +41 -0
- package/src/domains/eval/workspaces/local.ts +17 -0
- package/src/domains/eval/workspaces/temp-copy.ts +29 -0
- package/src/domains/evidence/build.ts +1754 -0
- package/src/domains/evidence/eval.ts +605 -0
- package/src/domains/evidence/failure-attribution.ts +54 -0
- package/src/domains/evidence/finish-contract-map.ts +42 -0
- package/src/domains/evidence/index.ts +55 -0
- package/src/domains/evidence/provenance.ts +200 -0
- package/src/domains/evidence/redact.ts +81 -0
- package/src/domains/evidence/store.ts +291 -0
- package/src/domains/evidence/types.ts +325 -0
- package/src/domains/evolution/SELF_EDIT_GATE.md +91 -0
- package/src/domains/evolution/index.ts +26 -0
- package/src/domains/evolution/manifest.ts +154 -0
- package/src/domains/evolution/validate.ts +304 -0
- package/src/domains/extensions/contract.ts +22 -0
- package/src/domains/extensions/discovery.ts +193 -0
- package/src/domains/extensions/extension.ts +46 -0
- package/src/domains/extensions/index.ts +34 -0
- package/src/domains/extensions/manager.ts +30 -0
- package/src/domains/extensions/manifest.ts +6 -0
- package/src/domains/extensions/resources.ts +38 -0
- package/src/domains/extensions/state.ts +224 -0
- package/src/domains/extensions/types.ts +83 -0
- package/src/domains/interop/consent.ts +185 -0
- package/src/domains/interop/contract.ts +29 -0
- package/src/domains/interop/detect.ts +173 -0
- package/src/domains/interop/extension.ts +59 -0
- package/src/domains/interop/index.ts +34 -0
- package/src/domains/interop/manifest.ts +6 -0
- package/src/domains/interop/registry.ts +139 -0
- package/src/domains/interop/state.ts +92 -0
- package/src/domains/interop/types.ts +109 -0
- package/src/domains/lifecycle/contract.ts +11 -0
- package/src/domains/lifecycle/doctor.ts +536 -0
- package/src/domains/lifecycle/extension.ts +23 -0
- package/src/domains/lifecycle/index.ts +21 -0
- package/src/domains/lifecycle/install-method.ts +51 -0
- package/src/domains/lifecycle/manifest.ts +6 -0
- package/src/domains/lifecycle/migrations/2026-08-18-lmstudio-runtime-id.ts +52 -0
- package/src/domains/lifecycle/migrations/index.ts +114 -0
- package/src/domains/lifecycle/state.ts +93 -0
- package/src/domains/lifecycle/upgrade-notice.ts +35 -0
- package/src/domains/lifecycle/version.ts +35 -0
- package/src/domains/memory/index.ts +128 -0
- package/src/domains/memory/operations.ts +143 -0
- package/src/domains/memory/prompt-section.ts +86 -0
- package/src/domains/memory/proposal.ts +180 -0
- package/src/domains/memory/store.ts +163 -0
- package/src/domains/memory/task-bank.ts +229 -0
- package/src/domains/memory/task-memory-handoff.ts +222 -0
- package/src/domains/memory/task-memory-policy.ts +602 -0
- package/src/domains/memory/task-memory-status.ts +58 -0
- package/src/domains/memory/task-memory-telemetry.ts +346 -0
- package/src/domains/memory/task-memory-trace.ts +89 -0
- package/src/domains/memory/types.ts +74 -0
- package/src/domains/memory/validate.ts +297 -0
- package/src/domains/middleware/budget.ts +216 -0
- package/src/domains/middleware/contract.ts +35 -0
- package/src/domains/middleware/dispatch-nudge.ts +469 -0
- package/src/domains/middleware/extension.ts +129 -0
- package/src/domains/middleware/hook-receipts.ts +70 -0
- package/src/domains/middleware/hooks-io.ts +186 -0
- package/src/domains/middleware/hooks.ts +432 -0
- package/src/domains/middleware/index.ts +154 -0
- package/src/domains/middleware/manifest.ts +6 -0
- package/src/domains/middleware/memory-intervention.ts +701 -0
- package/src/domains/middleware/rules.ts +42 -0
- package/src/domains/middleware/runtime.ts +333 -0
- package/src/domains/middleware/skills-reminder.ts +202 -0
- package/src/domains/middleware/snapshot.ts +71 -0
- package/src/domains/middleware/stalled-turn.ts +71 -0
- package/src/domains/middleware/task-board-reminder.ts +87 -0
- package/src/domains/middleware/task-nudge.ts +77 -0
- package/src/domains/middleware/tool-choice-control.ts +42 -0
- package/src/domains/middleware/types.ts +131 -0
- package/src/domains/middleware/validate.ts +329 -0
- package/src/domains/observability/accountability.ts +67 -0
- package/src/domains/observability/contract.ts +168 -0
- package/src/domains/observability/cost.ts +206 -0
- package/src/domains/observability/evidence-index.ts +123 -0
- package/src/domains/observability/extension.ts +281 -0
- package/src/domains/observability/index.ts +78 -0
- package/src/domains/observability/manifest.ts +10 -0
- package/src/domains/observability/metrics.ts +37 -0
- package/src/domains/observability/projection.ts +477 -0
- package/src/domains/observability/telemetry.ts +89 -0
- package/src/domains/observability/trace-store.ts +1454 -0
- package/src/domains/prompts/compiler.ts +614 -0
- package/src/domains/prompts/contract.ts +46 -0
- package/src/domains/prompts/extension.ts +336 -0
- package/src/domains/prompts/fragment-loader.ts +138 -0
- package/src/domains/prompts/fragments/identity/clio.md +5 -6
- package/src/domains/prompts/fragments/identity/docs-routing.md +10 -0
- package/src/domains/prompts/fragments/identity/self-awareness.md +19 -0
- package/src/domains/prompts/fragments/operating/contract.md +4 -41
- package/src/domains/prompts/fragments/operating/delegation.md +42 -0
- package/src/domains/prompts/fragments/operating/skills.md +26 -0
- package/src/domains/prompts/fragments/operating/worker.md +16 -0
- package/src/domains/prompts/fragments/safety/auto-edit.md +5 -5
- package/src/domains/prompts/fragments/safety/full-auto.md +3 -3
- package/src/domains/prompts/fragments/safety/read-only.md +4 -4
- package/src/domains/prompts/fragments/safety/suggest.md +2 -2
- package/src/domains/prompts/fragments/wiki/page.md +10 -0
- package/src/domains/prompts/fragments/wiki/plan.md +10 -0
- package/src/domains/prompts/hash.ts +73 -0
- package/src/domains/prompts/index.ts +32 -0
- package/src/domains/prompts/manifest.ts +8 -0
- package/src/domains/prompts/memory-intervention.ts +57 -0
- package/src/domains/prompts/preload.ts +82 -0
- package/src/domains/providers/auth/api-key.ts +47 -0
- package/src/domains/providers/auth/backend-file.ts +79 -0
- package/src/domains/providers/auth/backend-memory.ts +72 -0
- package/src/domains/providers/auth/boot-status.ts +65 -0
- package/src/domains/providers/auth/index.ts +37 -0
- package/src/domains/providers/auth/oauth.ts +37 -0
- package/src/domains/providers/auth/storage.ts +726 -0
- package/src/domains/providers/capabilities.ts +58 -0
- package/src/domains/providers/catalog.ts +162 -0
- package/src/domains/providers/contract.ts +162 -0
- package/src/domains/providers/credentials.ts +20 -0
- package/src/domains/providers/eligibility.ts +24 -0
- package/src/domains/providers/errors.ts +64 -0
- package/src/domains/providers/extension.ts +548 -0
- package/src/domains/providers/index.ts +142 -0
- package/src/domains/providers/knowledge-base-path.ts +102 -0
- package/src/domains/providers/manifest.ts +6 -0
- package/src/domains/providers/model-capabilities.ts +136 -0
- package/src/domains/providers/model-discovery.ts +144 -0
- package/src/domains/providers/model-family.ts +32 -0
- package/src/domains/providers/model-runtime-capabilities.ts +723 -0
- package/src/domains/providers/models/local-models/clio-local-coding-targets.yaml +120 -37
- package/src/domains/providers/plugins.ts +61 -0
- package/src/domains/providers/probe/fingerprint.ts +64 -0
- package/src/domains/providers/probe/http.ts +114 -0
- package/src/domains/providers/probe/reasoning.ts +111 -0
- package/src/domains/providers/registry.ts +187 -0
- package/src/domains/providers/resolver.ts +156 -0
- package/src/domains/providers/runtime-resolution.ts +720 -0
- package/src/domains/providers/runtimes/antigravity/antigravity-code.ts +90 -0
- package/src/domains/providers/runtimes/boot-manifest.ts +55 -0
- package/src/domains/providers/runtimes/builtins.ts +78 -0
- package/src/domains/providers/runtimes/claude/claude-code.ts +38 -0
- package/src/domains/providers/runtimes/claude/claude-sdk.ts +38 -0
- package/src/domains/providers/runtimes/claude/common.ts +55 -0
- package/src/domains/providers/runtimes/cloud/alcf.ts +171 -0
- package/src/domains/providers/runtimes/cloud/anthropic-max.ts +71 -0
- package/src/domains/providers/runtimes/cloud/anthropic.ts +43 -0
- package/src/domains/providers/runtimes/cloud/bedrock.ts +35 -0
- package/src/domains/providers/runtimes/cloud/deepseek.ts +50 -0
- package/src/domains/providers/runtimes/cloud/google.ts +41 -0
- package/src/domains/providers/runtimes/cloud/groq.ts +46 -0
- package/src/domains/providers/runtimes/cloud/mistral.ts +46 -0
- package/src/domains/providers/runtimes/cloud/openai-codex.ts +49 -0
- package/src/domains/providers/runtimes/cloud/openai.ts +46 -0
- package/src/domains/providers/runtimes/cloud/openrouter.ts +111 -0
- package/src/domains/providers/runtimes/common/lmstudio-http.ts +423 -0
- package/src/domains/providers/runtimes/common/local-synth.ts +164 -0
- package/src/domains/providers/runtimes/common/probe-helpers.ts +474 -0
- package/src/domains/providers/runtimes/local-native/lemonade-anthropic.ts +27 -0
- package/src/domains/providers/runtimes/local-native/lemonade-openai.ts +26 -0
- package/src/domains/providers/runtimes/local-native/llamacpp-anthropic.ts +88 -0
- package/src/domains/providers/runtimes/local-native/llamacpp-completion.ts +207 -0
- package/src/domains/providers/runtimes/local-native/llamacpp-embed.ts +180 -0
- package/src/domains/providers/runtimes/local-native/llamacpp-rerank.ts +126 -0
- package/src/domains/providers/runtimes/local-native/llamacpp.ts +98 -0
- package/src/domains/providers/runtimes/local-native/lmstudio.ts +241 -0
- package/src/domains/providers/runtimes/local-native/ollama-native.ts +117 -0
- package/src/domains/providers/runtimes/local-native/sglang.ts +26 -0
- package/src/domains/providers/runtimes/local-native/vllm.ts +27 -0
- package/src/domains/providers/runtimes/protocol/anthropic-compat.ts +104 -0
- package/src/domains/providers/runtimes/protocol/anthropic-messages.ts +28 -0
- package/src/domains/providers/runtimes/protocol/bedrock.ts +26 -0
- package/src/domains/providers/runtimes/protocol/google.ts +27 -0
- package/src/domains/providers/runtimes/protocol/openai-compat.ts +145 -0
- package/src/domains/providers/support.ts +241 -0
- package/src/domains/providers/thinking-control-policy.ts +85 -0
- package/src/domains/providers/types/capability-flags.ts +114 -0
- package/src/domains/providers/types/cost-provenance.ts +7 -0
- package/src/domains/providers/types/inference.ts +41 -0
- package/src/domains/providers/types/knowledge-base.ts +156 -0
- package/src/domains/providers/types/local-model-quirks.ts +216 -0
- package/src/domains/providers/types/runtime-descriptor.ts +181 -0
- package/src/domains/providers/types/target-descriptor.ts +60 -0
- package/src/domains/resources/collision.ts +92 -0
- package/src/domains/resources/common-loader.ts +140 -0
- package/src/domains/resources/contract.ts +24 -0
- package/src/domains/resources/extension.ts +59 -0
- package/src/domains/resources/index.ts +74 -0
- package/src/domains/resources/loader.ts +85 -0
- package/src/domains/resources/manifest.ts +6 -0
- package/src/domains/resources/prompts/loader.ts +246 -0
- package/src/domains/resources/prompts/substitute.ts +1 -0
- package/src/domains/resources/skills/content-hash.ts +107 -0
- package/src/domains/resources/skills/evals.ts +175 -0
- package/src/domains/resources/skills/install.ts +427 -0
- package/src/domains/resources/skills/loader.ts +985 -0
- package/src/domains/resources/skills/marketplace.ts +301 -0
- package/src/domains/resources/skills/provenance-pin.ts +115 -0
- package/src/domains/safety/action-classifier.ts +270 -0
- package/src/domains/safety/approval-axis.ts +23 -0
- package/src/domains/safety/audit.ts +493 -0
- package/src/domains/safety/autonomy.ts +174 -0
- package/src/domains/safety/call-target.ts +59 -0
- package/src/domains/safety/contract.ts +65 -0
- package/src/domains/safety/damage-control.ts +44 -0
- package/src/domains/safety/default-path-policy.ts +117 -0
- package/src/domains/safety/extension.ts +383 -0
- package/src/domains/safety/finish-contract-registration.ts +128 -0
- package/src/domains/safety/finish-contract.ts +554 -0
- package/src/domains/safety/index.ts +44 -0
- package/src/domains/safety/loop-detector.ts +143 -0
- package/src/domains/safety/manifest.ts +6 -0
- package/src/domains/safety/path-policy.ts +164 -0
- package/src/domains/safety/policy-engine.ts +994 -0
- package/src/domains/safety/project-policy.ts +391 -0
- package/src/domains/safety/protected-artifacts-registration.ts +189 -0
- package/src/domains/safety/protected-artifacts.ts +993 -0
- package/src/domains/safety/rejection-feedback.ts +102 -0
- package/src/domains/safety/rigor.ts +72 -0
- package/src/domains/safety/rule-compiler.ts +48 -0
- package/src/domains/safety/rule-pack-loader.ts +92 -0
- package/src/domains/safety/run-effects.ts +197 -0
- package/src/domains/safety/scope.ts +61 -0
- package/src/domains/scheduling/budget.ts +35 -0
- package/src/domains/scheduling/cluster.ts +175 -0
- package/src/domains/scheduling/contract.ts +30 -0
- package/src/domains/scheduling/extension.ts +91 -0
- package/src/domains/scheduling/index.ts +12 -0
- package/src/domains/scheduling/manifest.ts +6 -0
- package/src/domains/session/archive-readers.ts +208 -0
- package/src/domains/session/checkpoint.ts +38 -0
- package/src/domains/session/compaction/auto.ts +89 -0
- package/src/domains/session/compaction/branch-summary.ts +187 -0
- package/src/domains/session/compaction/compact.ts +493 -0
- package/src/domains/session/compaction/cut-point.ts +172 -0
- package/src/domains/session/compaction/defaults.ts +34 -0
- package/src/domains/session/compaction/mask-observations.ts +265 -0
- package/src/domains/session/compaction/session-entries.ts +18 -0
- package/src/domains/session/compaction/tokens.ts +148 -0
- package/src/domains/session/context-accounting.ts +583 -0
- package/src/domains/session/context-ledger.ts +362 -0
- package/src/domains/session/contract.ts +159 -0
- package/src/domains/session/cwd-fallback.ts +65 -0
- package/src/domains/session/decision-board.ts +190 -0
- package/src/domains/session/entries.ts +572 -0
- package/src/domains/session/extension.ts +403 -0
- package/src/domains/session/history.ts +172 -0
- package/src/domains/session/index.ts +84 -0
- package/src/domains/session/manager.ts +128 -0
- package/src/domains/session/manifest.ts +6 -0
- package/src/domains/session/migrations/index.ts +27 -0
- package/src/domains/session/prompt-manifest.ts +173 -0
- package/src/domains/session/protected-artifact-journal.ts +241 -0
- package/src/domains/session/protected-artifacts.ts +90 -0
- package/src/domains/session/retry.ts +230 -0
- package/src/domains/session/session-artifacts.ts +107 -0
- package/src/domains/session/task-board.ts +569 -0
- package/src/domains/session/tree/active-path.ts +137 -0
- package/src/domains/session/tree/fork.ts +159 -0
- package/src/domains/session/tree/manager.ts +128 -0
- package/src/domains/session/tree/navigator.ts +177 -0
- package/src/domains/session/tree/preview.ts +259 -0
- package/src/domains/session/usage.ts +148 -0
- package/src/domains/session/workspace/git-probe.ts +167 -0
- package/src/domains/session/workspace/index.ts +4 -0
- package/src/domains/session/workspace/project-type.ts +217 -0
- package/src/domains/session/workspace/snapshot.ts +74 -0
- package/src/domains/share/archive.ts +684 -0
- package/src/domains/share/contract.ts +9 -0
- package/src/domains/share/extension.ts +26 -0
- package/src/domains/share/index.ts +28 -0
- package/src/domains/share/manifest.ts +6 -0
- package/src/domains/user-tasks/store.ts +297 -0
- package/src/engine/acp/adapter.ts +397 -0
- package/src/engine/acp/errors.ts +128 -0
- package/src/engine/acp/event-mapper.ts +154 -0
- package/src/engine/acp/server.ts +2238 -0
- package/src/engine/acp/tool-mediator.ts +686 -0
- package/src/engine/acp/transport.ts +775 -0
- package/src/engine/acp/types.ts +196 -0
- package/src/engine/agent.ts +29 -0
- package/src/engine/ai.ts +207 -0
- package/src/engine/alcf-oauth.ts +205 -0
- package/src/engine/antigravity/subprocess-runtime.ts +205 -0
- package/src/engine/api-registry.ts +223 -0
- package/src/engine/apis/degraded-inference.ts +95 -0
- package/src/engine/apis/index.ts +16 -0
- package/src/engine/apis/llamacpp-residency.ts +292 -0
- package/src/engine/apis/lmstudio-residency.ts +76 -0
- package/src/engine/apis/lmstudio.ts +243 -0
- package/src/engine/apis/ollama-native.ts +528 -0
- package/src/engine/apis/openai-completions.ts +795 -0
- package/src/engine/apis/output-budget.ts +99 -0
- package/src/engine/apis/residency-lock.ts +29 -0
- package/src/engine/apis/residency.ts +776 -0
- package/src/engine/apis/resident-models.ts +42 -0
- package/src/engine/apis/sampling-overrides.ts +38 -0
- package/src/engine/apis/types.ts +16 -0
- package/src/engine/claude/sdk-runtime.ts +685 -0
- package/src/engine/claude/session-id.ts +5 -0
- package/src/engine/claude/subprocess-runtime.ts +316 -0
- package/src/engine/claude/tool-safety.ts +400 -0
- package/src/engine/env-api-keys.ts +98 -0
- package/src/engine/external-subprocess.ts +23 -0
- package/src/engine/gemma-channel-filter.ts +223 -0
- package/src/engine/harmony-response.ts +165 -0
- package/src/engine/instrumented-tui.ts +192 -0
- package/src/engine/loop-guard.ts +1127 -0
- package/src/engine/messages.ts +14 -0
- package/src/engine/models.ts +42 -0
- package/src/engine/oauth.ts +196 -0
- package/src/engine/pi-mono-names.ts +13 -0
- package/src/engine/prompt-templates.ts +1 -0
- package/src/engine/provider-payload.ts +272 -0
- package/src/engine/select-list.ts +53 -0
- package/src/engine/session.ts +834 -0
- package/src/engine/strip-tokenizer-sentinels.ts +125 -0
- package/src/engine/truncate.ts +9 -0
- package/src/engine/tui.ts +111 -0
- package/src/engine/types.ts +63 -0
- package/src/engine/worker-events.ts +100 -0
- package/src/engine/worker-runtime-capabilities.ts +5 -0
- package/src/engine/worker-runtime.ts +1022 -0
- package/src/engine/worker-tools.ts +208 -0
- package/src/entry/boot-options.ts +50 -0
- package/src/entry/orchestrator.ts +2011 -0
- package/src/interactive/application-controller.ts +433 -0
- package/src/interactive/bus-notices.ts +265 -0
- package/src/interactive/chat-loop-messages.ts +626 -0
- package/src/interactive/chat-loop-policy.ts +18 -0
- package/src/interactive/chat-loop.ts +1090 -0
- package/src/interactive/chat-panel.ts +1820 -0
- package/src/interactive/chat-renderer.ts +1228 -0
- package/src/interactive/clio-editor.ts +161 -0
- package/src/interactive/command-fallbacks.ts +23 -0
- package/src/interactive/command-output.ts +85 -0
- package/src/interactive/context-activity.ts +203 -0
- package/src/interactive/context-meter.ts +223 -0
- package/src/interactive/context-overlay.ts +246 -0
- package/src/interactive/cost-overlay.ts +279 -0
- package/src/interactive/dispatch-board.ts +1319 -0
- package/src/interactive/dispatch-steering.ts +86 -0
- package/src/interactive/editor-bash.ts +46 -0
- package/src/interactive/editor-steer.ts +51 -0
- package/src/interactive/editor-submit.ts +507 -0
- package/src/interactive/export-html/ansi-to-html.ts +161 -0
- package/src/interactive/export-html/index.ts +51 -0
- package/src/interactive/export-html/template.ts +45 -0
- package/src/interactive/export-html/tool-renderer.ts +54 -0
- package/src/interactive/external-editor.ts +67 -0
- package/src/interactive/follow-up-queue-panel.ts +75 -0
- package/src/interactive/footer/dashboard.ts +538 -0
- package/src/interactive/footer/notifications.ts +275 -0
- package/src/interactive/footer/widgets.ts +1264 -0
- package/src/interactive/footer-panel.ts +187 -0
- package/src/interactive/format-time.ts +97 -0
- package/src/interactive/index.ts +11 -0
- package/src/interactive/interactive-application.ts +939 -0
- package/src/interactive/interactive-event-projection.ts +385 -0
- package/src/interactive/interactive-input-runtime.ts +307 -0
- package/src/interactive/interactive-presentation.ts +584 -0
- package/src/interactive/interactive-shell.ts +388 -0
- package/src/interactive/interactive-slash-runtime.ts +554 -0
- package/src/interactive/interactive-subscriptions.ts +111 -0
- package/src/interactive/interactive-tickers.ts +183 -0
- package/src/interactive/keybinding-manager.ts +496 -0
- package/src/interactive/layout.ts +57 -0
- package/src/interactive/leader-key.ts +143 -0
- package/src/interactive/loop-guard-interrupt.ts +101 -0
- package/src/interactive/memory-overlay.ts +280 -0
- package/src/interactive/overlay-ask-user-lifecycle.ts +110 -0
- package/src/interactive/overlay-auth-lifecycle.ts +308 -0
- package/src/interactive/overlay-frame.ts +502 -0
- package/src/interactive/overlay-general-openers.ts +274 -0
- package/src/interactive/overlay-key-routing.ts +216 -0
- package/src/interactive/overlay-lifecycle.ts +426 -0
- package/src/interactive/overlay-model-selectors.ts +127 -0
- package/src/interactive/overlay-permission-lifecycle.ts +345 -0
- package/src/interactive/overlay-resource-openers.ts +112 -0
- package/src/interactive/overlay-session-lifecycle.ts +327 -0
- package/src/interactive/overlay-transitions.ts +57 -0
- package/src/interactive/overlays/agents.ts +69 -0
- package/src/interactive/overlays/ask-user.ts +921 -0
- package/src/interactive/overlays/auth-dialog.ts +221 -0
- package/src/interactive/overlays/context-reset.ts +108 -0
- package/src/interactive/overlays/cwd-fallback.ts +141 -0
- package/src/interactive/overlays/decisions.ts +300 -0
- package/src/interactive/overlays/extensions.ts +54 -0
- package/src/interactive/overlays/help-reference.ts +145 -0
- package/src/interactive/overlays/interop.ts +139 -0
- package/src/interactive/overlays/keybinding-detail.ts +90 -0
- package/src/interactive/overlays/list-overlay.ts +606 -0
- package/src/interactive/overlays/message-picker.ts +155 -0
- package/src/interactive/overlays/model-selector.ts +1109 -0
- package/src/interactive/overlays/prompts.ts +72 -0
- package/src/interactive/overlays/session-selector-search.ts +31 -0
- package/src/interactive/overlays/session-selector.ts +284 -0
- package/src/interactive/overlays/settings.ts +3451 -0
- package/src/interactive/overlays/skills-hub.ts +237 -0
- package/src/interactive/overlays/tree-selector.ts +422 -0
- package/src/interactive/permission-overlay.ts +170 -0
- package/src/interactive/render-trace.ts +534 -0
- package/src/interactive/renderers/branch-summary.ts +86 -0
- package/src/interactive/renderers/code-ink.ts +420 -0
- package/src/interactive/renderers/compaction-summary.ts +112 -0
- package/src/interactive/renderers/diff.ts +137 -0
- package/src/interactive/renderers/mermaid.ts +53 -0
- package/src/interactive/renderers/notice.ts +28 -0
- package/src/interactive/renderers/retry-status.ts +31 -0
- package/src/interactive/renderers/structured.ts +89 -0
- package/src/interactive/renderers/tool-execution.ts +1357 -0
- package/src/interactive/renderers/worker-entry.ts +280 -0
- package/src/interactive/session-last-turn.ts +186 -0
- package/src/interactive/session-switch-settlement.ts +10 -0
- package/src/interactive/session-transcript.ts +105 -0
- package/src/interactive/session-usage-reseed.ts +74 -0
- package/src/interactive/slash-autocomplete.ts +256 -0
- package/src/interactive/slash-commands.ts +1407 -0
- package/src/interactive/slash-spec.ts +424 -0
- package/src/interactive/status/controller.ts +260 -0
- package/src/interactive/status/index.ts +23 -0
- package/src/interactive/status/state-machine.ts +530 -0
- package/src/interactive/status/summary.ts +170 -0
- package/src/interactive/status/types.ts +121 -0
- package/src/interactive/status/verbs.ts +163 -0
- package/src/interactive/status/watchdog.ts +24 -0
- package/src/interactive/stdout-backpressure.ts +99 -0
- package/src/interactive/stream-pacer.ts +530 -0
- package/src/interactive/stream-pacing-policy.ts +66 -0
- package/src/interactive/tasks-overlay.ts +540 -0
- package/src/interactive/terminal-lease.ts +485 -0
- package/src/interactive/theme/components.ts +55 -0
- package/src/interactive/theme/glyphs.ts +70 -0
- package/src/interactive/theme/index.ts +15 -0
- package/src/interactive/theme/labels.ts +88 -0
- package/src/interactive/theme/rules.ts +138 -0
- package/src/interactive/theme/segments.ts +88 -0
- package/src/interactive/theme/tokens.ts +121 -0
- package/src/interactive/thinking-level-policy.ts +6 -0
- package/src/interactive/tool-prose-loop.ts +122 -0
- package/src/interactive/tool-prose-registration.ts +48 -0
- package/src/interactive/turn-context.ts +855 -0
- package/src/interactive/turn-middleware.ts +283 -0
- package/src/interactive/turn-persistence.ts +458 -0
- package/src/interactive/turn-queues.ts +181 -0
- package/src/interactive/turn-recovery.ts +323 -0
- package/src/interactive/turn-runtime.ts +869 -0
- package/src/interactive/turn-state.ts +133 -0
- package/src/interactive/view/artifacts.ts +1452 -0
- package/src/interactive/view/view-overlay.ts +735 -0
- package/src/interactive/welcome-dashboard.ts +537 -0
- package/src/interactive/worker-receipts.ts +143 -0
- package/src/interactive/worker-replay.ts +111 -0
- package/src/interactive/worker-share.ts +100 -0
- package/src/interactive/worker-stream.ts +446 -0
- package/src/interactive/workspace-facts.ts +141 -0
- package/src/tools/agent-tools.ts +402 -0
- package/src/tools/artifact.ts +103 -0
- package/src/tools/ask-user.ts +666 -0
- package/src/tools/bash.ts +204 -0
- package/src/tools/bootstrap.ts +77 -0
- package/src/tools/builtin-tool-catalog.ts +265 -0
- package/src/tools/codewiki/code-nav-surface.ts +29 -0
- package/src/tools/codewiki/code-nav.ts +549 -0
- package/src/tools/codewiki/shared.ts +118 -0
- package/src/tools/compete-worktrees.ts +898 -0
- package/src/tools/context/docs-engine.ts +630 -0
- package/src/tools/context/index.ts +514 -0
- package/src/tools/context/surface.ts +19 -0
- package/src/tools/core-bootstrap.ts +168 -0
- package/src/tools/credential-present.ts +151 -0
- package/src/tools/dispatch-admission.ts +533 -0
- package/src/tools/dispatch-arguments.ts +228 -0
- package/src/tools/dispatch-background.ts +54 -0
- package/src/tools/dispatch-event-text.ts +6 -0
- package/src/tools/dispatch-plan.ts +746 -0
- package/src/tools/dispatch-run-events.ts +238 -0
- package/src/tools/dispatch-runner.ts +2370 -0
- package/src/tools/dispatch-scout-admission.ts +295 -0
- package/src/tools/dispatch-scout.ts +461 -0
- package/src/tools/dispatch-types.ts +77 -0
- package/src/tools/dispatch.ts +244 -0
- package/src/tools/edit-diff.ts +534 -0
- package/src/tools/edit.ts +128 -0
- package/src/tools/executables.ts +22 -0
- package/src/tools/file-mutation-queue.ts +35 -0
- package/src/tools/find.ts +294 -0
- package/src/tools/grep.ts +495 -0
- package/src/tools/ignore-policy.ts +169 -0
- package/src/tools/lazy-tool.ts +60 -0
- package/src/tools/ledger.ts +206 -0
- package/src/tools/ls.ts +110 -0
- package/src/tools/monitor-surface.ts +36 -0
- package/src/tools/monitor.ts +767 -0
- package/src/tools/network-policy.ts +29 -0
- package/src/tools/observation.ts +421 -0
- package/src/tools/observers.ts +99 -0
- package/src/tools/path-utils.ts +79 -0
- package/src/tools/policy.ts +154 -0
- package/src/tools/profiles.ts +108 -0
- package/src/tools/read.ts +173 -0
- package/src/tools/registry.ts +1169 -0
- package/src/tools/result-shaping.ts +204 -0
- package/src/tools/safe-exec.ts +128 -0
- package/src/tools/spawn-hygiene.ts +182 -0
- package/src/tools/steer-surface.ts +17 -0
- package/src/tools/steer.ts +77 -0
- package/src/tools/tasks.ts +265 -0
- package/src/tools/truncate-utf8.ts +17 -0
- package/src/tools/truncate.ts +43 -0
- package/src/tools/verify/frontend.ts +687 -0
- package/src/tools/verify/index.ts +31 -0
- package/src/tools/verify/scripts.ts +108 -0
- package/src/tools/verify/surface.ts +46 -0
- package/src/tools/web-fetch-surface.ts +23 -0
- package/src/tools/web-fetch.ts +759 -0
- package/src/tools/worker-evidence.ts +215 -0
- package/src/tools/write.ts +57 -0
- package/src/utils/exif-orientation.ts +192 -0
- package/src/utils/git.ts +24 -0
- package/src/utils/image-resize.ts +148 -0
- package/src/utils/photon.ts +103 -0
- package/src/worker/control-lane.ts +22 -0
- package/src/worker/entry.ts +294 -0
- package/src/worker/event-projection.ts +56 -0
- package/src/worker/heartbeat.ts +24 -0
- package/src/worker/ledger-mirror.ts +95 -0
- package/src/worker/ndjson.ts +40 -0
- package/src/worker/protocol.ts +529 -0
- package/src/worker/resource-facts.ts +79 -0
- package/src/worker/runtime-registry.ts +12 -0
- package/src/worker/spec-contract.ts +675 -0
- package/src/worker/stdin-demux.ts +316 -0
- package/dist/acp-UMLFVA3F.js +0 -92
- package/dist/chunk-262G75JS.js +0 -35
- package/dist/chunk-6CWDTGUC.js +0 -20
- package/dist/chunk-BYEU76JP.js +0 -899
- package/dist/chunk-GNGDQYDU.js +0 -34688
- package/dist/chunk-GOTUR54M.js +0 -9
- package/dist/chunk-NI4DEJMC.js +0 -88
- package/dist/chunk-TPOCL34A.js +0 -2876
- package/dist/chunk-UU3R62TT.js +0 -128
- package/dist/clio-KIQ5SNDS.js +0 -53
- package/dist/configure-G4E3A2PG.js +0 -27
- package/dist/doctor-M5HJJZOL.js +0 -61
- package/dist/domains/agents/builtins/architect.md +0 -33
- package/dist/domains/agents/builtins/coder.md +0 -31
- package/dist/domains/agents/builtins/context-bootstrap.md +0 -38
- package/dist/domains/agents/builtins/debugger.md +0 -30
- package/dist/domains/agents/builtins/documenter.md +0 -31
- package/dist/domains/agents/builtins/git-master.md +0 -30
- package/dist/domains/agents/builtins/provenance.md +0 -30
- package/dist/domains/agents/builtins/researcher.md +0 -71
- package/dist/domains/agents/builtins/scout.md +0 -42
- package/dist/domains/agents/builtins/tester.md +0 -31
- package/dist/domains/agents/builtins/verifier.md +0 -30
- package/dist/domains/agents/builtins/wiki-writer.md +0 -41
- package/dist/fleet-preflight-CLIAX7YR.js +0 -21
- package/dist/preload-KZVHET2B.js +0 -11
- package/dist/version-ROZ6CZKH.js +0 -16
- package/docs/html/agents_blueprint.html +0 -936
- package/docs/html/alcf_blueprint.html +0 -324
- package/docs/html/architecture_blueprint.html +0 -850
- package/docs/html/commands_blueprint.html +0 -794
- package/docs/html/config_knobs_audit_blueprint.html +0 -178
- package/docs/html/configuration_blueprint.html +0 -1080
- package/docs/html/context_blueprint.html +0 -603
- package/docs/html/documentation_blueprint.html +0 -832
- package/docs/html/environment_blueprint.html +0 -404
- package/docs/html/eval_blueprint.html +0 -743
- package/docs/html/evals_internal_blueprint.html +0 -190
- package/docs/html/evolution_blueprint.html +0 -674
- package/docs/html/extensions_blueprint.html +0 -2065
- package/docs/html/fleet_dispatch_blueprint.html +0 -286
- package/docs/html/index.html +0 -919
- package/docs/html/lifecycle_blueprint.html +0 -723
- package/docs/html/memory_blueprint.html +0 -699
- package/docs/html/middleware_blueprint.html +0 -664
- package/docs/html/models_blueprint.html +0 -2366
- package/docs/html/observability_blueprint.html +0 -683
- package/docs/html/provider_adapter_blueprint.html +0 -245
- package/docs/html/safety_blueprint.html +0 -1386
- package/docs/html/shared.css +0 -571
- package/docs/html/shared.js +0 -143
- package/docs/html/skills_blueprint.html +0 -671
- package/docs/html/soak_blueprint.html +0 -182
- package/docs/html/tool_usage_blueprint.html +0 -350
- package/docs/html/tools_blueprint.html +0 -2249
- package/docs/html/trace_blueprint.html +0 -235
- package/docs/html/tui_design_blueprint.html +0 -314
- package/docs/html/validation_blueprint.html +0 -961
- package/docs/html/worker_dispatch_blueprint.html +0 -231
- package/skills/git/commit-crafting/SKILL.md +0 -79
- package/skills/git/commit-crafting/evals.md +0 -92
- package/skills/git/create-pr/SKILL.md +0 -116
- package/skills/git/create-pr/evals.md +0 -114
- package/skills/git/investigate-issue/SKILL.md +0 -139
- package/skills/git/investigate-issue/evals.md +0 -94
- package/skills/git/review-changes/SKILL.md +0 -103
- 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
|
+
}
|