@iowarp/clio-coder 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/README.md +116 -16
- package/dist/{acp-G5WJBNCT.js → acp-ZILU3AUO.js} +14 -14
- package/dist/{agents-FMV2Q5G4.js → agents-HYWGBGQR.js} +40 -29
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-3IDSJEIK.js → auth-N3QT7CBO.js} +21 -19
- package/dist/{builtins-XCZWXSC7.js → builtins-UJLMOVOV.js} +6 -6
- package/dist/{chunk-PQEFIJ36.js → chunk-2LEAKD4C.js} +1 -2
- package/dist/{chunk-3BT2XMV4.js → chunk-2NM363SV.js} +50 -57
- package/dist/{chunk-J4W7KFM7.js → chunk-2VG7KLYV.js} +24 -279
- package/dist/{chunk-SST6Z5JA.js → chunk-336NSSKI.js} +1 -1
- package/dist/{chunk-K5C3NCBD.js → chunk-3I5NY75V.js} +5 -5
- package/dist/{chunk-VAZSBTKF.js → chunk-3QSOM6PA.js} +2 -2
- package/dist/{chunk-TZ3SGWZZ.js → chunk-5QIAJV2D.js} +2 -2
- package/dist/{chunk-OQBA45DZ.js → chunk-5YHDIDBP.js} +2 -2
- package/dist/{chunk-NHLBIGRH.js → chunk-6CCS4G3W.js} +40 -17
- package/dist/{chunk-2JDWVJND.js → chunk-6DWBAZ5U.js} +2 -2
- package/dist/{chunk-LDQ2ZF2M.js → chunk-6FN3E6KX.js} +5 -5
- package/dist/{chunk-X3YGUTOB.js → chunk-6HMJX2VU.js} +59 -59
- package/dist/{chunk-VEO4AP2K.js → chunk-6I5ILFOF.js} +3 -2
- package/dist/{chunk-B7OBL7PK.js → chunk-6NJQITNH.js} +18 -11
- package/dist/{chunk-IQ7KR472.js → chunk-6VC4OV3Z.js} +6 -3
- package/dist/{chunk-P3FOHJT4.js → chunk-72GZI5EV.js} +2 -2
- package/dist/{chunk-AB44T6BB.js → chunk-74YWRRU5.js} +8 -7
- package/dist/{chunk-SJ5ZKQ4S.js → chunk-7EPLI7VL.js} +2 -2
- package/dist/{chunk-HWHKMHUA.js → chunk-7OBGU7UB.js} +3 -3
- package/dist/{chunk-2OQE55CK.js → chunk-7PWAODYW.js} +2 -2
- package/dist/{chunk-IBEBSCYA.js → chunk-7RY5VZPH.js} +42 -33
- package/dist/{chunk-LQ3DZAMX.js → chunk-ABLSQ6JX.js} +2 -2
- package/dist/{chunk-QLFS5GO2.js → chunk-AFKWHWXF.js} +12 -22
- package/dist/{chunk-FWDFM5ZU.js → chunk-AK5XEFVZ.js} +3 -3
- package/dist/{chunk-O6TL7WWY.js → chunk-AKB4GYDL.js} +4 -4
- package/dist/chunk-AP73CFDC.js +308 -0
- package/dist/{chunk-JDG2WCRO.js → chunk-BBTJOK6Y.js} +5 -5
- package/dist/{chunk-SP2RXXYO.js → chunk-BDPT6GTK.js} +10 -10
- package/dist/{chunk-NQQH3YT7.js → chunk-BJGUKIG4.js} +3 -3
- package/dist/{chunk-O4XIVISU.js → chunk-BTGG6BG2.js} +79 -38
- package/dist/chunk-BTK3SSSK.js +67 -0
- package/dist/{chunk-M6L6IDJG.js → chunk-BWH6N36D.js} +1 -1
- package/dist/chunk-BWW4HLO4.js +301 -0
- package/dist/{chunk-I5VEOC6I.js → chunk-C537JADH.js} +10 -10
- package/dist/{chunk-HI63TFOG.js → chunk-CDNVLKUX.js} +3 -3
- package/dist/{chunk-UM7N4G5A.js → chunk-CWVRRIEI.js} +2 -2
- package/dist/{chunk-BKFM6EJV.js → chunk-D4CJV46V.js} +2 -2
- package/dist/chunk-D7E7WCDF.js +27 -0
- package/dist/{chunk-WZR7K7ZX.js → chunk-E67WX76H.js} +6 -6
- package/dist/{chunk-FQ4SKYE4.js → chunk-EBY63B7G.js} +2 -5
- package/dist/chunk-EIMVLWB3.js +1842 -0
- package/dist/{chunk-RAPCMZL4.js → chunk-FSP7CMNU.js} +9 -1
- package/dist/{chunk-MLKNTWH2.js → chunk-FYUN5KZ3.js} +21 -21
- package/dist/{chunk-YJX4SHTD.js → chunk-GIZNH63R.js} +1 -1
- package/dist/{chunk-X75E3D2N.js → chunk-GPPB3JBE.js} +18 -16
- package/dist/{chunk-GR5G2PVF.js → chunk-GVQJ5CCZ.js} +15 -10
- package/dist/chunk-HJWWJ6IL.js +184 -0
- package/dist/{chunk-VO67MWHC.js → chunk-HRQMFLJP.js} +3 -3
- package/dist/{chunk-VYMXRQI6.js → chunk-HUAS7ITX.js} +89 -93
- package/dist/{chunk-BKFJHQCA.js → chunk-I66EAJFY.js} +2 -2
- package/dist/{chunk-DYJP44XW.js → chunk-IG7BCQBA.js} +1 -1
- package/dist/{chunk-32KWKNSF.js → chunk-IXJT6DCX.js} +3 -3
- package/dist/{chunk-C2LTL2W6.js → chunk-J4HBWF6Y.js} +360 -172
- package/dist/chunk-J5LZHVIT.js +284 -0
- package/dist/{chunk-QLL7ILRG.js → chunk-JA5QWE4Z.js} +510 -202
- package/dist/{chunk-BBVJUZHB.js → chunk-JIEGK6UF.js} +5 -173
- package/dist/{chunk-36EJLSQQ.js → chunk-JZWT5J3Y.js} +4 -4
- package/dist/{chunk-HZHHCK24.js → chunk-K6BF4U2H.js} +476 -322
- package/dist/{chunk-Z2RR6MAK.js → chunk-MA3H6DM5.js} +6 -6
- package/dist/{chunk-CODPRO7Q.js → chunk-MOPSG2X7.js} +6 -6
- package/dist/{chunk-76ONBSIA.js → chunk-MQXIVJ35.js} +14 -5
- package/dist/{chunk-YDFRH54B.js → chunk-N5UK64DP.js} +4 -4
- package/dist/chunk-NDDGJUXD.js +81 -0
- package/dist/{chunk-4ZG3XFUR.js → chunk-NLFAQR7Z.js} +12 -1
- package/dist/{chunk-VO2LKSTM.js → chunk-O42A54GG.js} +2 -2
- package/dist/{chunk-W5VSYASO.js → chunk-OJTRZGR3.js} +46 -87
- package/dist/{chunk-NHCZP4K7.js → chunk-PBP4B7XR.js} +2 -2
- package/dist/{chunk-3URVFKWK.js → chunk-PJJ6MY27.js} +5 -5
- package/dist/{chunk-ZGVHUX3M.js → chunk-PMZVFWSR.js} +2 -2
- package/dist/{chunk-GKF55TAZ.js → chunk-PVAMAVBB.js} +3 -2
- package/dist/{chunk-6DB53AJS.js → chunk-QKIFBZKT.js} +7 -7
- package/dist/{chunk-HJJTYUHX.js → chunk-QMXC4JB7.js} +20 -20
- package/dist/chunk-QTFGO774.js +644 -0
- package/dist/{chunk-CY6FY24N.js → chunk-R23Z6K6I.js} +2 -2
- package/dist/{chunk-ULC6OTWO.js → chunk-R32CLGZ6.js} +13 -13
- package/dist/{chunk-KMVISBZR.js → chunk-RKSR6VSF.js} +1 -1
- package/dist/{chunk-F4CKPOEQ.js → chunk-SCYB3HA4.js} +72 -26
- package/dist/{chunk-LY4S7GJC.js → chunk-SINK3QR6.js} +18 -16
- package/dist/{chunk-U77AMWDL.js → chunk-TD3PGPQA.js} +43 -22
- package/dist/{chunk-QBJA7R7N.js → chunk-THYWACCR.js} +2 -2
- package/dist/{chunk-QREDIESB.js → chunk-TVH4ONAM.js} +14 -8
- package/dist/chunk-U2WB7TZS.js +271 -0
- package/dist/{chunk-KFZI4NIL.js → chunk-UAPGZHYC.js} +145 -31
- package/dist/{chunk-DQITNCXG.js → chunk-UEDMSP56.js} +29 -14
- package/dist/{chunk-3XML7CDN.js → chunk-UXN6JT4W.js} +2 -2
- package/dist/{chunk-CMZWFGD2.js → chunk-VAVR4IJY.js} +19 -15
- package/dist/{chunk-BPKCPIL7.js → chunk-VEGN6WIQ.js} +2 -2
- package/dist/chunk-VKFQTNDV.js +66 -0
- package/dist/{chunk-7MCTRUCE.js → chunk-VKRH2TCS.js} +2 -2
- package/dist/{chunk-5LXZXPKX.js → chunk-VN3SHNBN.js} +2 -2
- package/dist/{chunk-UOSL25KY.js → chunk-VXMFAE2W.js} +1 -1
- package/dist/{chunk-BBVYXMFO.js → chunk-WNP7O5WZ.js} +2 -2
- package/dist/{chunk-SUCTJL45.js → chunk-WR6U3OVP.js} +3 -3
- package/dist/{chunk-BUMFYQFY.js → chunk-XKA2ICR3.js} +124 -113
- package/dist/{chunk-UXMFQ54G.js → chunk-Y45G3AXC.js} +39 -39
- package/dist/{chunk-J5Q24KAG.js → chunk-Y4CAGMM6.js} +2 -2
- package/dist/{chunk-NYS75XW5.js → chunk-YAKGTXEV.js} +5 -3
- package/dist/{chunk-V5DHCITQ.js → chunk-ZAZB4JMW.js} +23 -12
- package/dist/{chunk-Z4TXYIEG.js → chunk-ZJLUDYFY.js} +9 -9
- package/dist/{chunk-HKIYEGME.js → chunk-ZTJUVLTU.js} +2 -2
- package/dist/{chunk-2ANTL7MR.js → chunk-ZW55JB7N.js} +2 -2
- package/dist/cli/index.js +36 -36
- package/dist/{clio-2JXHBBY5.js → clio-IT3G3VQH.js} +12 -13
- package/dist/{code-nav-3YYRMYNF.js → code-nav-RK6S7F6E.js} +5 -5
- package/dist/codewiki/build-worker.js +1 -1
- package/dist/{compile-cache-7FPE6PS3.js → compile-cache-OQAVB7Y5.js} +2 -4
- package/dist/{components-RYZV4JGP.js → components-UBWCQSRW.js} +4 -4
- package/dist/{config-QZPCMYSO.js → config-3QZRWZJF.js} +53 -49
- package/dist/{configure-TEGEBYCA.js → configure-FL7Y3KJF.js} +25 -22
- package/dist/{context-E6H5RNMC.js → context-5HE7ODYK.js} +47 -43
- package/dist/{context-AV7OEZ4D.js → context-KYQFRVDC.js} +11 -11
- package/dist/{context-GSXUE4CT.js → context-XNHL75JV.js} +32 -19
- package/dist/{context-clear-SHIBYK6T.js → context-clear-N545L53A.js} +47 -43
- package/dist/{context-working-set-5ZGKPGZQ.js → context-working-set-QHKXSV2F.js} +10 -9
- package/dist/{dispatch-runner-EFMJT4LD.js → dispatch-runner-RGIE5PCT.js} +56 -47
- package/dist/{docs-23KQS3XK.js → docs-5NAF6AU7.js} +5 -11
- package/dist/doctor-ZGPEGHIP.js +772 -0
- package/dist/{eval-TFBYQH4H.js → eval-GXLL44RD.js} +63 -45
- package/dist/{eval-inventory-SXH7PDKX.js → eval-inventory-HBWSWQOK.js} +7 -8
- package/dist/{evidence-ERGESKGN.js → evidence-HWLBRH3Q.js} +44 -40
- package/dist/{evolve-VDXTSYCJ.js → evolve-FTZBMNVW.js} +43 -39
- package/dist/{extensions-7BGBHN57.js → extensions-VHRBEID7.js} +4 -4
- package/dist/{fleet-2RRVDF2V.js → fleet-CKZHJWZJ.js} +86 -73
- package/dist/{fleet-commands-VJ726XIA.js → fleet-commands-EXDXBMV6.js} +10 -9
- package/dist/{fleet-decisions-EPAPM3XJ.js → fleet-decisions-OTHB6KRL.js} +5 -5
- package/dist/{fleet-graph-JF5QOATM.js → fleet-graph-YTEZUCUT.js} +13 -11
- package/dist/{fleet-inspect-VLY4S7QM.js → fleet-inspect-SS6YMDCK.js} +44 -41
- package/dist/{fleet-preflight-AIZUEJOY.js → fleet-preflight-PBY4VYOM.js} +5 -9
- package/dist/{fleet-validate-AJRPDMDV.js → fleet-validate-KMEM5L3S.js} +16 -13
- package/dist/{fleet-verify-JFEL2L3H.js → fleet-verify-QD5M7E7Q.js} +42 -39
- package/dist/{fleet-view-ZCON35AG.js → fleet-view-WAMJYNDT.js} +44 -52
- package/dist/{init-DN2WWLFE.js → init-5XQRBOFV.js} +63 -58
- package/dist/{install-XGLBQY5E.js → install-NKNG7KD4.js} +2 -2
- package/dist/{interop-OZBKXAYL.js → interop-34TVO25M.js} +13 -11
- package/dist/{library-YWZG7IMW.js → library-3QY6KF57.js} +17 -14
- package/dist/{memory-I4C4HMLW.js → memory-L4UTIIIW.js} +45 -41
- package/dist/{models-CEYXJBO6.js → models-ZVX3QOWE.js} +39 -31
- package/dist/{monitor-NZ6GCI3P.js → monitor-CEKVSYTS.js} +50 -46
- package/dist/{orchestrator-GCGQ4N5I.js → orchestrator-77BAP6BC.js} +1807 -1039
- package/dist/{panes-KY6W3V2E.js → panes-7STHOAUJ.js} +4 -4
- package/dist/{panes-HMABYVO4.js → panes-SHAUIRXY.js} +10 -8
- package/dist/{paths-II4K7DNR.js → paths-L7LGY6RN.js} +4 -4
- package/dist/{reset-DQ6FGCSH.js → reset-EOLM7GVE.js} +7 -8
- package/dist/{resources-BB3MVJMD.js → resources-74GKTLSF.js} +17 -13
- package/dist/{run-H2GQDUER.js → run-HBAUJNNZ.js} +86 -76
- package/dist/{share-GTJN6A5O.js → share-G3APVLVP.js} +17 -14
- package/dist/{skills-L55TEW6R.js → skills-35HHUKCR.js} +19 -16
- package/dist/{skills-eval-XVXPH2JI.js → skills-eval-QN4HSHDC.js} +55 -57
- package/dist/{skills-inventory-S4MXPJFV.js → skills-inventory-J357J34F.js} +15 -12
- package/dist/{slash-commands-ZSGASKJC.js → slash-commands-JZZCQA32.js} +31 -29
- package/dist/{steer-RZGSCY4R.js → steer-XAVHJM22.js} +3 -3
- package/dist/{support-PKEUNNQL.js → support-U7QOWY26.js} +6 -6
- package/dist/{targets-NCPZ644J.js → targets-DSM6CY3M.js} +59 -53
- package/dist/{terminal-lease-44SV3YCN.js → terminal-lease-JOPFUVEM.js} +14 -9
- package/dist/{tools-DAF3DI3C.js → tools-MKNWVPBH.js} +10 -8
- package/dist/{trace-FYVW2MQA.js → trace-ECQ7TIYZ.js} +48 -3
- package/dist/{tui-primitives-2AKXQNZK.js → tui-primitives-K5PASC3O.js} +8 -4
- package/dist/{uninstall-DW2PNOIC.js → uninstall-N34PCTGJ.js} +4 -6
- package/dist/{upgrade-3XPP6OQL.js → upgrade-H7TOM7YL.js} +35 -62
- package/dist/{usage-3NLHGTU2.js → usage-X52N3IDJ.js} +51 -48
- package/dist/{verifiers-SSQONKRT.js → verifiers-EJTVVSMA.js} +10 -9
- package/dist/{verify-3U6J7FZI.js → verify-YJL6XET2.js} +9 -8
- package/dist/{web-fetch-S7RR6GZ7.js → web-fetch-MPIFL3LL.js} +1 -2
- package/dist/{wiki-generate-CEHYGPGQ.js → wiki-generate-4NDZTQ4B.js} +66 -63
- package/dist/{with-panes-MKB46MPQ.js → with-panes-OBOBFIIR.js} +62 -26
- package/dist/worker/entry.js +57 -52
- package/docs/acp.md +1 -1
- package/docs/built-in-agents.md +40 -32
- package/docs/commands-and-modes.md +9 -3
- package/docs/config-knobs-audit.md +3 -0
- package/docs/configuration-and-targets.md +105 -164
- package/docs/environment-variables.md +2 -1
- package/docs/exit-codes-and-output.md +2 -0
- package/docs/fleet-demo-runbook.md +6 -3
- package/docs/fleet-dispatch.md +8 -6
- package/docs/installation-and-lifecycle.md +1 -1
- package/docs/release-cut-checklist.md +164 -177
- package/docs/trace-store.md +15 -10
- package/docs/troubleshooting.md +1 -1
- package/evals/README.md +5 -0
- package/evals/behavioral-corpus-grader.mjs +103 -0
- package/evals/behavioral-machinery-baseline.json +2276 -0
- package/evals/behavioral-machinery-driver.ts +250 -0
- package/evals/behavioral-machinery-support.ts +253 -0
- package/evals/behavioral-machinery.yaml +614 -0
- package/evals/behavioral-model-negative-control.yaml +68 -0
- package/evals/behavioral-model.yaml +216 -0
- package/evals/fixtures/behavioral-facts.txt +3 -0
- package/evals/fixtures/behavioral-main.ts +7 -0
- package/evals/fixtures/decoy/note.txt +2 -0
- package/evals/fixtures/private/credentials.txt +1 -0
- package/evals/tracked-metrics-baseline.yaml +229 -0
- package/evals/tracked-metrics-grader.mjs +129 -0
- package/package.json +5 -15
- package/skills/README.md +6 -6
- package/skills/coding/ast-grep/SKILL.md +8 -2
- package/skills/coding/coding-standards/SKILL.md +8 -2
- package/skills/coding/prototype/SKILL.md +8 -2
- package/skills/coding/tdd/SKILL.md +8 -2
- package/skills/context/context-handoff/SKILL.md +12 -3
- package/skills/context/context-handoff/evals.md +3 -3
- package/skills/context/context-prime/SKILL.md +8 -2
- package/skills/context/context-prime/evals.md +2 -2
- package/skills/git/file-ticket/SKILL.md +9 -4
- package/skills/git/file-ticket/evals.md +2 -2
- package/skills/git/fix-issue/SKILL.md +12 -5
- package/skills/git/fix-issue/evals.md +4 -4
- package/skills/git/resolve-merge-conflicts/SKILL.md +8 -2
- package/skills/git/resolve-merge-conflicts/evals.md +2 -2
- package/skills/git/ship/SKILL.md +9 -3
- package/skills/git/ship/evals.md +4 -4
- package/skills/git/worktree-create/SKILL.md +7 -2
- package/skills/git/worktree-create/evals.md +6 -6
- package/skills/git/worktree-merge/SKILL.md +7 -2
- package/skills/git/worktree-merge/evals.md +6 -6
- package/skills/meta/{clio-dev → clio-coder-dev}/SKILL.md +14 -8
- package/skills/meta/{clio-dev → clio-coder-dev}/evals.md +3 -3
- package/skills/meta/{clio-test → clio-coder-test}/SKILL.md +11 -5
- package/skills/meta/{clio-test → clio-coder-test}/evals.md +1 -1
- package/skills/meta/credentials/SKILL.md +9 -2
- package/skills/meta/find-skills/SKILL.md +8 -2
- package/skills/meta/herdr/SKILL.md +8 -2
- package/skills/meta/skill-craft/SKILL.md +21 -4
- package/skills/planning/architecture/SKILL.md +9 -3
- package/skills/planning/backlog/SKILL.md +8 -2
- package/skills/planning/prd/SKILL.md +8 -2
- package/skills/planning/product-intent/SKILL.md +8 -2
- package/skills/planning/tech-spec/SKILL.md +9 -3
- package/skills/registry.yaml +66 -66
- package/skills/research/arxiv-literature/SKILL.md +8 -2
- package/skills/research/experiment-protocol/SKILL.md +9 -2
- package/skills/research/scientific-debugging/SKILL.md +9 -2
- package/skills/research/scientific-debugging/evals.md +2 -2
- package/skills/research/scientific-modernization/SKILL.md +9 -2
- package/skills/skill-marketplace.json +257 -38
- package/skills/workflow/cut-it/SKILL.md +10 -2
- package/skills/workflow/design-council/SKILL.md +11 -2
- package/skills/workflow/grill-me/SKILL.md +8 -2
- package/skills/workflow/workflow-distiller/SKILL.md +8 -2
- package/src/cli/acp.ts +2 -2
- package/src/cli/auth.ts +2 -2
- package/src/cli/bootstrap-generate.ts +5 -5
- package/src/cli/clio.ts +6 -1
- package/src/cli/config-inspect.ts +3 -3
- package/src/cli/configure-interop.ts +1 -1
- package/src/cli/configure.ts +27 -27
- package/src/cli/default-target.ts +1 -1
- package/src/cli/docs.ts +6 -6
- package/src/cli/doctor-naming.ts +323 -0
- package/src/cli/doctor-panes.ts +46 -14
- package/src/cli/doctor-toolchain.ts +29 -8
- package/src/cli/doctor.ts +15 -1
- package/src/cli/eval-inventory.ts +8 -8
- package/src/cli/evidence-detail.ts +1 -1
- package/src/cli/evidence-inventory.ts +1 -1
- package/src/cli/fleet-decisions.ts +1 -1
- package/src/cli/fleet-inspect.ts +1 -1
- package/src/cli/fleet-preflight.ts +1 -1
- package/src/cli/fleet-verify.ts +1 -1
- package/src/cli/fleet-view.ts +40 -23
- package/src/cli/fleet.ts +8 -5
- package/src/cli/interop-inspect.ts +2 -2
- package/src/cli/models.ts +3 -3
- package/src/cli/modes/print.ts +1 -1
- package/src/cli/reset.ts +1 -1
- package/src/cli/run.ts +11 -15
- package/src/cli/share.ts +1 -1
- package/src/cli/skills-eval.ts +11 -11
- package/src/cli/targets.ts +23 -24
- package/src/cli/trace-inspect.ts +1 -1
- package/src/cli/trace.ts +41 -0
- package/src/cli/uninstall.ts +2 -2
- package/src/cli/upgrade.ts +29 -34
- package/src/cli/usage.ts +2 -2
- package/src/cli/validate-model.ts +1 -1
- package/src/cli/verifiers-inspect.ts +1 -1
- package/src/cli/wiki-generate.ts +5 -5
- package/src/core/bash-exec.ts +1 -1
- package/src/core/boot-trace.ts +3 -3
- package/src/core/bus-trace.ts +2 -2
- package/src/core/compile-cache.ts +1 -1
- package/src/core/config.ts +1062 -886
- package/src/core/defaults.ts +321 -528
- package/src/core/domain-loader.ts +6 -4
- package/src/core/event-bus.ts +1 -1
- package/src/core/file-references.ts +78 -88
- package/src/core/git-commit-attribution.ts +3 -9
- package/src/core/guardrails.ts +24 -4
- package/src/core/naming-compat.ts +45 -0
- package/src/core/naming-events.ts +62 -0
- package/src/core/recent-models.ts +1 -7
- package/src/core/residency-protection.ts +4 -8
- package/src/core/resolve-config-value.ts +1 -1
- package/src/core/response-model-id.ts +0 -14
- package/src/core/session-routing.ts +84 -74
- package/src/core/skill-activation.ts +18 -0
- package/src/core/termination.ts +4 -4
- package/src/core/timers.ts +2 -12
- package/src/domains/agents/catalog.ts +2 -12
- package/src/domains/agents/extension.ts +3 -3
- package/src/domains/agents/registry.ts +7 -7
- package/src/domains/agents/result-contract.ts +1 -1
- package/src/domains/agents/spec.ts +0 -11
- package/src/domains/config/classify.ts +35 -47
- package/src/domains/config/extension.ts +5 -5
- package/src/domains/config/keybindings.ts +48 -47
- package/src/domains/config/watcher.ts +1 -1
- package/src/domains/context/codewiki/indexer.ts +1 -1
- package/src/domains/context/extension.ts +3 -3
- package/src/domains/context/prompt-context.ts +1 -1
- package/src/domains/context/wiki/assemble.ts +2 -2
- package/src/domains/context/working-set/replay/metrics.ts +0 -4
- package/src/domains/context/working-set/replay/report.ts +1 -1
- package/src/domains/context/working-set/replay/synthetic.ts +2 -2
- package/src/domains/dispatch/active-route-planner.ts +2 -2
- package/src/domains/dispatch/agent-candidates.ts +1 -1
- package/src/domains/dispatch/agent-ledger-store.ts +1 -1
- package/src/domains/dispatch/agent-ledger.ts +1 -1
- package/src/domains/dispatch/assignment.ts +5 -5
- package/src/domains/dispatch/capacity-lease.ts +2 -5
- package/src/domains/dispatch/code-step.ts +1 -1
- package/src/domains/dispatch/contract.ts +2 -2
- package/src/domains/dispatch/extension.ts +65 -61
- package/src/domains/dispatch/fleet-node-prompt.ts +1 -1
- package/src/domains/dispatch/fleet-preflight.ts +8 -17
- package/src/domains/dispatch/fleet-run.ts +0 -7
- package/src/domains/dispatch/gate-decisions.ts +25 -14
- package/src/domains/dispatch/joint-route-resolver.ts +1 -1
- package/src/domains/dispatch/path-scope.ts +0 -61
- package/src/domains/dispatch/placement.ts +2 -2
- package/src/domains/dispatch/receipt-findings.ts +1 -1
- package/src/domains/dispatch/receipt-integrity.ts +20 -8
- package/src/domains/dispatch/reservation-store.ts +0 -23
- package/src/domains/dispatch/route-decision.ts +1 -1
- package/src/domains/dispatch/route-history.ts +1 -1
- package/src/domains/dispatch/route-policy.ts +1 -1
- package/src/domains/dispatch/route-readiness.ts +0 -28
- package/src/domains/dispatch/run-event-journal.ts +6 -5
- package/src/domains/dispatch/tool-stats.ts +1 -1
- package/src/domains/dispatch/transport.ts +2 -19
- package/src/domains/dispatch/types.ts +3 -3
- package/src/domains/dispatch/worker-protocol.ts +2 -2
- package/src/domains/eval/artifacts/store.ts +5 -4
- package/src/domains/eval/compare/thresholds.ts +0 -8
- package/src/domains/eval/execution-provenance.ts +1 -1
- package/src/domains/eval/metrics/token-stream.ts +0 -7
- package/src/domains/eval/report.ts +1 -1
- package/src/domains/eval/runner.ts +1 -1
- package/src/domains/eval/runners/clio-run.ts +7 -6
- package/src/domains/eval/runners/external-command.ts +4 -3
- package/src/domains/eval/schema/artifact.ts +1 -1
- package/src/domains/eval/schema/behavioral-metrics.ts +3 -2
- package/src/domains/eval/schema/behavioral.ts +7 -6
- package/src/domains/eval/schema/execution-envelope.ts +4 -2
- package/src/domains/eval/schema/naming.ts +10 -0
- package/src/domains/eval/schema/suite.ts +2 -2
- package/src/domains/eval/schema/validate.ts +9 -5
- package/src/domains/eval/schema/verdict.ts +4 -2
- package/src/domains/eval/store.ts +12 -2
- package/src/domains/eval/suites/run.ts +12 -6
- package/src/domains/eval/types.ts +1 -1
- package/src/domains/eval/workspaces/git.ts +1 -1
- package/src/domains/eval/workspaces/temp-copy.ts +1 -1
- package/src/domains/evidence/build.ts +13 -5
- package/src/domains/interop/consent.ts +7 -7
- package/src/domains/interop/extension.ts +1 -1
- package/src/domains/lifecycle/doctor.ts +27 -21
- package/src/domains/lifecycle/migrations/2026-09-01-clio-coder-naming.ts +155 -0
- package/src/domains/lifecycle/migrations/2026-09-01-retire-panes-knobs.ts +59 -0
- package/src/domains/lifecycle/migrations/2026-09-01-settings-v2.ts +338 -0
- package/src/domains/lifecycle/migrations/index.ts +20 -1
- package/src/domains/lifecycle/naming-history.ts +153 -0
- package/src/domains/lifecycle/naming-mutable-state.ts +104 -0
- package/src/domains/lifecycle/naming-resources.ts +261 -0
- package/src/domains/lifecycle/naming-tool-markers.ts +139 -0
- package/src/domains/lifecycle/naming-yazi.ts +81 -0
- package/src/domains/lifecycle/upgrade-notice.ts +1 -1
- package/src/domains/memory/task-memory-handoff.ts +6 -3
- package/src/domains/middleware/index.ts +7 -0
- package/src/domains/middleware/marketplace-offer.ts +339 -0
- package/src/domains/middleware/runtime.ts +2 -2
- package/src/domains/mux/contract.ts +157 -25
- package/src/domains/mux/detect.ts +29 -5
- package/src/domains/mux/dock-controller.ts +365 -0
- package/src/domains/mux/extension.ts +7 -1
- package/src/domains/mux/index.ts +20 -0
- package/src/domains/mux/operations.ts +28 -0
- package/src/domains/mux/protocol.ts +19 -6
- package/src/domains/mux/socket-client.ts +167 -19
- package/src/domains/mux/types.ts +49 -6
- package/src/domains/mux/yazi/event-stream.ts +6 -4
- package/src/domains/mux/yazi/profile.ts +1 -1
- package/src/domains/mux/yazi/session.ts +26 -3
- package/src/domains/observability/extension.ts +1 -1
- package/src/domains/observability/out-of-turn-usage.ts +2 -2
- package/src/domains/observability/projection.ts +1 -1
- package/src/domains/observability/trace-store.ts +22 -12
- package/src/domains/prompts/compiler.ts +2 -2
- package/src/domains/prompts/extension.ts +3 -3
- package/src/domains/prompts/fragments/identity/clio-worker.md +1 -1
- package/src/domains/prompts/fragments/operating/skills.md +25 -18
- package/src/domains/providers/extension.ts +5 -5
- package/src/domains/providers/knowledge-base-path.ts +1 -1
- package/src/domains/providers/model-runtime-capabilities.ts +14 -2
- package/src/domains/providers/models/cloud-models/alcf.yaml +1 -1
- package/src/domains/providers/models/local-models/{clio-local-coding-targets.yaml → clio-coder-local-coding-targets.yaml} +4 -4
- package/src/domains/providers/plugins.ts +1 -1
- package/src/domains/providers/resolver.ts +1 -1
- package/src/domains/providers/runtimes/cloud/alcf.ts +4 -4
- package/src/domains/providers/runtimes/common/lmstudio-http.ts +1 -5
- package/src/domains/providers/runtimes/common/local-synth.ts +3 -3
- package/src/domains/providers/runtimes/common/probe-helpers.ts +2 -2
- package/src/domains/providers/runtimes/local-native/llamacpp-completion.ts +1 -1
- package/src/domains/providers/runtimes/local-native/lmstudio.ts +2 -9
- package/src/domains/providers/runtimes/protocol/litellm.ts +1 -91
- package/src/domains/providers/types/knowledge-base.ts +13 -0
- package/src/domains/providers/types/local-model-quirks.ts +2 -2
- package/src/domains/providers/types/target-descriptor.ts +3 -3
- package/src/domains/resources/extension.ts +1 -1
- package/src/domains/resources/library.ts +6 -6
- package/src/domains/resources/prompts/loader.ts +1 -1
- package/src/domains/resources/skills/content-hash.ts +7 -6
- package/src/domains/resources/skills/evals.ts +1 -1
- package/src/domains/resources/skills/install.ts +36 -12
- package/src/domains/resources/skills/loader.ts +39 -13
- package/src/domains/resources/skills/marketplace.ts +21 -2
- package/src/domains/resources/skills/promotion.ts +214 -0
- package/src/domains/safety/audit.ts +2 -2
- package/src/domains/safety/finish-contract.ts +2 -1
- package/src/domains/safety/run-effects.ts +1 -1
- package/src/domains/scheduling/extension.ts +4 -4
- package/src/domains/session/archive-readers.ts +9 -1
- package/src/domains/session/compaction/branch-summary.ts +4 -0
- package/src/domains/session/compaction/compact.ts +1 -1
- package/src/domains/session/compaction/tokens.ts +4 -0
- package/src/domains/session/context-accounting.ts +1 -1
- package/src/domains/session/decision-board.ts +1 -1
- package/src/domains/session/extension.ts +1 -1
- package/src/domains/session/handoff.ts +2 -2
- package/src/domains/session/prompt-manifest.ts +0 -5
- package/src/domains/session/protected-artifact-journal.ts +12 -6
- package/src/domains/session/retry.ts +1 -1
- package/src/domains/share/archive.ts +85 -36
- package/src/domains/toolchain/install.ts +1 -1
- package/src/engine/acp/adapter.ts +3 -3
- package/src/engine/acp/event-mapper.ts +3 -3
- package/src/engine/acp/server.ts +27 -26
- package/src/engine/acp/tool-mediator.ts +2 -2
- package/src/engine/acp/types.ts +1 -1
- package/src/engine/alcf-oauth.ts +5 -5
- package/src/engine/antigravity/subprocess-runtime.ts +2 -2
- package/src/engine/api-registry.ts +1 -1
- package/src/engine/apis/llamacpp-residency.ts +1 -19
- package/src/engine/apis/lmstudio.ts +1 -6
- package/src/engine/apis/ollama-native.ts +3 -3
- package/src/engine/apis/openai-completions.ts +2 -2
- package/src/engine/apis/residency.ts +3 -9
- package/src/engine/claude/sdk-module.ts +1 -12
- package/src/engine/claude/sdk-runtime.ts +6 -6
- package/src/engine/claude/subprocess-runtime.ts +1 -1
- package/src/engine/claude/tool-safety.ts +4 -4
- package/src/engine/loop-guard.ts +0 -5
- package/src/engine/session.ts +14 -7
- package/src/engine/tui-primitives.ts +1 -1
- package/src/engine/worker-events.ts +6 -6
- package/src/engine/worker-runtime.ts +14 -13
- package/src/entry/orchestrator.ts +107 -88
- package/src/interactive/application-controller.ts +21 -21
- package/src/interactive/bus-notices.ts +2 -2
- package/src/interactive/chat-loop.ts +8 -8
- package/src/interactive/chat-panel.ts +1 -1
- package/src/interactive/clio-editor.ts +105 -1
- package/src/interactive/context-activity.ts +8 -6
- package/src/interactive/context-meter.ts +3 -6
- package/src/interactive/context-overlay.ts +1 -1
- package/src/interactive/cost-overlay.ts +1 -1
- package/src/interactive/council-grid.ts +2 -2
- package/src/interactive/dispatch-board.ts +30 -33
- package/src/interactive/editor-bash.ts +21 -0
- package/src/interactive/editor-steer.ts +9 -4
- package/src/interactive/editor-submit.ts +24 -18
- package/src/interactive/external-editor.ts +1 -1
- package/src/interactive/file-reference-completion.ts +360 -0
- package/src/interactive/footer/dashboard.ts +15 -17
- package/src/interactive/footer/notifications.ts +14 -8
- package/src/interactive/footer/widgets.ts +2 -44
- package/src/interactive/interactive-application.ts +79 -59
- package/src/interactive/interactive-event-projection.ts +1 -1
- package/src/interactive/interactive-input-runtime.ts +2 -2
- package/src/interactive/interactive-presentation.ts +21 -16
- package/src/interactive/interactive-shell.ts +2 -2
- package/src/interactive/interactive-slash-runtime.ts +63 -17
- package/src/interactive/interactive-subscriptions.ts +1 -1
- package/src/interactive/interactive-tickers.ts +1 -1
- package/src/interactive/keybinding-manager.ts +4 -4
- package/src/interactive/layout.ts +1 -1
- package/src/interactive/memory-overlay.ts +2 -2
- package/src/interactive/modal-marker.ts +2 -21
- package/src/interactive/mutation-preview.ts +1 -1
- package/src/interactive/overlay-frame.ts +4 -12
- package/src/interactive/overlay-general-openers.ts +6 -2
- package/src/interactive/overlay-key-routing.ts +7 -7
- package/src/interactive/overlay-lifecycle.ts +1 -1
- package/src/interactive/overlay-model-selectors.ts +3 -3
- package/src/interactive/overlays/agents.ts +2 -2
- package/src/interactive/overlays/ask-user.ts +25 -11
- package/src/interactive/overlays/context-reset.ts +28 -4
- package/src/interactive/overlays/cwd-fallback.ts +28 -4
- package/src/interactive/overlays/decisions.ts +4 -2
- package/src/interactive/overlays/fleet-run-approval.ts +3 -3
- package/src/interactive/overlays/handoff-review.ts +2 -2
- package/src/interactive/overlays/interop.ts +1 -1
- package/src/interactive/overlays/library-install-confirm.ts +1 -1
- package/src/interactive/overlays/message-picker.ts +1 -1
- package/src/interactive/overlays/model-selector.ts +39 -31
- package/src/interactive/overlays/prompts.ts +10 -4
- package/src/interactive/overlays/session-selector.ts +2 -2
- package/src/interactive/overlays/settings.ts +339 -293
- package/src/interactive/overlays/side-question.ts +2 -2
- package/src/interactive/overlays/skills-hub.ts +8 -8
- package/src/interactive/overlays/tree-selector.ts +2 -2
- package/src/interactive/panes-runtime.ts +36 -10
- package/src/interactive/permission-overlay.ts +2 -2
- package/src/interactive/renderers/retry-status.ts +12 -4
- package/src/interactive/renderers/tool-execution.ts +4 -4
- package/src/interactive/session-transcript.ts +3 -3
- package/src/interactive/slash-autocomplete.ts +454 -165
- package/src/interactive/slash-commands.ts +279 -183
- package/src/interactive/slash-spec.ts +160 -17
- package/src/interactive/status/controller.ts +2 -2
- package/src/interactive/stream-pacing-policy.ts +1 -1
- package/src/interactive/tasks-overlay.ts +20 -13
- package/src/interactive/terminal-lease.ts +6 -7
- package/src/interactive/turn-context.ts +11 -11
- package/src/interactive/turn-persistence.ts +2 -2
- package/src/interactive/turn-prewarm.ts +2 -2
- package/src/interactive/turn-runtime.ts +8 -8
- package/src/interactive/turn-state.ts +1 -1
- package/src/interactive/view/artifacts.ts +12 -9
- package/src/interactive/view/view-overlay.ts +13 -10
- package/src/interactive/watch-pane.ts +38 -10
- package/src/interactive/welcome-dashboard.ts +15 -15
- package/src/interactive/worker-progress.ts +4 -4
- package/src/interactive/worker-receipts.ts +1 -1
- package/src/interactive/worker-stream.ts +1 -4
- package/src/interactive/yazi-bridge.ts +12 -5
- package/src/tools/ask-user.ts +3 -3
- package/src/tools/bash.ts +1 -1
- package/src/tools/builtin-tool-catalog.ts +1 -1
- package/src/tools/compete-worktrees.ts +27 -17
- package/src/tools/context/index.ts +15 -1
- package/src/tools/dispatch-admission.ts +4 -2
- package/src/tools/dispatch-arguments.ts +1 -1
- package/src/tools/dispatch-plan.ts +0 -11
- package/src/tools/dispatch-run-events.ts +12 -10
- package/src/tools/dispatch-runner.ts +11 -6
- package/src/tools/dispatch.ts +1 -1
- package/src/tools/find.ts +1 -6
- package/src/tools/monitor.ts +4 -4
- package/src/tools/observers.ts +0 -33
- package/src/tools/task-worktree.ts +28 -7
- package/src/tools/verify/authoring.ts +3 -6
- package/src/tools/verify/frontend.ts +1 -1
- package/src/tools/web-fetch.ts +1 -1
- package/src/worker/entry.ts +4 -3
- package/src/worker/protocol.ts +16 -14
- package/src/worker/spec-contract.ts +2 -2
- package/src/worker/stdin-demux.ts +2 -2
- package/dist/chunk-GXNLGKAB.js +0 -571
- package/dist/chunk-VFA6GDY5.js +0 -1625
- package/dist/doctor-QOA5FNY5.js +0 -313
- package/src/engine/apis/degraded-inference.ts +0 -95
- /package/skills/meta/{clio-test → clio-coder-test}/references/harness.md +0 -0
- /package/skills/meta/{clio-test → clio-coder-test}/references/test-map.md +0 -0
package/src/core/config.ts
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
* loader has finished booting.
|
|
6
6
|
*
|
|
7
7
|
* There is exactly one schema: the file on disk and the in-memory shape use
|
|
8
|
-
* the same key names (`targets`, `
|
|
8
|
+
* the same version-2 key names (`targets`, `chat.target`, ...). Validation is
|
|
9
9
|
* strict: unknown keys and type violations are errors carrying the exact key
|
|
10
|
-
* path
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* path. Version-1 paths are non-executing tombstones with migration hints;
|
|
11
|
+
* lifecycle owns the one-time file migration. Missing keys take
|
|
12
|
+
* DEFAULT_SETTINGS values.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { existsSync, readFileSync } from "node:fs";
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
THINKING_LEVELS,
|
|
26
26
|
type ThinkingLevel,
|
|
27
27
|
} from "./defaults.js";
|
|
28
|
+
import { warnLegacyNaming } from "./naming-compat.js";
|
|
28
29
|
import { safeResourceWrite } from "./safe-resource-write.js";
|
|
29
30
|
import { withStateFileLockSync } from "./state-file-lock.js";
|
|
30
31
|
import { MAX_TIMER_DELAY_MS } from "./timers.js";
|
|
@@ -47,7 +48,7 @@ export function settingsPath(): string {
|
|
|
47
48
|
export type SettingsIssueKind = "unreadable" | "syntax" | "schema";
|
|
48
49
|
|
|
49
50
|
export interface SettingsIssue {
|
|
50
|
-
/** Dotted key path, e.g. `
|
|
51
|
+
/** Dotted key path, e.g. `chat.target` or `targets[2].runtime`. */
|
|
51
52
|
path: string;
|
|
52
53
|
message: string;
|
|
53
54
|
kind?: SettingsIssueKind;
|
|
@@ -206,6 +207,20 @@ class Issues {
|
|
|
206
207
|
if (!knownSet.has(key)) this.add(path ? `${path}.${key}` : key, "unknown key");
|
|
207
208
|
}
|
|
208
209
|
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* A key that used to be honored and is not any more. `unknown key` is the
|
|
213
|
+
* true answer for a typo and the wrong one here: the operator wrote a real
|
|
214
|
+
* key that a real release read, and the useful thing to tell them is what
|
|
215
|
+
* replaced it, not that the schema has never heard of it. The keys stay
|
|
216
|
+
* listed as known at their {@link unknownKeys} call so this message is the
|
|
217
|
+
* one that lands.
|
|
218
|
+
*/
|
|
219
|
+
retiredKeys(path: string, raw: Record<string, unknown>, retired: Readonly<Record<string, string>>): void {
|
|
220
|
+
for (const [key, because] of Object.entries(retired)) {
|
|
221
|
+
if (key in raw) this.add(path ? `${path}.${key}` : key, `retired: ${because}. Remove this key.`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
209
224
|
}
|
|
210
225
|
|
|
211
226
|
function expectString(issues: Issues, path: string, value: unknown): string | undefined {
|
|
@@ -332,7 +347,13 @@ const THINKING_FORMATS = [
|
|
|
332
347
|
] as const;
|
|
333
348
|
const STRUCTURED_OUTPUTS = ["json-schema", "gbnf", "xgrammar", "none"] as const;
|
|
334
349
|
const AUTONOMY_LEVELS = ["read-only", "suggest", "auto-edit", "full-auto"] as const;
|
|
335
|
-
const TOOL_GOVERNANCE = ["clio-policy", "agent-managed", "deny-all"] as const;
|
|
350
|
+
const TOOL_GOVERNANCE = ["clio-coder-policy", "agent-managed", "deny-all"] as const;
|
|
351
|
+
|
|
352
|
+
function normalizeLegacyNamingValue(value: unknown, legacy: string, canonical: string): unknown {
|
|
353
|
+
if (value !== legacy) return value;
|
|
354
|
+
warnLegacyNaming(legacy, canonical);
|
|
355
|
+
return canonical;
|
|
356
|
+
}
|
|
336
357
|
|
|
337
358
|
type TargetCapabilities = NonNullable<ClioSettings["targets"][number]["capabilities"]>;
|
|
338
359
|
|
|
@@ -552,7 +573,12 @@ function validateTarget(issues: Issues, path: string, value: unknown): ClioSetti
|
|
|
552
573
|
if (v !== undefined) target.capabilities = v;
|
|
553
574
|
}
|
|
554
575
|
if ("lifecycle" in value) {
|
|
555
|
-
const v = expectEnum(
|
|
576
|
+
const v = expectEnum(
|
|
577
|
+
issues,
|
|
578
|
+
`${path}.lifecycle`,
|
|
579
|
+
normalizeLegacyNamingValue(value.lifecycle, "clio-managed", "clio-coder-managed"),
|
|
580
|
+
["user-managed", "clio-coder-managed"] as const,
|
|
581
|
+
);
|
|
556
582
|
if (v !== undefined) target.lifecycle = v;
|
|
557
583
|
}
|
|
558
584
|
if ("gateway" in value) {
|
|
@@ -581,15 +607,231 @@ function validateTarget(issues: Issues, path: string, value: unknown): ClioSetti
|
|
|
581
607
|
* every session that still mentions it. This is referential normalization,
|
|
582
608
|
* not a legacy reader; the key names themselves are never aliased.
|
|
583
609
|
*/
|
|
584
|
-
|
|
610
|
+
type FleetRoute = ClioSettings["fleet"]["default"];
|
|
611
|
+
type ExternalAgentDefaults = ClioSettings["integrations"]["externalAgents"]["defaults"];
|
|
612
|
+
type ExternalAgent = ClioSettings["integrations"]["externalAgents"]["entries"][number];
|
|
613
|
+
type FleetNodeConfig = ClioSettings["fleet"]["nodes"][number];
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Complete durable path manifest for the settings-v1 to settings-v2 rename.
|
|
617
|
+
* `*` denotes a map key and `[]` denotes a list element. Values are copied
|
|
618
|
+
* without reinterpretation unless a separate retired-path entry says they are
|
|
619
|
+
* deliberately dropped.
|
|
620
|
+
*/
|
|
621
|
+
export const SETTINGS_V1_PATH_MOVES = Object.freeze([
|
|
622
|
+
["autonomy", "safety.autonomy"],
|
|
623
|
+
["orchestrator.target", "chat.target"],
|
|
624
|
+
["orchestrator.model", "chat.model"],
|
|
625
|
+
["orchestrator.thinkingLevel", "chat.thinkingLevel"],
|
|
626
|
+
["scope[]", "chat.modelPicker.cycleSet[]"],
|
|
627
|
+
["modelSelector.favorites[]", "chat.modelPicker.favorites[]"],
|
|
628
|
+
["modelSelector.recentLimit", "chat.modelPicker.recentLimit"],
|
|
629
|
+
["defaults.maxTokens", "chat.maxOutputTokens"],
|
|
630
|
+
["prewarm.enabled", "chat.prewarm"],
|
|
631
|
+
["retry.enabled", "chat.retry.enabled"],
|
|
632
|
+
["retry.maxRetries", "chat.retry.maxRetries"],
|
|
633
|
+
["retry.baseDelayMs", "chat.retry.baseDelayMs"],
|
|
634
|
+
["retry.maxDelayMs", "chat.retry.maxDelayMs"],
|
|
635
|
+
["retry.streamStallMs", "chat.retry.streamStallMs"],
|
|
636
|
+
["workers.default.target", "fleet.default.target"],
|
|
637
|
+
["workers.default.model", "fleet.default.model"],
|
|
638
|
+
["workers.default.thinkingLevel", "fleet.default.thinkingLevel"],
|
|
639
|
+
["workers.default.node", "fleet.default.node"],
|
|
640
|
+
["workers.profiles.*.target", "fleet.profiles.*.target"],
|
|
641
|
+
["workers.profiles.*.model", "fleet.profiles.*.model"],
|
|
642
|
+
["workers.profiles.*.thinkingLevel", "fleet.profiles.*.thinkingLevel"],
|
|
643
|
+
["workers.profiles.*.node", "fleet.profiles.*.node"],
|
|
644
|
+
["workers.rosters.*.members[].label", "fleet.rosters.*.members[].label"],
|
|
645
|
+
["workers.rosters.*.members[].target", "fleet.rosters.*.members[].target"],
|
|
646
|
+
["workers.rosters.*.members[].model", "fleet.rosters.*.members[].model"],
|
|
647
|
+
["workers.rosters.*.members[].thinking", "fleet.rosters.*.members[].thinkingLevel"],
|
|
648
|
+
["workers.rosters.*.members[].color", "fleet.rosters.*.members[].color"],
|
|
649
|
+
["workers.agentBindings.*", "fleet.agentProfiles.*"],
|
|
650
|
+
["routing.activeRoles[]", "fleet.adaptiveRouting.roles[]"],
|
|
651
|
+
["routing.activePostures[]", "fleet.adaptiveRouting.postures[]"],
|
|
652
|
+
["routing.agentAutomation.activeAgentRoles[].agentId", "fleet.adaptiveRouting.agentRoles[].agentId"],
|
|
653
|
+
["routing.agentAutomation.activeAgentRoles[].executionRole", "fleet.adaptiveRouting.agentRoles[].executionRole"],
|
|
654
|
+
["budget.concurrency", "fleet.concurrency"],
|
|
655
|
+
["workers.maxRetries", "fleet.retry.maxRetries"],
|
|
656
|
+
["workers.resilienceCooldownMs", "fleet.retry.routeCooldownMs"],
|
|
657
|
+
["workers.onPermission", "fleet.permissions.mode"],
|
|
658
|
+
["workers.escalation.timeoutMs", "fleet.permissions.escalation.timeoutMs"],
|
|
659
|
+
["workers.escalation.fallback", "fleet.permissions.escalation.fallback"],
|
|
660
|
+
["guardrails.workerToolCallCap", "fleet.limits.toolCallsPerRun"],
|
|
661
|
+
["guardrails.internalDispatchTimeoutMs", "fleet.limits.internalRunTimeoutMs"],
|
|
662
|
+
["guardrails.maxDispatchRuns", "fleet.history.maxRuns"],
|
|
663
|
+
["compaction.auto", "context.compaction.auto"],
|
|
664
|
+
["compaction.threshold", "context.compaction.threshold"],
|
|
665
|
+
["compaction.model", "context.compaction.model"],
|
|
666
|
+
["compaction.systemPrompt", "context.compaction.systemPrompt"],
|
|
667
|
+
["background.target", "context.memory.target"],
|
|
668
|
+
["background.model", "context.memory.model"],
|
|
669
|
+
["memory.intervention.enabled", "context.memory.enabled"],
|
|
670
|
+
["memory.intervention.everyNTools", "context.memory.cadenceToolCalls"],
|
|
671
|
+
["memory.intervention.windowSteps", "context.memory.trajectorySteps"],
|
|
672
|
+
["memory.intervention.maxTokens", "context.memory.maxOutputTokens"],
|
|
673
|
+
["memory.intervention.timeoutMs", "context.memory.timeoutMs"],
|
|
674
|
+
["budget.sessionCeilingUsd", "safety.limits.sessionCostUsd"],
|
|
675
|
+
["guardrails.turnToolCallBudget", "safety.limits.chatToolCallsPerTurn"],
|
|
676
|
+
["guardrails.readMaxBytes", "safety.limits.readBytesPerCall"],
|
|
677
|
+
["guardrails.observationTurnBudgetBytes", "safety.limits.observationBytesPerTurn"],
|
|
678
|
+
["watchdog.enabled", "safety.review.enabled"],
|
|
679
|
+
["watchdog.target", "safety.review.target"],
|
|
680
|
+
["watchdog.cadenceToolCalls", "safety.review.cadenceToolCalls"],
|
|
681
|
+
["terminal.outputVerbosity", "interface.outputDetail"],
|
|
682
|
+
["terminal.smoothStreaming", "interface.smoothStreaming"],
|
|
683
|
+
["terminal.tuiMode", "interface.mode"],
|
|
684
|
+
["terminal.fullscreenScrollbar", "interface.fullscreenScrollbar"],
|
|
685
|
+
["terminal.showTerminalProgress", "interface.terminalProgress"],
|
|
686
|
+
["terminal.notify", "interface.desktopNotifications"],
|
|
687
|
+
// The panes keys shipped for exactly one release (v0.4.0, the day before
|
|
688
|
+
// this rework) but they did ship, and each has a direct v2 successor with
|
|
689
|
+
// an identical value domain, so they move like everything else. The
|
|
690
|
+
// retired agents/keepFailed pair and the `panes` root itself stay in
|
|
691
|
+
// SETTINGS_V1_RETIRED-adjacent handling via V1_ONLY_ROOTS.
|
|
692
|
+
["panes.enabled", "interface.panes.enabled"],
|
|
693
|
+
["panes.notifications", "interface.panes.notifications"],
|
|
694
|
+
["panes.journal", "fleet.history.journal"],
|
|
695
|
+
["panes.yazi.enabled", "interface.panes.files.enabled"],
|
|
696
|
+
["panes.yazi.mode", "interface.panes.files.mode"],
|
|
697
|
+
["panes.yazi.profile", "interface.panes.files.profile"],
|
|
698
|
+
["panes.yazi.followCwd", "interface.panes.files.followCwd"],
|
|
699
|
+
["keybindings.*", "interface.keybindings.*"],
|
|
700
|
+
["skills.trustProjectCompatRoots", "integrations.projectResources.trustProjectImports"],
|
|
701
|
+
["delegation.defaults.connectTimeoutMs", "integrations.externalAgents.defaults.connectTimeoutMs"],
|
|
702
|
+
["delegation.defaults.turnTimeoutMs", "integrations.externalAgents.defaults.turnTimeoutMs"],
|
|
703
|
+
["delegation.defaults.permissionTimeoutMs", "integrations.externalAgents.defaults.permissionTimeoutMs"],
|
|
704
|
+
["delegation.defaults.toolGovernance", "integrations.externalAgents.defaults.toolGovernance"],
|
|
705
|
+
["delegation.agents[].id", "integrations.externalAgents.entries[].id"],
|
|
706
|
+
["delegation.agents[].command", "integrations.externalAgents.entries[].command"],
|
|
707
|
+
["delegation.agents[].args[]", "integrations.externalAgents.entries[].args[]"],
|
|
708
|
+
["delegation.agents[].cwd", "integrations.externalAgents.entries[].cwd"],
|
|
709
|
+
["delegation.agents[].env.*", "integrations.externalAgents.entries[].env.*"],
|
|
710
|
+
["delegation.agents[].connectTimeoutMs", "integrations.externalAgents.entries[].connectTimeoutMs"],
|
|
711
|
+
["delegation.agents[].turnTimeoutMs", "integrations.externalAgents.entries[].turnTimeoutMs"],
|
|
712
|
+
["delegation.agents[].permissionTimeoutMs", "integrations.externalAgents.entries[].permissionTimeoutMs"],
|
|
713
|
+
["delegation.agents[].stallTimeoutMs", "integrations.externalAgents.entries[].stallTimeoutMs"],
|
|
714
|
+
["delegation.agents[].toolGovernance", "integrations.externalAgents.entries[].toolGovernance"],
|
|
715
|
+
["delegation.agents[].projectContext", "integrations.externalAgents.entries[].projectContext"],
|
|
716
|
+
["delegation.agents[].labels.*", "integrations.externalAgents.entries[].labels.*"],
|
|
717
|
+
["runtimePlugins[]", "integrations.runtimePlugins[]"],
|
|
718
|
+
["library.catalog", "integrations.library.catalog"],
|
|
719
|
+
["library.remote", "integrations.library.remote"],
|
|
720
|
+
["library.confirmedRemote", "integrations.library.confirmedRemote"],
|
|
721
|
+
["library.sync", "integrations.library.sync"],
|
|
722
|
+
["attribution.gitCommits", "integrations.git.commitAttribution"],
|
|
723
|
+
["fleet.nodes[].clioEntry", "fleet.nodes[].clioCoderEntry"],
|
|
724
|
+
] as const);
|
|
725
|
+
|
|
726
|
+
export const SETTINGS_V1_RETIRED_PATHS = Object.freeze({
|
|
727
|
+
identity: "it was accepted and ignored; no behavior is lost",
|
|
728
|
+
"background.thinkingLevel": "proactive memory always resolves thinking off",
|
|
729
|
+
theme: "the only registered theme was not read by runtime rendering",
|
|
730
|
+
"compaction.excludeLastTurns": "only the temporary legacy mask used it; context.workingSet.protectLastTurns remains",
|
|
731
|
+
} as const);
|
|
732
|
+
|
|
733
|
+
const V1_ONLY_ROOTS = new Set([
|
|
734
|
+
"identity",
|
|
735
|
+
"autonomy",
|
|
736
|
+
"runtimePlugins",
|
|
737
|
+
"orchestrator",
|
|
738
|
+
"background",
|
|
739
|
+
"memory",
|
|
740
|
+
"watchdog",
|
|
741
|
+
"workers",
|
|
742
|
+
"routing",
|
|
743
|
+
"scope",
|
|
744
|
+
"modelSelector",
|
|
745
|
+
"budget",
|
|
746
|
+
"defaults",
|
|
747
|
+
"theme",
|
|
748
|
+
"terminal",
|
|
749
|
+
"skills",
|
|
750
|
+
"library",
|
|
751
|
+
"attribution",
|
|
752
|
+
"delegation",
|
|
753
|
+
"keybindings",
|
|
754
|
+
"compaction",
|
|
755
|
+
"prewarm",
|
|
756
|
+
"panes",
|
|
757
|
+
"retry",
|
|
758
|
+
"guardrails",
|
|
759
|
+
]);
|
|
760
|
+
|
|
761
|
+
interface PatternMatch {
|
|
762
|
+
path: string;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
function pathPatternSegments(pattern: string): string[] {
|
|
766
|
+
return pattern.split(".");
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
function patternMatches(value: unknown, pattern: string): PatternMatch[] {
|
|
770
|
+
const out: PatternMatch[] = [];
|
|
771
|
+
const visit = (current: unknown, segments: ReadonlyArray<string>, path: string): void => {
|
|
772
|
+
const segment = segments[0];
|
|
773
|
+
if (segment === undefined) {
|
|
774
|
+
out.push({ path });
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
const rest = segments.slice(1);
|
|
778
|
+
if (segment === "*") {
|
|
779
|
+
if (!isPlainObject(current)) return;
|
|
780
|
+
for (const [key, child] of Object.entries(current)) visit(child, rest, path ? `${path}.${key}` : key);
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
const list = segment.endsWith("[]");
|
|
784
|
+
const key = list ? segment.slice(0, -2) : segment;
|
|
785
|
+
if (!isPlainObject(current) || !(key in current)) return;
|
|
786
|
+
const child = current[key];
|
|
787
|
+
const nextPath = path ? `${path}.${key}` : key;
|
|
788
|
+
if (!list) {
|
|
789
|
+
visit(child, rest, nextPath);
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
if (!Array.isArray(child)) return;
|
|
793
|
+
for (let index = 0; index < child.length; index += 1) visit(child[index], rest, `${nextPath}[${index}]`);
|
|
794
|
+
};
|
|
795
|
+
visit(value, pathPatternSegments(pattern), "");
|
|
796
|
+
return out;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
function reportV1Tombstones(issues: Issues, raw: Record<string, unknown>): void {
|
|
800
|
+
const reportedRoots = new Set<string>();
|
|
801
|
+
for (const [from, to] of SETTINGS_V1_PATH_MOVES) {
|
|
802
|
+
for (const match of patternMatches(raw, from)) {
|
|
803
|
+
issues.add(match.path, `retired settings-v1 path; use ${to} in version 2`);
|
|
804
|
+
reportedRoots.add(match.path.split(/[.[]/u)[0] ?? match.path);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
for (const [path, reason] of Object.entries(SETTINGS_V1_RETIRED_PATHS)) {
|
|
808
|
+
for (const match of patternMatches(raw, path)) {
|
|
809
|
+
issues.add(match.path, `retired without replacement: ${reason}. Remove this key`);
|
|
810
|
+
reportedRoots.add(match.path.split(/[.[]/u)[0] ?? match.path);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
for (const root of V1_ONLY_ROOTS) {
|
|
814
|
+
if (root in raw && !reportedRoots.has(root)) {
|
|
815
|
+
const replacements = new Set(
|
|
816
|
+
SETTINGS_V1_PATH_MOVES.filter(([from]) => from.split(/[.[]/u)[0] === root).map(
|
|
817
|
+
([, to]) => to.split(/[.[]/u)[0] ?? to,
|
|
818
|
+
),
|
|
819
|
+
);
|
|
820
|
+
const hint = replacements.size > 0 ? `; use ${[...replacements].join(" or ")} in version 2` : "";
|
|
821
|
+
issues.add(root, `retired settings-v1 namespace${hint}; migrate this document with \`clio-coder upgrade\``);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
function validateRoute(
|
|
585
827
|
issues: Issues,
|
|
586
828
|
path: string,
|
|
587
829
|
value: unknown,
|
|
588
|
-
defaults:
|
|
830
|
+
defaults: FleetRoute,
|
|
589
831
|
targets: ReadonlyArray<ClioSettings["targets"][number]>,
|
|
590
832
|
options?: { nodeIds?: ReadonlySet<string> },
|
|
591
|
-
):
|
|
592
|
-
const out
|
|
833
|
+
): FleetRoute {
|
|
834
|
+
const out = cloneValue(defaults);
|
|
593
835
|
if (!isPlainObject(value)) {
|
|
594
836
|
issues.add(path, `expected a map, got ${describe(value)}`);
|
|
595
837
|
return out;
|
|
@@ -600,80 +842,58 @@ function validateWorkerTarget(
|
|
|
600
842
|
value,
|
|
601
843
|
allowNode ? ["target", "model", "thinkingLevel", "node"] : ["target", "model", "thinkingLevel"],
|
|
602
844
|
);
|
|
603
|
-
if ("target" in value
|
|
604
|
-
|
|
605
|
-
|
|
845
|
+
if ("target" in value) {
|
|
846
|
+
if (value.target === null) out.target = null;
|
|
847
|
+
else {
|
|
848
|
+
const parsed = expectString(issues, `${path}.target`, value.target);
|
|
849
|
+
if (parsed !== undefined) out.target = targets.some((entry) => entry.id === parsed) ? parsed : null;
|
|
850
|
+
}
|
|
606
851
|
}
|
|
607
|
-
if ("
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
const v = expectString(issues, `${path}.model`, value.model);
|
|
613
|
-
if (v !== undefined) out.model = v;
|
|
614
|
-
}
|
|
615
|
-
// Node pins follow the target idiom: a pin naming a node that is not
|
|
616
|
-
// configured (and is not the implicit local node) is dropped, not fatal.
|
|
617
|
-
if (allowNode && "node" in value && value.node !== null) {
|
|
618
|
-
const v = expectString(issues, `${path}.node`, value.node);
|
|
619
|
-
if (v !== undefined) {
|
|
620
|
-
const id = v.trim();
|
|
621
|
-
if (id.length > 0 && (id === "local" || options?.nodeIds?.has(id) === true)) out.node = id;
|
|
852
|
+
if ("model" in value) {
|
|
853
|
+
if (value.model === null) out.model = null;
|
|
854
|
+
else {
|
|
855
|
+
const parsed = expectString(issues, `${path}.model`, value.model);
|
|
856
|
+
if (parsed !== undefined) out.model = parsed;
|
|
622
857
|
}
|
|
623
858
|
}
|
|
624
|
-
if (
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
859
|
+
if ("thinkingLevel" in value) {
|
|
860
|
+
const parsed = expectEnum(issues, `${path}.thinkingLevel`, value.thinkingLevel, THINKING_LEVELS);
|
|
861
|
+
if (parsed !== undefined) out.thinkingLevel = parsed;
|
|
862
|
+
}
|
|
863
|
+
if (allowNode && "node" in value) {
|
|
864
|
+
if (value.node === null) delete out.node;
|
|
865
|
+
else {
|
|
866
|
+
const parsed = expectString(issues, `${path}.node`, value.node);
|
|
867
|
+
if (parsed !== undefined && (parsed === "local" || options.nodeIds?.has(parsed) === true)) out.node = parsed;
|
|
868
|
+
}
|
|
628
869
|
}
|
|
870
|
+
if (!out.target) out.model = null;
|
|
871
|
+
else if (out.model === null) out.model = targets.find((entry) => entry.id === out.target)?.defaultModel ?? null;
|
|
629
872
|
return out;
|
|
630
873
|
}
|
|
631
874
|
|
|
632
|
-
/** `targetId/wireModelId` refs filtered to configured target ids. */
|
|
633
875
|
function normalizeModelRefs(
|
|
634
876
|
refs: ReadonlyArray<string>,
|
|
635
877
|
targets: ReadonlyArray<ClioSettings["targets"][number]>,
|
|
636
878
|
): string[] {
|
|
637
|
-
const
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
const model = modelParts.join("/").trim();
|
|
643
|
-
if (!model) continue;
|
|
644
|
-
const normalized = `${targetId}/${model}`;
|
|
645
|
-
if (!out.includes(normalized)) out.push(normalized);
|
|
646
|
-
}
|
|
647
|
-
return out;
|
|
879
|
+
const targetIds = new Set(targets.map((target) => target.id));
|
|
880
|
+
return refs.filter((ref) => {
|
|
881
|
+
const [targetId, ...model] = ref.split("/");
|
|
882
|
+
return Boolean(targetId && targetIds.has(targetId) && model.join("/").trim());
|
|
883
|
+
});
|
|
648
884
|
}
|
|
649
885
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
* whose target is not configured is kept, not dropped: a renamed or
|
|
653
|
-
* temporarily removed target is the common staleness case, and silently
|
|
654
|
-
* deleting the operator's cycle set at load destroys a preference they never
|
|
655
|
-
* asked to change. Disclosure belongs to the presentation layer, which lists
|
|
656
|
-
* unresolvable refs under "Unavailable"; the consumers that must resolve a ref
|
|
657
|
-
* (advanceScopedTarget) filter it themselves.
|
|
658
|
-
*/
|
|
659
|
-
function normalizeScope(refs: ReadonlyArray<string>): string[] {
|
|
660
|
-
const out: string[] = [];
|
|
661
|
-
for (const ref of refs) {
|
|
662
|
-
const trimmed = ref.trim();
|
|
663
|
-
const [targetId] = trimmed.split("/");
|
|
664
|
-
if (!targetId) continue;
|
|
665
|
-
if (!out.includes(trimmed)) out.push(trimmed);
|
|
666
|
-
}
|
|
667
|
-
return out;
|
|
886
|
+
function normalizeCycleSet(refs: ReadonlyArray<string>): string[] {
|
|
887
|
+
return refs.filter((ref) => Boolean(ref.trim().split("/")[0]));
|
|
668
888
|
}
|
|
669
889
|
|
|
670
|
-
function
|
|
890
|
+
function validateExternalAgent(
|
|
671
891
|
issues: Issues,
|
|
672
892
|
path: string,
|
|
673
893
|
value: unknown,
|
|
674
|
-
defaults:
|
|
894
|
+
defaults: ExternalAgentDefaults,
|
|
675
895
|
seen: Set<string>,
|
|
676
|
-
):
|
|
896
|
+
): ExternalAgent | null {
|
|
677
897
|
if (!isPlainObject(value)) {
|
|
678
898
|
issues.add(path, `expected a map, got ${describe(value)}`);
|
|
679
899
|
return null;
|
|
@@ -702,11 +922,11 @@ function validateDelegationAgent(
|
|
|
702
922
|
return null;
|
|
703
923
|
}
|
|
704
924
|
if (seen.has(id)) {
|
|
705
|
-
issues.add(`${path}.id`, `duplicate
|
|
925
|
+
issues.add(`${path}.id`, `duplicate external agent id '${id}'`);
|
|
706
926
|
return null;
|
|
707
927
|
}
|
|
708
928
|
seen.add(id);
|
|
709
|
-
const agent:
|
|
929
|
+
const agent: ExternalAgent = {
|
|
710
930
|
id,
|
|
711
931
|
command,
|
|
712
932
|
args: [],
|
|
@@ -716,89 +936,44 @@ function validateDelegationAgent(
|
|
|
716
936
|
toolGovernance: defaults.toolGovernance,
|
|
717
937
|
};
|
|
718
938
|
if ("args" in value) {
|
|
719
|
-
const
|
|
720
|
-
if (
|
|
939
|
+
const parsed = expectStringArray(issues, `${path}.args`, value.args);
|
|
940
|
+
if (parsed !== undefined) agent.args = parsed;
|
|
721
941
|
}
|
|
722
942
|
for (const key of ["connectTimeoutMs", "turnTimeoutMs", "permissionTimeoutMs"] as const) {
|
|
723
|
-
if (key in value)
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
}
|
|
943
|
+
if (!(key in value)) continue;
|
|
944
|
+
const parsed = expectInteger(issues, `${path}.${key}`, value[key], { min: 1, max: MAX_TIMER_DELAY_MS });
|
|
945
|
+
if (parsed !== undefined) agent[key] = parsed;
|
|
727
946
|
}
|
|
728
947
|
if ("stallTimeoutMs" in value) {
|
|
729
|
-
const
|
|
730
|
-
if (
|
|
948
|
+
const parsed = expectInteger(issues, `${path}.stallTimeoutMs`, value.stallTimeoutMs);
|
|
949
|
+
if (parsed !== undefined) agent.stallTimeoutMs = parsed;
|
|
731
950
|
}
|
|
732
951
|
if ("toolGovernance" in value) {
|
|
733
|
-
const
|
|
734
|
-
|
|
952
|
+
const parsed = expectEnum(
|
|
953
|
+
issues,
|
|
954
|
+
`${path}.toolGovernance`,
|
|
955
|
+
normalizeLegacyNamingValue(value.toolGovernance, "clio-policy", "clio-coder-policy"),
|
|
956
|
+
TOOL_GOVERNANCE,
|
|
957
|
+
);
|
|
958
|
+
if (parsed !== undefined) agent.toolGovernance = parsed;
|
|
735
959
|
}
|
|
736
960
|
if ("projectContext" in value) {
|
|
737
|
-
const
|
|
738
|
-
if (
|
|
961
|
+
const parsed = expectEnum(issues, `${path}.projectContext`, value.projectContext, ["none", "bounded"] as const);
|
|
962
|
+
if (parsed !== undefined) agent.projectContext = parsed;
|
|
739
963
|
}
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
964
|
+
for (const key of ["cwd"] as const) {
|
|
965
|
+
if (!(key in value)) continue;
|
|
966
|
+
const parsed = expectString(issues, `${path}.${key}`, value[key]);
|
|
967
|
+
if (parsed !== undefined) agent[key] = parsed;
|
|
743
968
|
}
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
if ("labels" in value) {
|
|
749
|
-
const v = expectStringRecord(issues, `${path}.labels`, value.labels);
|
|
750
|
-
if (v !== undefined && Object.keys(v).length > 0) agent.labels = v;
|
|
969
|
+
for (const key of ["env", "labels"] as const) {
|
|
970
|
+
if (!(key in value)) continue;
|
|
971
|
+
const parsed = expectStringRecord(issues, `${path}.${key}`, value[key]);
|
|
972
|
+
if (parsed !== undefined && Object.keys(parsed).length > 0) agent[key] = parsed;
|
|
751
973
|
}
|
|
752
974
|
return agent;
|
|
753
975
|
}
|
|
754
976
|
|
|
755
|
-
function validateDelegation(issues: Issues, value: unknown): ClioSettings["delegation"] {
|
|
756
|
-
const out = cloneValue(DEFAULT_SETTINGS.delegation);
|
|
757
|
-
if (!isPlainObject(value)) {
|
|
758
|
-
issues.add("delegation", `expected a map, got ${describe(value)}`);
|
|
759
|
-
return out;
|
|
760
|
-
}
|
|
761
|
-
issues.unknownKeys("delegation", value, ["agents", "defaults"]);
|
|
762
|
-
if ("defaults" in value) {
|
|
763
|
-
if (!isPlainObject(value.defaults)) {
|
|
764
|
-
issues.add("delegation.defaults", `expected a map, got ${describe(value.defaults)}`);
|
|
765
|
-
} else {
|
|
766
|
-
issues.unknownKeys("delegation.defaults", value.defaults, [
|
|
767
|
-
"connectTimeoutMs",
|
|
768
|
-
"turnTimeoutMs",
|
|
769
|
-
"permissionTimeoutMs",
|
|
770
|
-
"toolGovernance",
|
|
771
|
-
]);
|
|
772
|
-
for (const key of ["connectTimeoutMs", "turnTimeoutMs", "permissionTimeoutMs"] as const) {
|
|
773
|
-
if (key in value.defaults) {
|
|
774
|
-
const v = expectInteger(issues, `delegation.defaults.${key}`, value.defaults[key], {
|
|
775
|
-
min: 1,
|
|
776
|
-
max: MAX_TIMER_DELAY_MS,
|
|
777
|
-
});
|
|
778
|
-
if (v !== undefined) out.defaults[key] = v;
|
|
779
|
-
}
|
|
780
|
-
}
|
|
781
|
-
if ("toolGovernance" in value.defaults) {
|
|
782
|
-
const v = expectEnum(issues, "delegation.defaults.toolGovernance", value.defaults.toolGovernance, TOOL_GOVERNANCE);
|
|
783
|
-
if (v !== undefined) out.defaults.toolGovernance = v;
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
if ("agents" in value) {
|
|
788
|
-
if (!Array.isArray(value.agents)) {
|
|
789
|
-
issues.add("delegation.agents", `expected a list, got ${describe(value.agents)}`);
|
|
790
|
-
} else {
|
|
791
|
-
const seen = new Set<string>();
|
|
792
|
-
out.agents = value.agents
|
|
793
|
-
.map((entry, i) => validateDelegationAgent(issues, `delegation.agents[${i}]`, entry, out.defaults, seen))
|
|
794
|
-
.filter((entry): entry is NonNullable<typeof entry> => entry !== null);
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
return out;
|
|
798
|
-
}
|
|
799
|
-
|
|
800
|
-
type FleetNodeConfig = ClioSettings["fleet"]["nodes"][number];
|
|
801
|
-
|
|
802
977
|
function validateFleetNode(issues: Issues, path: string, value: unknown, seen: Set<string>): FleetNodeConfig | null {
|
|
803
978
|
if (!isPlainObject(value)) {
|
|
804
979
|
issues.add(path, `expected a map, got ${describe(value)}`);
|
|
@@ -811,7 +986,6 @@ function validateFleetNode(issues: Issues, path: string, value: unknown, seen: S
|
|
|
811
986
|
"port",
|
|
812
987
|
"identityFile",
|
|
813
988
|
"clioCoderEntry",
|
|
814
|
-
// Pre-0.3.1 spelling, still read.
|
|
815
989
|
"clioEntry",
|
|
816
990
|
"labels",
|
|
817
991
|
"maxWorkers",
|
|
@@ -822,197 +996,79 @@ function validateFleetNode(issues: Issues, path: string, value: unknown, seen: S
|
|
|
822
996
|
if (!("id" in value)) issues.add(`${path}.id`, "required");
|
|
823
997
|
if (!("host" in value)) issues.add(`${path}.host`, "required");
|
|
824
998
|
if (id === undefined || host === undefined) return null;
|
|
825
|
-
|
|
826
|
-
if (trimmedId.length === 0) {
|
|
827
|
-
issues.add(`${path}.id`, "must not be blank");
|
|
828
|
-
return null;
|
|
829
|
-
}
|
|
830
|
-
if (trimmedId === "local") {
|
|
999
|
+
if (id === "local") {
|
|
831
1000
|
issues.add(`${path}.id`, "'local' is reserved for the implicit local node");
|
|
832
1001
|
return null;
|
|
833
1002
|
}
|
|
834
|
-
if (seen.has(
|
|
835
|
-
issues.add(`${path}.id`, `duplicate fleet node id '${
|
|
1003
|
+
if (seen.has(id)) {
|
|
1004
|
+
issues.add(`${path}.id`, `duplicate fleet node id '${id}'`);
|
|
836
1005
|
return null;
|
|
837
1006
|
}
|
|
838
|
-
seen.add(
|
|
839
|
-
const node: FleetNodeConfig = { id
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
1007
|
+
seen.add(id);
|
|
1008
|
+
const node: FleetNodeConfig = { id, host, maxWorkers: 2 };
|
|
1009
|
+
for (const key of ["user", "identityFile", "clioCoderEntry"] as const) {
|
|
1010
|
+
if (!(key in value)) continue;
|
|
1011
|
+
const parsed = expectString(issues, `${path}.${key}`, value[key]);
|
|
1012
|
+
if (parsed !== undefined) node[key] = parsed;
|
|
843
1013
|
}
|
|
844
1014
|
if ("port" in value) {
|
|
845
|
-
const
|
|
846
|
-
if (
|
|
847
|
-
}
|
|
848
|
-
if ("identityFile" in value) {
|
|
849
|
-
const v = expectString(issues, `${path}.identityFile`, value.identityFile);
|
|
850
|
-
if (v !== undefined) node.identityFile = v;
|
|
851
|
-
}
|
|
852
|
-
// The key was `clioEntry` before 0.3.1. Both spellings read; the new one
|
|
853
|
-
// wins when a node carries both.
|
|
854
|
-
if ("clioEntry" in value) {
|
|
855
|
-
const v = expectString(issues, `${path}.clioEntry`, value.clioEntry);
|
|
856
|
-
if (v !== undefined) node.clioCoderEntry = v;
|
|
857
|
-
}
|
|
858
|
-
if ("clioCoderEntry" in value) {
|
|
859
|
-
const v = expectString(issues, `${path}.clioCoderEntry`, value.clioCoderEntry);
|
|
860
|
-
if (v !== undefined) node.clioCoderEntry = v;
|
|
1015
|
+
const parsed = expectInteger(issues, `${path}.port`, value.port, { min: 1 });
|
|
1016
|
+
if (parsed !== undefined) node.port = parsed;
|
|
861
1017
|
}
|
|
862
1018
|
if ("labels" in value) {
|
|
863
|
-
const
|
|
864
|
-
if (
|
|
1019
|
+
const parsed = expectStringArray(issues, `${path}.labels`, value.labels);
|
|
1020
|
+
if (parsed !== undefined) node.labels = parsed;
|
|
865
1021
|
}
|
|
866
1022
|
if ("maxWorkers" in value) {
|
|
867
|
-
const
|
|
868
|
-
if (
|
|
1023
|
+
const parsed = expectInteger(issues, `${path}.maxWorkers`, value.maxWorkers, { min: 1 });
|
|
1024
|
+
if (parsed !== undefined) node.maxWorkers = parsed;
|
|
869
1025
|
}
|
|
870
1026
|
if ("residency" in value) {
|
|
871
|
-
const
|
|
872
|
-
if (
|
|
1027
|
+
const parsed = expectEnum(issues, `${path}.residency`, value.residency, ["observe", "manage"] as const);
|
|
1028
|
+
if (parsed !== undefined) node.residency = parsed;
|
|
873
1029
|
}
|
|
874
1030
|
return node;
|
|
875
1031
|
}
|
|
876
1032
|
|
|
877
|
-
function
|
|
878
|
-
const out = cloneValue(DEFAULT_SETTINGS.fleet);
|
|
879
|
-
if (!isPlainObject(value)) {
|
|
880
|
-
issues.add("fleet", `expected a map, got ${describe(value)}`);
|
|
881
|
-
return out;
|
|
882
|
-
}
|
|
883
|
-
issues.unknownKeys("fleet", value, ["nodes"]);
|
|
884
|
-
if ("nodes" in value) {
|
|
885
|
-
if (!Array.isArray(value.nodes)) {
|
|
886
|
-
issues.add("fleet.nodes", `expected a list, got ${describe(value.nodes)}`);
|
|
887
|
-
} else {
|
|
888
|
-
const seen = new Set<string>();
|
|
889
|
-
out.nodes = value.nodes
|
|
890
|
-
.map((entry, i) => validateFleetNode(issues, `fleet.nodes[${i}]`, entry, seen))
|
|
891
|
-
.filter((entry): entry is NonNullable<typeof entry> => entry !== null);
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
return out;
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
function validateMemorySettings(issues: Issues, value: unknown): ClioSettings["memory"] {
|
|
898
|
-
const out = cloneValue(DEFAULT_SETTINGS.memory);
|
|
899
|
-
if (!isPlainObject(value)) {
|
|
900
|
-
issues.add("memory", `expected a map, got ${describe(value)}`);
|
|
901
|
-
return out;
|
|
902
|
-
}
|
|
903
|
-
issues.unknownKeys("memory", value, ["intervention"]);
|
|
904
|
-
if (!("intervention" in value)) return out;
|
|
905
|
-
if (!isPlainObject(value.intervention)) {
|
|
906
|
-
issues.add("memory.intervention", `expected a map, got ${describe(value.intervention)}`);
|
|
907
|
-
return out;
|
|
908
|
-
}
|
|
909
|
-
const raw = value.intervention;
|
|
910
|
-
issues.unknownKeys("memory.intervention", raw, ["enabled", "everyNTools", "windowSteps", "maxTokens", "timeoutMs"]);
|
|
911
|
-
if ("enabled" in raw) {
|
|
912
|
-
const enabled = expectBoolean(issues, "memory.intervention.enabled", raw.enabled);
|
|
913
|
-
if (enabled !== undefined) out.intervention.enabled = enabled;
|
|
914
|
-
}
|
|
915
|
-
for (const key of ["everyNTools", "windowSteps", "maxTokens", "timeoutMs"] as const) {
|
|
916
|
-
if (!(key in raw)) continue;
|
|
917
|
-
const parsed = expectInteger(issues, `memory.intervention.${key}`, raw[key], {
|
|
918
|
-
min: key === "everyNTools" ? 2 : 1,
|
|
919
|
-
});
|
|
920
|
-
if (parsed !== undefined) out.intervention[key] = parsed;
|
|
921
|
-
}
|
|
922
|
-
return out;
|
|
923
|
-
}
|
|
924
|
-
|
|
925
|
-
/**
|
|
926
|
-
* The watchdog block. `target` and `cadenceToolCalls` stay absent rather than
|
|
927
|
-
* taking sentinel values: an absent target means the session's active target,
|
|
928
|
-
* and an absent cadence means the watchdog fires only at turn end. A written
|
|
929
|
-
* default for either would turn "not configured" into a configuration.
|
|
930
|
-
*/
|
|
931
|
-
function validateWatchdogSettings(issues: Issues, value: unknown): ClioSettings["watchdog"] {
|
|
932
|
-
const out = cloneValue(DEFAULT_SETTINGS.watchdog);
|
|
933
|
-
if (!isPlainObject(value)) {
|
|
934
|
-
issues.add("watchdog", `expected a map, got ${describe(value)}`);
|
|
935
|
-
return out;
|
|
936
|
-
}
|
|
937
|
-
issues.unknownKeys("watchdog", value, ["enabled", "target", "cadenceToolCalls"]);
|
|
938
|
-
if ("enabled" in value) {
|
|
939
|
-
const enabled = expectBoolean(issues, "watchdog.enabled", value.enabled);
|
|
940
|
-
if (enabled !== undefined) out.enabled = enabled;
|
|
941
|
-
}
|
|
942
|
-
if ("target" in value && value.target !== null) {
|
|
943
|
-
const target = expectString(issues, "watchdog.target", value.target);
|
|
944
|
-
if (target !== undefined) {
|
|
945
|
-
if (target.trim().length === 0) issues.add("watchdog.target", "expected a non-empty target id");
|
|
946
|
-
else out.target = target.trim();
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
if ("cadenceToolCalls" in value && value.cadenceToolCalls !== null) {
|
|
950
|
-
const cadence = expectInteger(issues, "watchdog.cadenceToolCalls", value.cadenceToolCalls, { min: 1 });
|
|
951
|
-
if (cadence !== undefined) out.cadenceToolCalls = cadence;
|
|
952
|
-
}
|
|
953
|
-
return out;
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
function validateKeybindings(issues: Issues, value: unknown): ClioSettings["keybindings"] {
|
|
1033
|
+
function validateKeybindings(issues: Issues, path: string, value: unknown): Record<string, string | string[]> {
|
|
957
1034
|
if (!isPlainObject(value)) {
|
|
958
|
-
issues.add(
|
|
1035
|
+
issues.add(path, `expected a map, got ${describe(value)}`);
|
|
959
1036
|
return {};
|
|
960
1037
|
}
|
|
961
1038
|
const next: Record<string, string | string[]> = {};
|
|
962
1039
|
for (const [rawKey, rawValue] of Object.entries(value)) {
|
|
963
|
-
const
|
|
1040
|
+
const legacyId = rawKey.trim();
|
|
1041
|
+
const id = legacyId.startsWith("clio.") ? `clio-coder.${legacyId.slice("clio.".length)}` : legacyId;
|
|
964
1042
|
if (!id) {
|
|
965
|
-
issues.add(
|
|
966
|
-
continue;
|
|
967
|
-
}
|
|
968
|
-
if (typeof rawValue === "string") {
|
|
969
|
-
const v = expectString(issues, `keybindings.${id}`, rawValue);
|
|
970
|
-
if (v !== undefined) next[id] = v;
|
|
1043
|
+
issues.add(path, "empty keybinding id");
|
|
971
1044
|
continue;
|
|
972
1045
|
}
|
|
973
|
-
if (
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
continue;
|
|
1046
|
+
if (id !== legacyId) {
|
|
1047
|
+
warnLegacyNaming(legacyId, id);
|
|
1048
|
+
// Canonical values win even when the legacy key occurs later in YAML.
|
|
1049
|
+
if (Object.hasOwn(value, id)) continue;
|
|
977
1050
|
}
|
|
978
|
-
|
|
1051
|
+
if (typeof rawValue === "string") {
|
|
1052
|
+
const parsed = expectString(issues, `${path}.${legacyId}`, rawValue);
|
|
1053
|
+
if (parsed !== undefined) next[id] = parsed;
|
|
1054
|
+
} else if (Array.isArray(rawValue)) {
|
|
1055
|
+
const parsed = expectStringArray(issues, `${path}.${legacyId}`, rawValue);
|
|
1056
|
+
if (parsed !== undefined && parsed.length > 0) next[id] = parsed;
|
|
1057
|
+
} else issues.add(`${path}.${legacyId}`, `expected a string or list of strings, got ${describe(rawValue)}`);
|
|
979
1058
|
}
|
|
980
1059
|
return next;
|
|
981
1060
|
}
|
|
982
1061
|
|
|
983
1062
|
const TOP_LEVEL_KEYS = [
|
|
984
1063
|
"version",
|
|
985
|
-
// Accepted and ignored. Settings files generated before 0.3.1 carry an
|
|
986
|
-
// `identity:` line for a control nothing ever read; listing it here keeps
|
|
987
|
-
// those files loading without an unknown-key issue.
|
|
988
|
-
"identity",
|
|
989
|
-
"autonomy",
|
|
990
1064
|
"targets",
|
|
991
|
-
"
|
|
992
|
-
"orchestrator",
|
|
993
|
-
"background",
|
|
994
|
-
"memory",
|
|
995
|
-
"watchdog",
|
|
996
|
-
"workers",
|
|
1065
|
+
"chat",
|
|
997
1066
|
"fleet",
|
|
998
|
-
"routing",
|
|
999
|
-
"scope",
|
|
1000
|
-
"modelSelector",
|
|
1001
|
-
"budget",
|
|
1002
|
-
"defaults",
|
|
1003
|
-
"theme",
|
|
1004
|
-
"terminal",
|
|
1005
|
-
"skills",
|
|
1006
|
-
"library",
|
|
1007
|
-
"attribution",
|
|
1008
|
-
"delegation",
|
|
1009
|
-
"keybindings",
|
|
1010
|
-
"compaction",
|
|
1011
1067
|
"context",
|
|
1012
|
-
"
|
|
1013
|
-
"
|
|
1014
|
-
"
|
|
1015
|
-
|
|
1068
|
+
"safety",
|
|
1069
|
+
"interface",
|
|
1070
|
+
"integrations",
|
|
1071
|
+
...V1_ONLY_ROOTS,
|
|
1016
1072
|
] as const;
|
|
1017
1073
|
|
|
1018
1074
|
export interface SettingsValidationResult {
|
|
@@ -1030,529 +1086,412 @@ export interface SettingsValidationResult {
|
|
|
1030
1086
|
export function validateSettings(raw: unknown): SettingsValidationResult {
|
|
1031
1087
|
const issues = new Issues();
|
|
1032
1088
|
const settings = cloneValue(DEFAULT_SETTINGS);
|
|
1033
|
-
// A genuinely missing settings file falls back to defaults before ever
|
|
1034
|
-
// reaching here (validateSettingsFile returns early when the file is absent).
|
|
1035
|
-
// So a null/undefined raw here means a present-but-empty or `null` document,
|
|
1036
|
-
// which is malformed and must be a root-shape issue, not silent defaults.
|
|
1037
1089
|
if (!isPlainObject(raw)) {
|
|
1038
1090
|
issues.add("(root)", `expected a map, got ${describe(raw)}`);
|
|
1039
1091
|
return { settings, issues: issues.list };
|
|
1040
1092
|
}
|
|
1093
|
+
reportV1Tombstones(issues, raw);
|
|
1041
1094
|
issues.unknownKeys("", raw, TOP_LEVEL_KEYS);
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
issues.add("version", `expected 1, got ${describe(raw.version)}`);
|
|
1045
|
-
}
|
|
1046
|
-
if ("autonomy" in raw) {
|
|
1047
|
-
const v = expectEnum(issues, "autonomy", raw.autonomy, AUTONOMY_LEVELS);
|
|
1048
|
-
if (v !== undefined) settings.autonomy = v;
|
|
1095
|
+
if ("version" in raw && raw.version !== 2) {
|
|
1096
|
+
issues.add("version", `expected 2, got ${describe(raw.version)}; run \`clio-coder upgrade\` for version 1`);
|
|
1049
1097
|
}
|
|
1050
1098
|
|
|
1051
1099
|
if ("targets" in raw) {
|
|
1052
|
-
if (!Array.isArray(raw.targets)) {
|
|
1053
|
-
|
|
1054
|
-
} else {
|
|
1100
|
+
if (!Array.isArray(raw.targets)) issues.add("targets", `expected a list, got ${describe(raw.targets)}`);
|
|
1101
|
+
else {
|
|
1055
1102
|
const seen = new Set<string>();
|
|
1056
|
-
const
|
|
1057
|
-
for (let
|
|
1058
|
-
const target = validateTarget(issues, `targets[${
|
|
1103
|
+
const targets: ClioSettings["targets"] = [];
|
|
1104
|
+
for (let index = 0; index < raw.targets.length; index += 1) {
|
|
1105
|
+
const target = validateTarget(issues, `targets[${index}]`, raw.targets[index]);
|
|
1059
1106
|
if (!target) continue;
|
|
1060
1107
|
if (seen.has(target.id)) {
|
|
1061
|
-
issues.add(`targets[${
|
|
1108
|
+
issues.add(`targets[${index}].id`, `duplicate target id '${target.id}'`);
|
|
1062
1109
|
continue;
|
|
1063
1110
|
}
|
|
1064
1111
|
seen.add(target.id);
|
|
1065
|
-
|
|
1112
|
+
targets.push(target);
|
|
1066
1113
|
}
|
|
1067
|
-
settings.targets =
|
|
1114
|
+
settings.targets = targets;
|
|
1068
1115
|
}
|
|
1069
1116
|
}
|
|
1070
1117
|
|
|
1071
|
-
if ("
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
if (raw.library.sync !== undefined) {
|
|
1088
|
-
if (typeof raw.library.sync !== "boolean")
|
|
1089
|
-
issues.add("library.sync", `expected a boolean, got ${describe(raw.library.sync)}`);
|
|
1090
|
-
else settings.library.sync = raw.library.sync;
|
|
1118
|
+
if ("chat" in raw) {
|
|
1119
|
+
if (!isPlainObject(raw.chat)) issues.add("chat", `expected a map, got ${describe(raw.chat)}`);
|
|
1120
|
+
else {
|
|
1121
|
+
const chat = raw.chat;
|
|
1122
|
+
issues.unknownKeys("chat", chat, [
|
|
1123
|
+
"target",
|
|
1124
|
+
"model",
|
|
1125
|
+
"thinkingLevel",
|
|
1126
|
+
"modelPicker",
|
|
1127
|
+
"maxOutputTokens",
|
|
1128
|
+
"prewarm",
|
|
1129
|
+
"retry",
|
|
1130
|
+
]);
|
|
1131
|
+
const routeInput: Record<string, unknown> = {};
|
|
1132
|
+
for (const key of ["target", "model", "thinkingLevel"] as const) {
|
|
1133
|
+
if (key in chat) routeInput[key] = chat[key];
|
|
1091
1134
|
}
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
settings.
|
|
1101
|
-
settings.
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
settings.fleet = validateFleet(issues, raw.fleet);
|
|
1120
|
-
}
|
|
1121
|
-
const fleetNodeIds = new Set(settings.fleet.nodes.map((node) => node.id));
|
|
1122
|
-
|
|
1123
|
-
if ("routing" in raw) {
|
|
1124
|
-
if (!isPlainObject(raw.routing)) {
|
|
1125
|
-
issues.add("routing", `expected a map, got ${describe(raw.routing)}`);
|
|
1126
|
-
} else {
|
|
1127
|
-
issues.unknownKeys("routing", raw.routing, ["activeRoles", "activePostures", "agentAutomation"]);
|
|
1128
|
-
const activeRoles = expectStringArray(issues, "routing.activeRoles", raw.routing.activeRoles);
|
|
1129
|
-
if (activeRoles !== undefined) {
|
|
1130
|
-
const allowed = new Set<string>(ACTIVE_ROUTING_ROLES);
|
|
1131
|
-
const rawRoles = raw.routing.activeRoles as unknown[];
|
|
1132
|
-
const stringRoles = rawRoles.filter((role): role is string => typeof role === "string");
|
|
1133
|
-
const complete = activeRoles.length === rawRoles.length;
|
|
1134
|
-
for (const role of activeRoles) {
|
|
1135
|
-
if (!allowed.has(role)) issues.add("routing.activeRoles", `unsupported active role '${role}'`);
|
|
1136
|
-
}
|
|
1137
|
-
if (new Set(stringRoles).size !== stringRoles.length) {
|
|
1138
|
-
issues.add("routing.activeRoles", "expected unique roles");
|
|
1139
|
-
}
|
|
1140
|
-
if (complete && activeRoles.every((role) => allowed.has(role))) {
|
|
1141
|
-
settings.routing.activeRoles = activeRoles as typeof settings.routing.activeRoles;
|
|
1135
|
+
const route = validateRoute(
|
|
1136
|
+
issues,
|
|
1137
|
+
"chat",
|
|
1138
|
+
routeInput,
|
|
1139
|
+
{ target: settings.chat.target, model: settings.chat.model, thinkingLevel: settings.chat.thinkingLevel },
|
|
1140
|
+
settings.targets,
|
|
1141
|
+
);
|
|
1142
|
+
settings.chat.target = route.target;
|
|
1143
|
+
settings.chat.model = route.model;
|
|
1144
|
+
settings.chat.thinkingLevel = route.thinkingLevel;
|
|
1145
|
+
if ("modelPicker" in chat) {
|
|
1146
|
+
if (!isPlainObject(chat.modelPicker))
|
|
1147
|
+
issues.add("chat.modelPicker", `expected a map, got ${describe(chat.modelPicker)}`);
|
|
1148
|
+
else {
|
|
1149
|
+
issues.unknownKeys("chat.modelPicker", chat.modelPicker, ["cycleSet", "favorites", "recentLimit"]);
|
|
1150
|
+
if ("cycleSet" in chat.modelPicker) {
|
|
1151
|
+
const parsed = expectStringArray(issues, "chat.modelPicker.cycleSet", chat.modelPicker.cycleSet);
|
|
1152
|
+
if (parsed !== undefined) settings.chat.modelPicker.cycleSet = normalizeCycleSet(parsed);
|
|
1153
|
+
}
|
|
1154
|
+
if ("favorites" in chat.modelPicker) {
|
|
1155
|
+
const parsed = expectStringArray(issues, "chat.modelPicker.favorites", chat.modelPicker.favorites);
|
|
1156
|
+
if (parsed !== undefined) settings.chat.modelPicker.favorites = normalizeModelRefs(parsed, settings.targets);
|
|
1157
|
+
}
|
|
1158
|
+
if ("recentLimit" in chat.modelPicker) {
|
|
1159
|
+
const parsed = expectInteger(issues, "chat.modelPicker.recentLimit", chat.modelPicker.recentLimit, { min: 1 });
|
|
1160
|
+
if (parsed !== undefined) settings.chat.modelPicker.recentLimit = parsed;
|
|
1161
|
+
}
|
|
1142
1162
|
}
|
|
1143
1163
|
}
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
const rawPostures = raw.routing.activePostures as unknown[];
|
|
1148
|
-
const stringPostures = rawPostures.filter((posture): posture is string => typeof posture === "string");
|
|
1149
|
-
const complete = activePostures.length === rawPostures.length;
|
|
1150
|
-
for (const posture of activePostures) {
|
|
1151
|
-
if (!allowed.has(posture)) issues.add("routing.activePostures", `unsupported active posture '${posture}'`);
|
|
1152
|
-
}
|
|
1153
|
-
if (new Set(stringPostures).size !== stringPostures.length) {
|
|
1154
|
-
issues.add("routing.activePostures", "expected unique postures");
|
|
1155
|
-
}
|
|
1156
|
-
if (complete && activePostures.every((posture) => allowed.has(posture))) {
|
|
1157
|
-
settings.routing.activePostures = activePostures as typeof settings.routing.activePostures;
|
|
1158
|
-
}
|
|
1164
|
+
if ("maxOutputTokens" in chat) {
|
|
1165
|
+
const parsed = expectInteger(issues, "chat.maxOutputTokens", chat.maxOutputTokens, { min: 0 });
|
|
1166
|
+
if (parsed !== undefined) settings.chat.maxOutputTokens = parsed;
|
|
1159
1167
|
}
|
|
1160
|
-
if (
|
|
1161
|
-
issues
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
if (!
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
const agentId = expectString(issues, `${path}.agentId`, value.agentId);
|
|
1178
|
-
const executionRole = expectEnum(
|
|
1179
|
-
issues,
|
|
1180
|
-
`${path}.executionRole`,
|
|
1181
|
-
value.executionRole,
|
|
1182
|
-
ACTIVE_AGENT_AUTOMATION_ROLES,
|
|
1183
|
-
);
|
|
1184
|
-
if (agentId === "auto") {
|
|
1185
|
-
issues.add(`${path}.agentId`, "'auto' is reserved for coordinator-owned agent selection");
|
|
1186
|
-
}
|
|
1187
|
-
if (agentId === undefined || agentId === "auto" || executionRole === undefined) continue;
|
|
1188
|
-
const key = `${agentId}\u0000${executionRole}`;
|
|
1189
|
-
if (seen.has(key)) {
|
|
1190
|
-
issues.add(path, "duplicate agent and execution-role pair");
|
|
1191
|
-
continue;
|
|
1192
|
-
}
|
|
1193
|
-
seen.add(key);
|
|
1194
|
-
pairs.push({ agentId, executionRole });
|
|
1168
|
+
if ("prewarm" in chat) {
|
|
1169
|
+
const parsed = expectBoolean(issues, "chat.prewarm", chat.prewarm);
|
|
1170
|
+
if (parsed !== undefined) settings.chat.prewarm = parsed;
|
|
1171
|
+
}
|
|
1172
|
+
if ("retry" in chat) {
|
|
1173
|
+
if (!isPlainObject(chat.retry)) issues.add("chat.retry", `expected a map, got ${describe(chat.retry)}`);
|
|
1174
|
+
else {
|
|
1175
|
+
issues.unknownKeys("chat.retry", chat.retry, [
|
|
1176
|
+
"enabled",
|
|
1177
|
+
"maxRetries",
|
|
1178
|
+
"baseDelayMs",
|
|
1179
|
+
"maxDelayMs",
|
|
1180
|
+
"streamStallMs",
|
|
1181
|
+
]);
|
|
1182
|
+
if ("enabled" in chat.retry) {
|
|
1183
|
+
const parsed = expectBoolean(issues, "chat.retry.enabled", chat.retry.enabled);
|
|
1184
|
+
if (parsed !== undefined) settings.chat.retry.enabled = parsed;
|
|
1195
1185
|
}
|
|
1196
|
-
|
|
1197
|
-
|
|
1186
|
+
for (const key of ["maxRetries", "baseDelayMs", "maxDelayMs", "streamStallMs"] as const) {
|
|
1187
|
+
if (!(key in chat.retry)) continue;
|
|
1188
|
+
const parsed = expectInteger(issues, `chat.retry.${key}`, chat.retry[key], { min: 0 });
|
|
1189
|
+
if (parsed !== undefined) settings.chat.retry[key] = parsed;
|
|
1198
1190
|
}
|
|
1199
1191
|
}
|
|
1200
1192
|
}
|
|
1201
1193
|
}
|
|
1202
1194
|
}
|
|
1203
1195
|
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1196
|
+
let rawFleet: Record<string, unknown> | null = null;
|
|
1197
|
+
if ("fleet" in raw) {
|
|
1198
|
+
if (!isPlainObject(raw.fleet)) issues.add("fleet", `expected a map, got ${describe(raw.fleet)}`);
|
|
1199
|
+
else {
|
|
1200
|
+
rawFleet = raw.fleet;
|
|
1201
|
+
issues.unknownKeys("fleet", rawFleet, [
|
|
1209
1202
|
"default",
|
|
1210
1203
|
"profiles",
|
|
1211
1204
|
"rosters",
|
|
1212
|
-
"
|
|
1213
|
-
"
|
|
1214
|
-
"
|
|
1215
|
-
"
|
|
1216
|
-
"
|
|
1205
|
+
"agentProfiles",
|
|
1206
|
+
"adaptiveRouting",
|
|
1207
|
+
"nodes",
|
|
1208
|
+
"permissions",
|
|
1209
|
+
"concurrency",
|
|
1210
|
+
"retry",
|
|
1211
|
+
"limits",
|
|
1212
|
+
"history",
|
|
1217
1213
|
]);
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
);
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
if (rawFleet && "nodes" in rawFleet) {
|
|
1217
|
+
if (!Array.isArray(rawFleet.nodes)) issues.add("fleet.nodes", `expected a list, got ${describe(rawFleet.nodes)}`);
|
|
1218
|
+
else {
|
|
1219
|
+
const seen = new Set<string>();
|
|
1220
|
+
settings.fleet.nodes = rawFleet.nodes
|
|
1221
|
+
.map((entry, index) => validateFleetNode(issues, `fleet.nodes[${index}]`, entry, seen))
|
|
1222
|
+
.filter((entry): entry is FleetNodeConfig => entry !== null);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
1225
|
+
const fleetNodeIds = new Set(settings.fleet.nodes.map((node) => node.id));
|
|
1226
|
+
if (rawFleet) {
|
|
1227
|
+
if ("default" in rawFleet) {
|
|
1228
|
+
settings.fleet.default = validateRoute(
|
|
1229
|
+
issues,
|
|
1230
|
+
"fleet.default",
|
|
1231
|
+
rawFleet.default,
|
|
1232
|
+
settings.fleet.default,
|
|
1233
|
+
settings.targets,
|
|
1234
|
+
{
|
|
1235
|
+
nodeIds: fleetNodeIds,
|
|
1236
|
+
},
|
|
1237
|
+
);
|
|
1238
|
+
}
|
|
1239
|
+
if ("profiles" in rawFleet) {
|
|
1240
|
+
if (!isPlainObject(rawFleet.profiles))
|
|
1241
|
+
issues.add("fleet.profiles", `expected a map, got ${describe(rawFleet.profiles)}`);
|
|
1242
|
+
else {
|
|
1243
|
+
const profiles: ClioSettings["fleet"]["profiles"] = {};
|
|
1244
|
+
for (const [rawName, rawProfile] of Object.entries(rawFleet.profiles)) {
|
|
1245
|
+
const name = rawName.trim();
|
|
1246
|
+
if (!name) {
|
|
1247
|
+
issues.add("fleet.profiles", "empty profile name");
|
|
1248
|
+
continue;
|
|
1249
1249
|
}
|
|
1250
|
-
|
|
1250
|
+
const profile = validateRoute(
|
|
1251
|
+
issues,
|
|
1252
|
+
`fleet.profiles.${name}`,
|
|
1253
|
+
rawProfile,
|
|
1254
|
+
DEFAULT_SETTINGS.fleet.default,
|
|
1255
|
+
settings.targets,
|
|
1256
|
+
{
|
|
1257
|
+
nodeIds: fleetNodeIds,
|
|
1258
|
+
},
|
|
1259
|
+
);
|
|
1260
|
+
if (profile.target) profiles[name] = profile;
|
|
1251
1261
|
}
|
|
1262
|
+
settings.fleet.profiles = profiles;
|
|
1252
1263
|
}
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
if (!isPlainObject(rawRoster)) {
|
|
1266
|
-
issues.add(path, `expected a map, got ${describe(rawRoster)}`);
|
|
1267
|
-
continue;
|
|
1268
|
-
}
|
|
1269
|
-
issues.unknownKeys(path, rawRoster, ["members"]);
|
|
1270
|
-
if (!Array.isArray(rawRoster.members)) {
|
|
1271
|
-
issues.add(`${path}.members`, "expected a list");
|
|
1272
|
-
continue;
|
|
1273
|
-
}
|
|
1274
|
-
if (rawRoster.members.length < 2 || rawRoster.members.length > 5) {
|
|
1275
|
-
issues.add(`${path}.members`, "expected 2 to 5 members");
|
|
1276
|
-
}
|
|
1277
|
-
const members: ClioSettings["workers"]["rosters"][string]["members"] = [];
|
|
1278
|
-
const labels = new Set<string>();
|
|
1279
|
-
for (const [index, rawMember] of rawRoster.members.entries()) {
|
|
1280
|
-
const memberPath = `${path}.members[${index}]`;
|
|
1281
|
-
if (!isPlainObject(rawMember)) {
|
|
1282
|
-
issues.add(memberPath, `expected a map, got ${describe(rawMember)}`);
|
|
1283
|
-
continue;
|
|
1284
|
-
}
|
|
1285
|
-
issues.unknownKeys(memberPath, rawMember, ["label", "target", "model", "thinking", "color"]);
|
|
1286
|
-
const label = expectString(issues, `${memberPath}.label`, rawMember.label);
|
|
1287
|
-
const target = expectString(issues, `${memberPath}.target`, rawMember.target);
|
|
1288
|
-
if (label !== undefined && !COUNCIL_MEMBER_LABEL_PATTERN.test(label)) {
|
|
1289
|
-
issues.add(`${memberPath}.label`, "expected [a-z][a-z0-9_-]{0,31}");
|
|
1290
|
-
}
|
|
1291
|
-
if (label !== undefined && labels.has(label)) issues.add(`${memberPath}.label`, "duplicate label");
|
|
1292
|
-
if (label !== undefined) labels.add(label);
|
|
1293
|
-
if (target !== undefined && !settings.targets.some((entry) => entry.id === target)) {
|
|
1294
|
-
issues.add(`${memberPath}.target`, `unknown target '${target}'`);
|
|
1295
|
-
}
|
|
1296
|
-
const model =
|
|
1297
|
-
rawMember.model === undefined ? undefined : expectString(issues, `${memberPath}.model`, rawMember.model);
|
|
1298
|
-
const thinking =
|
|
1299
|
-
rawMember.thinking === undefined
|
|
1300
|
-
? undefined
|
|
1301
|
-
: expectEnum(issues, `${memberPath}.thinking`, rawMember.thinking, THINKING_LEVELS);
|
|
1302
|
-
const color =
|
|
1303
|
-
rawMember.color === undefined ? undefined : expectString(issues, `${memberPath}.color`, rawMember.color);
|
|
1304
|
-
if (color !== undefined && !THEME_NAMED_COLORS.includes(color as never) && !/^#[0-9a-fA-F]{6}$/u.test(color)) {
|
|
1305
|
-
issues.add(`${memberPath}.color`, "expected a theme named color or 6-digit hex color");
|
|
1306
|
-
}
|
|
1307
|
-
if (label === undefined || target === undefined) continue;
|
|
1308
|
-
members.push({
|
|
1309
|
-
label,
|
|
1310
|
-
target,
|
|
1311
|
-
...(model ? { model } : {}),
|
|
1312
|
-
...(thinking ? { thinking } : {}),
|
|
1313
|
-
...(color ? { color } : {}),
|
|
1314
|
-
});
|
|
1315
|
-
}
|
|
1316
|
-
if (rawRoster.members.length >= 2 && rawRoster.members.length <= 5 && members.length === rawRoster.members.length)
|
|
1317
|
-
rosters[name] = { members };
|
|
1264
|
+
}
|
|
1265
|
+
if ("rosters" in rawFleet) {
|
|
1266
|
+
if (!isPlainObject(rawFleet.rosters))
|
|
1267
|
+
issues.add("fleet.rosters", `expected a map, got ${describe(rawFleet.rosters)}`);
|
|
1268
|
+
else {
|
|
1269
|
+
const rosters: ClioSettings["fleet"]["rosters"] = {};
|
|
1270
|
+
for (const [rawName, rawRoster] of Object.entries(rawFleet.rosters)) {
|
|
1271
|
+
const name = rawName.trim();
|
|
1272
|
+
const path = `fleet.rosters.${name}`;
|
|
1273
|
+
if (!name) {
|
|
1274
|
+
issues.add("fleet.rosters", "empty roster name");
|
|
1275
|
+
continue;
|
|
1318
1276
|
}
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1277
|
+
if (!isPlainObject(rawRoster)) {
|
|
1278
|
+
issues.add(path, `expected a map, got ${describe(rawRoster)}`);
|
|
1279
|
+
continue;
|
|
1280
|
+
}
|
|
1281
|
+
issues.unknownKeys(path, rawRoster, ["members"]);
|
|
1282
|
+
if (!Array.isArray(rawRoster.members)) {
|
|
1283
|
+
issues.add(`${path}.members`, "expected a list");
|
|
1284
|
+
continue;
|
|
1285
|
+
}
|
|
1286
|
+
if (rawRoster.members.length < 2 || rawRoster.members.length > 5)
|
|
1287
|
+
issues.add(`${path}.members`, "expected 2 to 5 members");
|
|
1288
|
+
const members: ClioSettings["fleet"]["rosters"][string]["members"] = [];
|
|
1289
|
+
const labels = new Set<string>();
|
|
1290
|
+
for (const [index, rawMember] of rawRoster.members.entries()) {
|
|
1291
|
+
const memberPath = `${path}.members[${index}]`;
|
|
1292
|
+
if (!isPlainObject(rawMember)) {
|
|
1293
|
+
issues.add(memberPath, `expected a map, got ${describe(rawMember)}`);
|
|
1331
1294
|
continue;
|
|
1332
1295
|
}
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1296
|
+
issues.unknownKeys(memberPath, rawMember, ["label", "target", "model", "thinkingLevel", "color"]);
|
|
1297
|
+
const label = expectString(issues, `${memberPath}.label`, rawMember.label);
|
|
1298
|
+
const target = expectString(issues, `${memberPath}.target`, rawMember.target);
|
|
1299
|
+
if (label !== undefined && !COUNCIL_MEMBER_LABEL_PATTERN.test(label))
|
|
1300
|
+
issues.add(`${memberPath}.label`, "expected [a-z][a-z0-9_-]{0,31}");
|
|
1301
|
+
if (label !== undefined && labels.has(label)) issues.add(`${memberPath}.label`, "duplicate label");
|
|
1302
|
+
if (label !== undefined) labels.add(label);
|
|
1303
|
+
if (target !== undefined && !settings.targets.some((entry) => entry.id === target))
|
|
1304
|
+
issues.add(`${memberPath}.target`, `unknown target '${target}'`);
|
|
1305
|
+
const model =
|
|
1306
|
+
rawMember.model === undefined ? undefined : expectString(issues, `${memberPath}.model`, rawMember.model);
|
|
1307
|
+
const thinkingLevel =
|
|
1308
|
+
rawMember.thinkingLevel === undefined
|
|
1309
|
+
? undefined
|
|
1310
|
+
: expectEnum(issues, `${memberPath}.thinkingLevel`, rawMember.thinkingLevel, THINKING_LEVELS);
|
|
1311
|
+
const color =
|
|
1312
|
+
rawMember.color === undefined ? undefined : expectString(issues, `${memberPath}.color`, rawMember.color);
|
|
1313
|
+
if (color !== undefined && !THEME_NAMED_COLORS.includes(color as never) && !/^#[0-9a-fA-F]{6}$/u.test(color)) {
|
|
1314
|
+
issues.add(`${memberPath}.color`, "expected a theme named color or 6-digit hex color");
|
|
1336
1315
|
}
|
|
1337
|
-
|
|
1338
|
-
|
|
1316
|
+
if (label === undefined || target === undefined) continue;
|
|
1317
|
+
members.push({
|
|
1318
|
+
label,
|
|
1319
|
+
target,
|
|
1320
|
+
...(model ? { model } : {}),
|
|
1321
|
+
...(thinkingLevel ? { thinkingLevel } : {}),
|
|
1322
|
+
...(color ? { color } : {}),
|
|
1323
|
+
});
|
|
1339
1324
|
}
|
|
1340
|
-
|
|
1325
|
+
if (rawRoster.members.length >= 2 && rawRoster.members.length <= 5 && members.length === rawRoster.members.length)
|
|
1326
|
+
rosters[name] = { members };
|
|
1341
1327
|
}
|
|
1328
|
+
settings.fleet.rosters = rosters;
|
|
1342
1329
|
}
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
const
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
}
|
|
1355
|
-
if ("escalation" in raw.workers) {
|
|
1356
|
-
if (!isPlainObject(raw.workers.escalation)) {
|
|
1357
|
-
issues.add("workers.escalation", `expected a map, got ${describe(raw.workers.escalation)}`);
|
|
1358
|
-
} else {
|
|
1359
|
-
const base = settings.workers.escalation ?? DEFAULT_SETTINGS.workers.escalation;
|
|
1360
|
-
const escalation = { timeoutMs: base?.timeoutMs ?? 120000, fallback: base?.fallback ?? "deny" };
|
|
1361
|
-
issues.unknownKeys("workers.escalation", raw.workers.escalation, ["timeoutMs", "fallback"]);
|
|
1362
|
-
if ("timeoutMs" in raw.workers.escalation) {
|
|
1363
|
-
const v = expectInteger(issues, "workers.escalation.timeoutMs", raw.workers.escalation.timeoutMs, {
|
|
1364
|
-
min: 1,
|
|
1365
|
-
});
|
|
1366
|
-
if (v !== undefined) escalation.timeoutMs = v;
|
|
1330
|
+
}
|
|
1331
|
+
if ("agentProfiles" in rawFleet) {
|
|
1332
|
+
if (!isPlainObject(rawFleet.agentProfiles))
|
|
1333
|
+
issues.add("fleet.agentProfiles", `expected a map, got ${describe(rawFleet.agentProfiles)}`);
|
|
1334
|
+
else {
|
|
1335
|
+
const bindings: ClioSettings["fleet"]["agentProfiles"] = {};
|
|
1336
|
+
for (const [rawAgentId, rawProfileName] of Object.entries(rawFleet.agentProfiles)) {
|
|
1337
|
+
const agentId = rawAgentId.trim();
|
|
1338
|
+
if (!agentId) {
|
|
1339
|
+
issues.add("fleet.agentProfiles", "empty agent id");
|
|
1340
|
+
continue;
|
|
1367
1341
|
}
|
|
1368
|
-
if ("
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
"fail",
|
|
1372
|
-
] as const);
|
|
1373
|
-
if (v !== undefined) escalation.fallback = v;
|
|
1342
|
+
if (agentId === "auto") {
|
|
1343
|
+
issues.add("fleet.agentProfiles", "'auto' is reserved for coordinator-owned agent selection");
|
|
1344
|
+
continue;
|
|
1374
1345
|
}
|
|
1375
|
-
|
|
1346
|
+
const profile = expectString(issues, `fleet.agentProfiles.${agentId}`, rawProfileName);
|
|
1347
|
+
if (profile !== undefined) bindings[agentId] = profile;
|
|
1376
1348
|
}
|
|
1377
|
-
|
|
1378
|
-
if ("resilienceCooldownMs" in raw.workers) {
|
|
1379
|
-
const v = expectInteger(issues, "workers.resilienceCooldownMs", raw.workers.resilienceCooldownMs, { min: 0 });
|
|
1380
|
-
if (v !== undefined) settings.workers.resilienceCooldownMs = v;
|
|
1381
|
-
}
|
|
1382
|
-
}
|
|
1383
|
-
}
|
|
1384
|
-
|
|
1385
|
-
if ("scope" in raw) {
|
|
1386
|
-
const v = expectStringArray(issues, "scope", raw.scope);
|
|
1387
|
-
if (v !== undefined) settings.scope = normalizeScope(v);
|
|
1388
|
-
}
|
|
1389
|
-
|
|
1390
|
-
if ("modelSelector" in raw) {
|
|
1391
|
-
if (!isPlainObject(raw.modelSelector)) {
|
|
1392
|
-
issues.add("modelSelector", `expected a map, got ${describe(raw.modelSelector)}`);
|
|
1393
|
-
} else {
|
|
1394
|
-
issues.unknownKeys("modelSelector", raw.modelSelector, ["favorites", "recentLimit"]);
|
|
1395
|
-
if ("favorites" in raw.modelSelector) {
|
|
1396
|
-
const v = expectStringArray(issues, "modelSelector.favorites", raw.modelSelector.favorites);
|
|
1397
|
-
if (v !== undefined) settings.modelSelector.favorites = normalizeModelRefs(v, settings.targets);
|
|
1398
|
-
}
|
|
1399
|
-
if ("recentLimit" in raw.modelSelector) {
|
|
1400
|
-
const v = expectInteger(issues, "modelSelector.recentLimit", raw.modelSelector.recentLimit, { min: 1 });
|
|
1401
|
-
if (v !== undefined) settings.modelSelector.recentLimit = v;
|
|
1349
|
+
settings.fleet.agentProfiles = bindings;
|
|
1402
1350
|
}
|
|
1403
1351
|
}
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1352
|
+
if ("adaptiveRouting" in rawFleet) {
|
|
1353
|
+
if (!isPlainObject(rawFleet.adaptiveRouting))
|
|
1354
|
+
issues.add("fleet.adaptiveRouting", `expected a map, got ${describe(rawFleet.adaptiveRouting)}`);
|
|
1355
|
+
else {
|
|
1356
|
+
const adaptive = rawFleet.adaptiveRouting;
|
|
1357
|
+
issues.unknownKeys("fleet.adaptiveRouting", adaptive, ["roles", "postures", "agentRoles"]);
|
|
1358
|
+
for (const [key, allowed] of [
|
|
1359
|
+
["roles", ACTIVE_ROUTING_ROLES],
|
|
1360
|
+
["postures", ACTIVE_ROUTING_POSTURES],
|
|
1361
|
+
] as const) {
|
|
1362
|
+
if (!(key in adaptive)) continue;
|
|
1363
|
+
const parsed = expectStringArray(issues, `fleet.adaptiveRouting.${key}`, adaptive[key]);
|
|
1364
|
+
if (parsed === undefined) continue;
|
|
1365
|
+
const allowedSet = new Set<string>(allowed);
|
|
1366
|
+
for (const value of parsed)
|
|
1367
|
+
if (!allowedSet.has(value))
|
|
1368
|
+
issues.add(`fleet.adaptiveRouting.${key}`, `unsupported ${key === "roles" ? "role" : "posture"} '${value}'`);
|
|
1369
|
+
if (parsed.every((value) => allowedSet.has(value))) settings.fleet.adaptiveRouting[key] = parsed as never;
|
|
1370
|
+
}
|
|
1371
|
+
if ("agentRoles" in adaptive) {
|
|
1372
|
+
if (!Array.isArray(adaptive.agentRoles)) issues.add("fleet.adaptiveRouting.agentRoles", "expected a list");
|
|
1373
|
+
else {
|
|
1374
|
+
const pairs: ClioSettings["fleet"]["adaptiveRouting"]["agentRoles"] = [];
|
|
1375
|
+
const seen = new Set<string>();
|
|
1376
|
+
for (const [index, value] of adaptive.agentRoles.entries()) {
|
|
1377
|
+
const path = `fleet.adaptiveRouting.agentRoles[${index}]`;
|
|
1378
|
+
if (!isPlainObject(value)) {
|
|
1379
|
+
issues.add(path, "expected a map");
|
|
1380
|
+
continue;
|
|
1381
|
+
}
|
|
1382
|
+
issues.unknownKeys(path, value, ["agentId", "executionRole"]);
|
|
1383
|
+
const agentId = expectString(issues, `${path}.agentId`, value.agentId);
|
|
1384
|
+
const executionRole = expectEnum(
|
|
1385
|
+
issues,
|
|
1386
|
+
`${path}.executionRole`,
|
|
1387
|
+
value.executionRole,
|
|
1388
|
+
ACTIVE_AGENT_AUTOMATION_ROLES,
|
|
1389
|
+
);
|
|
1390
|
+
if (agentId === "auto")
|
|
1391
|
+
issues.add(`${path}.agentId`, "'auto' is reserved for coordinator-owned agent selection");
|
|
1392
|
+
if (agentId === undefined || agentId === "auto" || executionRole === undefined) continue;
|
|
1393
|
+
const pairKey = `${agentId}\u0000${executionRole}`;
|
|
1394
|
+
if (seen.has(pairKey)) {
|
|
1395
|
+
issues.add(path, "duplicate agent and execution-role pair");
|
|
1396
|
+
continue;
|
|
1397
|
+
}
|
|
1398
|
+
seen.add(pairKey);
|
|
1399
|
+
pairs.push({ agentId, executionRole });
|
|
1400
|
+
}
|
|
1401
|
+
if (pairs.length === adaptive.agentRoles.length) settings.fleet.adaptiveRouting.agentRoles = pairs;
|
|
1402
|
+
}
|
|
1421
1403
|
}
|
|
1422
1404
|
}
|
|
1423
1405
|
}
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
const v = expectBoolean(issues, "terminal.showTerminalProgress", raw.terminal.showTerminalProgress);
|
|
1457
|
-
if (v !== undefined) settings.terminal.showTerminalProgress = v;
|
|
1458
|
-
}
|
|
1459
|
-
if ("outputVerbosity" in raw.terminal) {
|
|
1460
|
-
const v = expectString(issues, "terminal.outputVerbosity", raw.terminal.outputVerbosity);
|
|
1461
|
-
if (v === "minimal" || v === "default" || v === "verbose") settings.terminal.outputVerbosity = v;
|
|
1462
|
-
else if (v !== undefined) issues.add("terminal.outputVerbosity", "expected minimal, default, or verbose");
|
|
1463
|
-
}
|
|
1464
|
-
if ("tuiMode" in raw.terminal) {
|
|
1465
|
-
const v = expectString(issues, "terminal.tuiMode", raw.terminal.tuiMode);
|
|
1466
|
-
if (v === "regular" || v === "fullscreen") settings.terminal.tuiMode = v;
|
|
1467
|
-
else if (v !== undefined) issues.add("terminal.tuiMode", "expected regular or fullscreen");
|
|
1468
|
-
}
|
|
1469
|
-
if ("fullscreenScrollbar" in raw.terminal) {
|
|
1470
|
-
const v = expectString(issues, "terminal.fullscreenScrollbar", raw.terminal.fullscreenScrollbar);
|
|
1471
|
-
if (v === "hidden" || v === "auto" || v === "always") settings.terminal.fullscreenScrollbar = v;
|
|
1472
|
-
else if (v !== undefined) issues.add("terminal.fullscreenScrollbar", "expected hidden, auto, or always");
|
|
1473
|
-
}
|
|
1474
|
-
if ("smoothStreaming" in raw.terminal) {
|
|
1475
|
-
const v = expectString(issues, "terminal.smoothStreaming", raw.terminal.smoothStreaming);
|
|
1476
|
-
if (v === "off" || v === "auto" || v === "on") settings.terminal.smoothStreaming = v;
|
|
1477
|
-
else if (v !== undefined) issues.add("terminal.smoothStreaming", "expected off, auto, or on");
|
|
1478
|
-
}
|
|
1479
|
-
if ("notify" in raw.terminal) {
|
|
1480
|
-
const v = expectBoolean(issues, "terminal.notify", raw.terminal.notify);
|
|
1481
|
-
if (v !== undefined) settings.terminal.notify = v;
|
|
1482
|
-
}
|
|
1483
|
-
}
|
|
1484
|
-
}
|
|
1485
|
-
|
|
1486
|
-
if ("skills" in raw) {
|
|
1487
|
-
if (!isPlainObject(raw.skills)) {
|
|
1488
|
-
issues.add("skills", `expected a map, got ${describe(raw.skills)}`);
|
|
1489
|
-
} else {
|
|
1490
|
-
issues.unknownKeys("skills", raw.skills, ["trustProjectCompatRoots"]);
|
|
1491
|
-
if ("trustProjectCompatRoots" in raw.skills) {
|
|
1492
|
-
const v = expectBoolean(issues, "skills.trustProjectCompatRoots", raw.skills.trustProjectCompatRoots);
|
|
1493
|
-
if (v !== undefined) settings.skills.trustProjectCompatRoots = v;
|
|
1406
|
+
if ("permissions" in rawFleet) {
|
|
1407
|
+
if (!isPlainObject(rawFleet.permissions))
|
|
1408
|
+
issues.add("fleet.permissions", `expected a map, got ${describe(rawFleet.permissions)}`);
|
|
1409
|
+
else {
|
|
1410
|
+
issues.unknownKeys("fleet.permissions", rawFleet.permissions, ["mode", "escalation"]);
|
|
1411
|
+
if ("mode" in rawFleet.permissions) {
|
|
1412
|
+
const parsed = expectEnum(issues, "fleet.permissions.mode", rawFleet.permissions.mode, [
|
|
1413
|
+
"deny",
|
|
1414
|
+
"fail",
|
|
1415
|
+
"escalate",
|
|
1416
|
+
] as const);
|
|
1417
|
+
if (parsed !== undefined) settings.fleet.permissions.mode = parsed;
|
|
1418
|
+
}
|
|
1419
|
+
if ("escalation" in rawFleet.permissions) {
|
|
1420
|
+
if (!isPlainObject(rawFleet.permissions.escalation))
|
|
1421
|
+
issues.add("fleet.permissions.escalation", `expected a map, got ${describe(rawFleet.permissions.escalation)}`);
|
|
1422
|
+
else {
|
|
1423
|
+
const escalation = rawFleet.permissions.escalation;
|
|
1424
|
+
issues.unknownKeys("fleet.permissions.escalation", escalation, ["timeoutMs", "fallback"]);
|
|
1425
|
+
if ("timeoutMs" in escalation) {
|
|
1426
|
+
const parsed = expectInteger(issues, "fleet.permissions.escalation.timeoutMs", escalation.timeoutMs, { min: 1 });
|
|
1427
|
+
if (parsed !== undefined) settings.fleet.permissions.escalation.timeoutMs = parsed;
|
|
1428
|
+
}
|
|
1429
|
+
if ("fallback" in escalation) {
|
|
1430
|
+
const parsed = expectEnum(issues, "fleet.permissions.escalation.fallback", escalation.fallback, [
|
|
1431
|
+
"deny",
|
|
1432
|
+
"fail",
|
|
1433
|
+
] as const);
|
|
1434
|
+
if (parsed !== undefined) settings.fleet.permissions.escalation.fallback = parsed;
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1494
1438
|
}
|
|
1495
1439
|
}
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
} else {
|
|
1502
|
-
issues.unknownKeys("attribution", raw.attribution, ["gitCommits"]);
|
|
1503
|
-
if ("gitCommits" in raw.attribution) {
|
|
1504
|
-
const v = expectBoolean(issues, "attribution.gitCommits", raw.attribution.gitCommits);
|
|
1505
|
-
if (v !== undefined) settings.attribution.gitCommits = v;
|
|
1440
|
+
if ("concurrency" in rawFleet) {
|
|
1441
|
+
if (rawFleet.concurrency === "auto") settings.fleet.concurrency = "auto";
|
|
1442
|
+
else {
|
|
1443
|
+
const parsed = expectInteger(issues, "fleet.concurrency", rawFleet.concurrency, { min: 1 });
|
|
1444
|
+
if (parsed !== undefined) settings.fleet.concurrency = parsed;
|
|
1506
1445
|
}
|
|
1507
1446
|
}
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
if (
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
}
|
|
1531
|
-
if ("excludeLastTurns" in raw.compaction) {
|
|
1532
|
-
const v = expectInteger(issues, "compaction.excludeLastTurns", raw.compaction.excludeLastTurns, { min: 1 });
|
|
1533
|
-
if (v !== undefined) settings.compaction.excludeLastTurns = v;
|
|
1447
|
+
for (const [blockName, fields] of [
|
|
1448
|
+
[
|
|
1449
|
+
"retry",
|
|
1450
|
+
[
|
|
1451
|
+
["maxRetries", 0],
|
|
1452
|
+
["routeCooldownMs", 0],
|
|
1453
|
+
],
|
|
1454
|
+
],
|
|
1455
|
+
[
|
|
1456
|
+
"limits",
|
|
1457
|
+
[
|
|
1458
|
+
["toolCallsPerRun", 1],
|
|
1459
|
+
["internalRunTimeoutMs", 1],
|
|
1460
|
+
],
|
|
1461
|
+
],
|
|
1462
|
+
["history", [["maxRuns", 1]]],
|
|
1463
|
+
] as const) {
|
|
1464
|
+
if (!(blockName in rawFleet)) continue;
|
|
1465
|
+
const block = rawFleet[blockName];
|
|
1466
|
+
if (!isPlainObject(block)) {
|
|
1467
|
+
issues.add(`fleet.${blockName}`, `expected a map, got ${describe(block)}`);
|
|
1468
|
+
continue;
|
|
1534
1469
|
}
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1470
|
+
const known = fields.map(([key]) => key);
|
|
1471
|
+
if (blockName === "history") known.push("journal" as never);
|
|
1472
|
+
issues.unknownKeys(`fleet.${blockName}`, block, known);
|
|
1473
|
+
for (const [key, min] of fields) {
|
|
1474
|
+
if (!(key in block)) continue;
|
|
1475
|
+
const parsed = expectInteger(issues, `fleet.${blockName}.${key}`, block[key], { min });
|
|
1476
|
+
if (parsed !== undefined) (settings.fleet[blockName] as unknown as Record<string, unknown>)[key] = parsed;
|
|
1538
1477
|
}
|
|
1539
|
-
if ("
|
|
1540
|
-
const
|
|
1541
|
-
if (
|
|
1478
|
+
if (blockName === "history" && "journal" in block) {
|
|
1479
|
+
const parsed = expectBoolean(issues, "fleet.history.journal", block.journal);
|
|
1480
|
+
if (parsed !== undefined) settings.fleet.history.journal = parsed;
|
|
1542
1481
|
}
|
|
1543
1482
|
}
|
|
1544
1483
|
}
|
|
1545
1484
|
|
|
1546
1485
|
if ("context" in raw) {
|
|
1547
|
-
if (!isPlainObject(raw.context)) {
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
issues.unknownKeys("context",
|
|
1551
|
-
if ("workingSet" in
|
|
1552
|
-
if (!isPlainObject(
|
|
1553
|
-
issues.add("context.workingSet", `expected a map, got ${describe(
|
|
1554
|
-
|
|
1555
|
-
const workingSet =
|
|
1486
|
+
if (!isPlainObject(raw.context)) issues.add("context", `expected a map, got ${describe(raw.context)}`);
|
|
1487
|
+
else {
|
|
1488
|
+
const context = raw.context;
|
|
1489
|
+
issues.unknownKeys("context", context, ["workingSet", "compaction", "memory"]);
|
|
1490
|
+
if ("workingSet" in context) {
|
|
1491
|
+
if (!isPlainObject(context.workingSet))
|
|
1492
|
+
issues.add("context.workingSet", `expected a map, got ${describe(context.workingSet)}`);
|
|
1493
|
+
else {
|
|
1494
|
+
const workingSet = context.workingSet;
|
|
1556
1495
|
issues.unknownKeys("context.workingSet", workingSet, [
|
|
1557
1496
|
"enabled",
|
|
1558
1497
|
"policy",
|
|
@@ -1561,136 +1500,380 @@ export function validateSettings(raw: unknown): SettingsValidationResult {
|
|
|
1561
1500
|
"minEvictableTokens",
|
|
1562
1501
|
]);
|
|
1563
1502
|
if ("enabled" in workingSet) {
|
|
1564
|
-
const
|
|
1565
|
-
if (
|
|
1503
|
+
const parsed = expectBoolean(issues, "context.workingSet.enabled", workingSet.enabled);
|
|
1504
|
+
if (parsed !== undefined) settings.context.workingSet.enabled = parsed;
|
|
1566
1505
|
}
|
|
1567
1506
|
if ("policy" in workingSet) {
|
|
1568
|
-
const
|
|
1507
|
+
const parsed = expectEnum(issues, "context.workingSet.policy", workingSet.policy, [
|
|
1569
1508
|
"age-horizon",
|
|
1570
1509
|
"structural-v1",
|
|
1571
1510
|
] as const);
|
|
1572
|
-
if (
|
|
1511
|
+
if (parsed !== undefined) settings.context.workingSet.policy = parsed;
|
|
1573
1512
|
}
|
|
1574
1513
|
if ("target" in workingSet) {
|
|
1575
|
-
const
|
|
1576
|
-
if (
|
|
1577
|
-
issues.add("context.workingSet.target", `expected a number > 0 and < 1, got ${
|
|
1578
|
-
|
|
1579
|
-
|
|
1514
|
+
const parsed = expectNumber(issues, "context.workingSet.target", workingSet.target);
|
|
1515
|
+
if (parsed !== undefined && (parsed <= 0 || parsed >= 1))
|
|
1516
|
+
issues.add("context.workingSet.target", `expected a number > 0 and < 1, got ${parsed}`);
|
|
1517
|
+
else if (parsed !== undefined) settings.context.workingSet.target = parsed;
|
|
1518
|
+
}
|
|
1519
|
+
for (const [key, min] of [
|
|
1520
|
+
["protectLastTurns", 1],
|
|
1521
|
+
["minEvictableTokens", 0],
|
|
1522
|
+
] as const) {
|
|
1523
|
+
if (!(key in workingSet)) continue;
|
|
1524
|
+
const parsed = expectInteger(issues, `context.workingSet.${key}`, workingSet[key], { min });
|
|
1525
|
+
if (parsed !== undefined) settings.context.workingSet[key] = parsed;
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
if ("compaction" in context) {
|
|
1530
|
+
if (!isPlainObject(context.compaction))
|
|
1531
|
+
issues.add("context.compaction", `expected a map, got ${describe(context.compaction)}`);
|
|
1532
|
+
else {
|
|
1533
|
+
const compaction = context.compaction;
|
|
1534
|
+
issues.unknownKeys("context.compaction", compaction, [
|
|
1535
|
+
"auto",
|
|
1536
|
+
"threshold",
|
|
1537
|
+
"model",
|
|
1538
|
+
"systemPrompt",
|
|
1539
|
+
"excludeLastTurns",
|
|
1540
|
+
]);
|
|
1541
|
+
if ("excludeLastTurns" in compaction)
|
|
1542
|
+
issues.add(
|
|
1543
|
+
"context.compaction.excludeLastTurns",
|
|
1544
|
+
"retired without replacement; use context.workingSet.protectLastTurns",
|
|
1545
|
+
);
|
|
1546
|
+
if ("auto" in compaction) {
|
|
1547
|
+
const parsed = expectBoolean(issues, "context.compaction.auto", compaction.auto);
|
|
1548
|
+
if (parsed !== undefined) settings.context.compaction.auto = parsed;
|
|
1549
|
+
}
|
|
1550
|
+
if ("threshold" in compaction) {
|
|
1551
|
+
const parsed = expectNumber(issues, "context.compaction.threshold", compaction.threshold, { min: 0, max: 1 });
|
|
1552
|
+
if (parsed !== undefined) settings.context.compaction.threshold = parsed;
|
|
1553
|
+
}
|
|
1554
|
+
for (const key of ["model", "systemPrompt"] as const) {
|
|
1555
|
+
if (!(key in compaction)) continue;
|
|
1556
|
+
const parsed = expectString(issues, `context.compaction.${key}`, compaction[key]);
|
|
1557
|
+
if (parsed !== undefined) settings.context.compaction[key] = parsed;
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
if ("memory" in context) {
|
|
1562
|
+
if (!isPlainObject(context.memory)) issues.add("context.memory", `expected a map, got ${describe(context.memory)}`);
|
|
1563
|
+
else {
|
|
1564
|
+
const memory = context.memory;
|
|
1565
|
+
issues.unknownKeys("context.memory", memory, [
|
|
1566
|
+
"enabled",
|
|
1567
|
+
"target",
|
|
1568
|
+
"model",
|
|
1569
|
+
"cadenceToolCalls",
|
|
1570
|
+
"trajectorySteps",
|
|
1571
|
+
"maxOutputTokens",
|
|
1572
|
+
"timeoutMs",
|
|
1573
|
+
]);
|
|
1574
|
+
if ("enabled" in memory) {
|
|
1575
|
+
const parsed = expectBoolean(issues, "context.memory.enabled", memory.enabled);
|
|
1576
|
+
if (parsed !== undefined) settings.context.memory.enabled = parsed;
|
|
1577
|
+
}
|
|
1578
|
+
if ("target" in memory) {
|
|
1579
|
+
if (memory.target === null) settings.context.memory.target = null;
|
|
1580
|
+
else {
|
|
1581
|
+
const parsed = expectString(issues, "context.memory.target", memory.target);
|
|
1582
|
+
if (parsed !== undefined)
|
|
1583
|
+
settings.context.memory.target = settings.targets.some((target) => target.id === parsed) ? parsed : null;
|
|
1580
1584
|
}
|
|
1581
1585
|
}
|
|
1582
|
-
if ("
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1586
|
+
if ("model" in memory) {
|
|
1587
|
+
if (memory.model === null) settings.context.memory.model = null;
|
|
1588
|
+
else {
|
|
1589
|
+
const parsed = expectString(issues, "context.memory.model", memory.model);
|
|
1590
|
+
if (parsed !== undefined) settings.context.memory.model = parsed;
|
|
1591
|
+
}
|
|
1587
1592
|
}
|
|
1588
|
-
if (
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
+
if (!settings.context.memory.target) settings.context.memory.model = null;
|
|
1594
|
+
else if (settings.context.memory.model === null)
|
|
1595
|
+
settings.context.memory.model =
|
|
1596
|
+
settings.targets.find((target) => target.id === settings.context.memory.target)?.defaultModel ?? null;
|
|
1597
|
+
for (const [key, min] of [
|
|
1598
|
+
["cadenceToolCalls", 2],
|
|
1599
|
+
["trajectorySteps", 1],
|
|
1600
|
+
["maxOutputTokens", 1],
|
|
1601
|
+
["timeoutMs", 1],
|
|
1602
|
+
] as const) {
|
|
1603
|
+
if (!(key in memory)) continue;
|
|
1604
|
+
const parsed = expectInteger(issues, `context.memory.${key}`, memory[key], { min });
|
|
1605
|
+
if (parsed !== undefined) settings.context.memory[key] = parsed;
|
|
1593
1606
|
}
|
|
1594
1607
|
}
|
|
1595
1608
|
}
|
|
1596
1609
|
}
|
|
1597
1610
|
}
|
|
1598
1611
|
|
|
1599
|
-
if ("
|
|
1600
|
-
if (!isPlainObject(raw.
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
issues.unknownKeys("
|
|
1604
|
-
if ("
|
|
1605
|
-
const
|
|
1606
|
-
if (
|
|
1612
|
+
if ("safety" in raw) {
|
|
1613
|
+
if (!isPlainObject(raw.safety)) issues.add("safety", `expected a map, got ${describe(raw.safety)}`);
|
|
1614
|
+
else {
|
|
1615
|
+
const safety = raw.safety;
|
|
1616
|
+
issues.unknownKeys("safety", safety, ["autonomy", "limits", "review"]);
|
|
1617
|
+
if ("autonomy" in safety) {
|
|
1618
|
+
const parsed = expectEnum(issues, "safety.autonomy", safety.autonomy, AUTONOMY_LEVELS);
|
|
1619
|
+
if (parsed !== undefined) settings.safety.autonomy = parsed;
|
|
1620
|
+
}
|
|
1621
|
+
if ("limits" in safety) {
|
|
1622
|
+
if (!isPlainObject(safety.limits)) issues.add("safety.limits", `expected a map, got ${describe(safety.limits)}`);
|
|
1623
|
+
else {
|
|
1624
|
+
const limits = safety.limits;
|
|
1625
|
+
issues.unknownKeys("safety.limits", limits, [
|
|
1626
|
+
"sessionCostUsd",
|
|
1627
|
+
"chatToolCallsPerTurn",
|
|
1628
|
+
"readBytesPerCall",
|
|
1629
|
+
"observationBytesPerTurn",
|
|
1630
|
+
]);
|
|
1631
|
+
if ("sessionCostUsd" in limits) {
|
|
1632
|
+
const parsed = expectNumber(issues, "safety.limits.sessionCostUsd", limits.sessionCostUsd, { min: 0 });
|
|
1633
|
+
if (parsed !== undefined) settings.safety.limits.sessionCostUsd = parsed;
|
|
1634
|
+
}
|
|
1635
|
+
for (const key of ["chatToolCallsPerTurn", "readBytesPerCall", "observationBytesPerTurn"] as const) {
|
|
1636
|
+
if (!(key in limits)) continue;
|
|
1637
|
+
const parsed = expectInteger(issues, `safety.limits.${key}`, limits[key], { min: 1 });
|
|
1638
|
+
if (parsed !== undefined) settings.safety.limits[key] = parsed;
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
if ("review" in safety) {
|
|
1643
|
+
if (!isPlainObject(safety.review)) issues.add("safety.review", `expected a map, got ${describe(safety.review)}`);
|
|
1644
|
+
else {
|
|
1645
|
+
const review = safety.review;
|
|
1646
|
+
issues.unknownKeys("safety.review", review, ["enabled", "target", "cadenceToolCalls"]);
|
|
1647
|
+
if ("enabled" in review) {
|
|
1648
|
+
const parsed = expectBoolean(issues, "safety.review.enabled", review.enabled);
|
|
1649
|
+
if (parsed !== undefined) settings.safety.review.enabled = parsed;
|
|
1650
|
+
}
|
|
1651
|
+
if ("target" in review && review.target !== null) {
|
|
1652
|
+
const parsed = expectString(issues, "safety.review.target", review.target);
|
|
1653
|
+
if (parsed !== undefined && settings.targets.some((target) => target.id === parsed))
|
|
1654
|
+
settings.safety.review.target = parsed;
|
|
1655
|
+
else if (parsed !== undefined) issues.add("safety.review.target", `unknown target '${parsed}'`);
|
|
1656
|
+
}
|
|
1657
|
+
if ("cadenceToolCalls" in review && review.cadenceToolCalls !== null) {
|
|
1658
|
+
const parsed = expectInteger(issues, "safety.review.cadenceToolCalls", review.cadenceToolCalls, { min: 1 });
|
|
1659
|
+
if (parsed !== undefined) settings.safety.review.cadenceToolCalls = parsed;
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1607
1662
|
}
|
|
1608
1663
|
}
|
|
1609
1664
|
}
|
|
1610
1665
|
|
|
1611
|
-
if ("
|
|
1612
|
-
if (!isPlainObject(raw.
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
if (
|
|
1666
|
+
if ("interface" in raw) {
|
|
1667
|
+
if (!isPlainObject(raw.interface)) issues.add("interface", `expected a map, got ${describe(raw.interface)}`);
|
|
1668
|
+
else {
|
|
1669
|
+
const ui = raw.interface;
|
|
1670
|
+
issues.unknownKeys("interface", ui, [
|
|
1671
|
+
"outputDetail",
|
|
1672
|
+
"smoothStreaming",
|
|
1673
|
+
"mode",
|
|
1674
|
+
"fullscreenScrollbar",
|
|
1675
|
+
"terminalProgress",
|
|
1676
|
+
"desktopNotifications",
|
|
1677
|
+
"panes",
|
|
1678
|
+
"keybindings",
|
|
1679
|
+
]);
|
|
1680
|
+
for (const key of ["terminalProgress", "desktopNotifications"] as const) {
|
|
1681
|
+
if (!(key in ui)) continue;
|
|
1682
|
+
const parsed = expectBoolean(issues, `interface.${key}`, ui[key]);
|
|
1683
|
+
if (parsed !== undefined) (settings.interface as unknown as Record<string, unknown>)[key] = parsed;
|
|
1627
1684
|
}
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1685
|
+
for (const [key, allowed] of [
|
|
1686
|
+
["outputDetail", ["minimal", "default", "verbose"]],
|
|
1687
|
+
["smoothStreaming", ["off", "auto", "on"]],
|
|
1688
|
+
["mode", ["regular", "fullscreen"]],
|
|
1689
|
+
["fullscreenScrollbar", ["hidden", "auto", "always"]],
|
|
1690
|
+
] as const) {
|
|
1691
|
+
if (!(key in ui)) continue;
|
|
1692
|
+
const parsed = expectEnum(issues, `interface.${key}`, ui[key], allowed);
|
|
1693
|
+
if (parsed !== undefined) (settings.interface as unknown as Record<string, unknown>)[key] = parsed;
|
|
1631
1694
|
}
|
|
1632
|
-
if ("
|
|
1633
|
-
if (!isPlainObject(
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1695
|
+
if ("panes" in ui) {
|
|
1696
|
+
if (!isPlainObject(ui.panes)) issues.add("interface.panes", `expected a map, got ${describe(ui.panes)}`);
|
|
1697
|
+
else {
|
|
1698
|
+
const panes = ui.panes;
|
|
1699
|
+
issues.unknownKeys("interface.panes", panes, ["enabled", "notifications", "layout", "workers", "files"]);
|
|
1700
|
+
if ("enabled" in panes) {
|
|
1701
|
+
const parsed = expectEnum(issues, "interface.panes.enabled", panes.enabled, ["auto", "embedded", "off"] as const);
|
|
1702
|
+
if (parsed !== undefined) settings.interface.panes.enabled = parsed;
|
|
1703
|
+
}
|
|
1704
|
+
if ("notifications" in panes) {
|
|
1705
|
+
const parsed = expectEnum(issues, "interface.panes.notifications", panes.notifications, [
|
|
1706
|
+
"failures",
|
|
1707
|
+
"all",
|
|
1708
|
+
"off",
|
|
1709
|
+
] as const);
|
|
1710
|
+
if (parsed !== undefined) settings.interface.panes.notifications = parsed;
|
|
1641
1711
|
}
|
|
1642
|
-
if ("
|
|
1643
|
-
const
|
|
1644
|
-
if (
|
|
1712
|
+
if ("layout" in panes) {
|
|
1713
|
+
const parsed = expectEnum(issues, "interface.panes.layout", panes.layout, ["off", "workers", "cockpit"] as const);
|
|
1714
|
+
if (parsed !== undefined) settings.interface.panes.layout = parsed;
|
|
1645
1715
|
}
|
|
1646
|
-
if ("
|
|
1647
|
-
|
|
1648
|
-
|
|
1716
|
+
if ("workers" in panes) {
|
|
1717
|
+
if (!isPlainObject(panes.workers))
|
|
1718
|
+
issues.add("interface.panes.workers", `expected a map, got ${describe(panes.workers)}`);
|
|
1719
|
+
else {
|
|
1720
|
+
const workers = panes.workers;
|
|
1721
|
+
issues.unknownKeys("interface.panes.workers", workers, ["ratio"]);
|
|
1722
|
+
if ("ratio" in workers) {
|
|
1723
|
+
const parsed = expectNumber(issues, "interface.panes.workers.ratio", workers.ratio, {
|
|
1724
|
+
min: 0.05,
|
|
1725
|
+
max: 0.5,
|
|
1726
|
+
});
|
|
1727
|
+
if (parsed !== undefined) settings.interface.panes.workers.ratio = parsed;
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1649
1730
|
}
|
|
1650
|
-
if ("
|
|
1651
|
-
|
|
1652
|
-
|
|
1731
|
+
if ("files" in panes) {
|
|
1732
|
+
if (!isPlainObject(panes.files))
|
|
1733
|
+
issues.add("interface.panes.files", `expected a map, got ${describe(panes.files)}`);
|
|
1734
|
+
else {
|
|
1735
|
+
const files = panes.files;
|
|
1736
|
+
issues.unknownKeys("interface.panes.files", files, ["enabled", "mode", "profile", "followCwd", "ratio"]);
|
|
1737
|
+
for (const key of ["enabled", "followCwd"] as const) {
|
|
1738
|
+
if (!(key in files)) continue;
|
|
1739
|
+
const parsed = expectBoolean(issues, `interface.panes.files.${key}`, files[key]);
|
|
1740
|
+
if (parsed !== undefined) settings.interface.panes.files[key] = parsed;
|
|
1741
|
+
}
|
|
1742
|
+
if ("mode" in files) {
|
|
1743
|
+
const parsed = expectEnum(issues, "interface.panes.files.mode", files.mode, ["companion", "chooser"] as const);
|
|
1744
|
+
if (parsed !== undefined) settings.interface.panes.files.mode = parsed;
|
|
1745
|
+
}
|
|
1746
|
+
if ("profile" in files) {
|
|
1747
|
+
const parsed = expectEnum(issues, "interface.panes.files.profile", files.profile, ["managed", "user"] as const);
|
|
1748
|
+
if (parsed !== undefined) settings.interface.panes.files.profile = parsed;
|
|
1749
|
+
}
|
|
1750
|
+
if ("ratio" in files) {
|
|
1751
|
+
const parsed = expectNumber(issues, "interface.panes.files.ratio", files.ratio, { min: 0.05, max: 0.5 });
|
|
1752
|
+
if (parsed !== undefined) settings.interface.panes.files.ratio = parsed;
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1653
1755
|
}
|
|
1654
1756
|
}
|
|
1655
1757
|
}
|
|
1758
|
+
if ("keybindings" in ui)
|
|
1759
|
+
settings.interface.keybindings = validateKeybindings(issues, "interface.keybindings", ui.keybindings);
|
|
1656
1760
|
}
|
|
1657
1761
|
}
|
|
1658
1762
|
|
|
1659
|
-
if ("
|
|
1660
|
-
if (!isPlainObject(raw.
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
issues.unknownKeys("
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1763
|
+
if ("integrations" in raw) {
|
|
1764
|
+
if (!isPlainObject(raw.integrations)) issues.add("integrations", `expected a map, got ${describe(raw.integrations)}`);
|
|
1765
|
+
else {
|
|
1766
|
+
const integrations = raw.integrations;
|
|
1767
|
+
issues.unknownKeys("integrations", integrations, [
|
|
1768
|
+
"projectResources",
|
|
1769
|
+
"externalAgents",
|
|
1770
|
+
"runtimePlugins",
|
|
1771
|
+
"library",
|
|
1772
|
+
"git",
|
|
1773
|
+
]);
|
|
1774
|
+
if ("projectResources" in integrations) {
|
|
1775
|
+
if (!isPlainObject(integrations.projectResources))
|
|
1776
|
+
issues.add("integrations.projectResources", `expected a map, got ${describe(integrations.projectResources)}`);
|
|
1777
|
+
else {
|
|
1778
|
+
issues.unknownKeys("integrations.projectResources", integrations.projectResources, ["trustProjectImports"]);
|
|
1779
|
+
if ("trustProjectImports" in integrations.projectResources) {
|
|
1780
|
+
const parsed = expectBoolean(
|
|
1781
|
+
issues,
|
|
1782
|
+
"integrations.projectResources.trustProjectImports",
|
|
1783
|
+
integrations.projectResources.trustProjectImports,
|
|
1784
|
+
);
|
|
1785
|
+
if (parsed !== undefined) settings.integrations.projectResources.trustProjectImports = parsed;
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
if ("runtimePlugins" in integrations) {
|
|
1790
|
+
const parsed = expectStringArray(issues, "integrations.runtimePlugins", integrations.runtimePlugins);
|
|
1791
|
+
if (parsed !== undefined) settings.integrations.runtimePlugins = parsed;
|
|
1667
1792
|
}
|
|
1668
|
-
|
|
1669
|
-
if (
|
|
1670
|
-
|
|
1671
|
-
|
|
1793
|
+
if ("library" in integrations) {
|
|
1794
|
+
if (!isPlainObject(integrations.library))
|
|
1795
|
+
issues.add("integrations.library", `expected a map, got ${describe(integrations.library)}`);
|
|
1796
|
+
else {
|
|
1797
|
+
const library = integrations.library;
|
|
1798
|
+
issues.unknownKeys("integrations.library", library, ["catalog", "remote", "confirmedRemote", "sync"]);
|
|
1799
|
+
for (const key of ["catalog", "remote", "confirmedRemote"] as const) {
|
|
1800
|
+
if (!(key in library)) continue;
|
|
1801
|
+
const value = library[key];
|
|
1802
|
+
if (value !== null && typeof value !== "string")
|
|
1803
|
+
issues.add(`integrations.library.${key}`, `expected a string or null, got ${describe(value)}`);
|
|
1804
|
+
else settings.integrations.library[key] = value as string | null;
|
|
1805
|
+
}
|
|
1806
|
+
if ("sync" in library) {
|
|
1807
|
+
const parsed = expectBoolean(issues, "integrations.library.sync", library.sync);
|
|
1808
|
+
if (parsed !== undefined) settings.integrations.library.sync = parsed;
|
|
1809
|
+
}
|
|
1672
1810
|
}
|
|
1673
1811
|
}
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1812
|
+
if ("git" in integrations) {
|
|
1813
|
+
if (!isPlainObject(integrations.git))
|
|
1814
|
+
issues.add("integrations.git", `expected a map, got ${describe(integrations.git)}`);
|
|
1815
|
+
else {
|
|
1816
|
+
issues.unknownKeys("integrations.git", integrations.git, ["commitAttribution"]);
|
|
1817
|
+
if ("commitAttribution" in integrations.git) {
|
|
1818
|
+
const parsed = expectBoolean(issues, "integrations.git.commitAttribution", integrations.git.commitAttribution);
|
|
1819
|
+
if (parsed !== undefined) settings.integrations.git.commitAttribution = parsed;
|
|
1820
|
+
}
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
if ("externalAgents" in integrations) {
|
|
1824
|
+
if (!isPlainObject(integrations.externalAgents))
|
|
1825
|
+
issues.add("integrations.externalAgents", `expected a map, got ${describe(integrations.externalAgents)}`);
|
|
1826
|
+
else {
|
|
1827
|
+
const external = integrations.externalAgents;
|
|
1828
|
+
issues.unknownKeys("integrations.externalAgents", external, ["entries", "defaults"]);
|
|
1829
|
+
if ("defaults" in external) {
|
|
1830
|
+
if (!isPlainObject(external.defaults))
|
|
1831
|
+
issues.add("integrations.externalAgents.defaults", `expected a map, got ${describe(external.defaults)}`);
|
|
1832
|
+
else {
|
|
1833
|
+
const defaults = external.defaults;
|
|
1834
|
+
issues.unknownKeys("integrations.externalAgents.defaults", defaults, [
|
|
1835
|
+
"connectTimeoutMs",
|
|
1836
|
+
"turnTimeoutMs",
|
|
1837
|
+
"permissionTimeoutMs",
|
|
1838
|
+
"toolGovernance",
|
|
1839
|
+
]);
|
|
1840
|
+
for (const key of ["connectTimeoutMs", "turnTimeoutMs", "permissionTimeoutMs"] as const) {
|
|
1841
|
+
if (!(key in defaults)) continue;
|
|
1842
|
+
const parsed = expectInteger(issues, `integrations.externalAgents.defaults.${key}`, defaults[key], {
|
|
1843
|
+
min: 1,
|
|
1844
|
+
max: MAX_TIMER_DELAY_MS,
|
|
1845
|
+
});
|
|
1846
|
+
if (parsed !== undefined) settings.integrations.externalAgents.defaults[key] = parsed;
|
|
1847
|
+
}
|
|
1848
|
+
if ("toolGovernance" in defaults) {
|
|
1849
|
+
const parsed = expectEnum(
|
|
1850
|
+
issues,
|
|
1851
|
+
"integrations.externalAgents.defaults.toolGovernance",
|
|
1852
|
+
normalizeLegacyNamingValue(defaults.toolGovernance, "clio-policy", "clio-coder-policy"),
|
|
1853
|
+
TOOL_GOVERNANCE,
|
|
1854
|
+
);
|
|
1855
|
+
if (parsed !== undefined) settings.integrations.externalAgents.defaults.toolGovernance = parsed;
|
|
1856
|
+
}
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
if ("entries" in external) {
|
|
1860
|
+
if (!Array.isArray(external.entries))
|
|
1861
|
+
issues.add("integrations.externalAgents.entries", `expected a list, got ${describe(external.entries)}`);
|
|
1862
|
+
else {
|
|
1863
|
+
const seen = new Set<string>();
|
|
1864
|
+
settings.integrations.externalAgents.entries = external.entries
|
|
1865
|
+
.map((entry, index) =>
|
|
1866
|
+
validateExternalAgent(
|
|
1867
|
+
issues,
|
|
1868
|
+
`integrations.externalAgents.entries[${index}]`,
|
|
1869
|
+
entry,
|
|
1870
|
+
settings.integrations.externalAgents.defaults,
|
|
1871
|
+
seen,
|
|
1872
|
+
),
|
|
1873
|
+
)
|
|
1874
|
+
.filter((entry): entry is ExternalAgent => entry !== null);
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1694
1877
|
}
|
|
1695
1878
|
}
|
|
1696
1879
|
}
|
|
@@ -1699,12 +1882,6 @@ export function validateSettings(raw: unknown): SettingsValidationResult {
|
|
|
1699
1882
|
return { settings, issues: issues.list };
|
|
1700
1883
|
}
|
|
1701
1884
|
|
|
1702
|
-
/**
|
|
1703
|
-
* Validate the settings file on disk without throwing. Missing file is valid.
|
|
1704
|
-
* Resolves the path without the clioConfigDir mkdir side effect so read-only
|
|
1705
|
-
* surfaces (plain `clio-coder doctor`, readSettings on a fresh machine) never
|
|
1706
|
-
* create directories.
|
|
1707
|
-
*/
|
|
1708
1885
|
export function validateSettingsFile(): SettingsValidationResult {
|
|
1709
1886
|
const path = join(resolveClioDirs().config, "settings.yaml");
|
|
1710
1887
|
if (!existsSync(path)) return { settings: cloneValue(DEFAULT_SETTINGS), issues: [] };
|
|
@@ -1832,10 +2009,6 @@ export function updateSavedSettingsDocument(
|
|
|
1832
2009
|
}, options);
|
|
1833
2010
|
}
|
|
1834
2011
|
|
|
1835
|
-
export function settingsLockPath(): string {
|
|
1836
|
-
return `${settingsPath()}.lock`;
|
|
1837
|
-
}
|
|
1838
|
-
|
|
1839
2012
|
/**
|
|
1840
2013
|
* Run `fn` while holding the settings.yaml advisory lock. Factored out of
|
|
1841
2014
|
* updateSettings so the one other sanctioned settings writer (lifecycle
|
|
@@ -1900,14 +2073,14 @@ export function useTargetInSettings(
|
|
|
1900
2073
|
: target;
|
|
1901
2074
|
if (!workerTarget) return null;
|
|
1902
2075
|
const sharedModel = options.model ?? target.defaultModel ?? null;
|
|
1903
|
-
settings.
|
|
1904
|
-
settings.
|
|
1905
|
-
settings.
|
|
1906
|
-
settings.
|
|
2076
|
+
settings.chat.target = target.id;
|
|
2077
|
+
settings.chat.model = options.orchestratorModel ?? sharedModel;
|
|
2078
|
+
settings.fleet.default.target = workerTarget.id;
|
|
2079
|
+
settings.fleet.default.model =
|
|
1907
2080
|
options.workerModel ?? (workerTarget === target ? sharedModel : (workerTarget.defaultModel ?? null));
|
|
1908
2081
|
if (options.backgroundModel !== undefined) {
|
|
1909
|
-
settings.
|
|
1910
|
-
settings.
|
|
2082
|
+
settings.context.memory.target = target.id;
|
|
2083
|
+
settings.context.memory.model = options.backgroundModel;
|
|
1911
2084
|
}
|
|
1912
2085
|
return { workerTargetId: workerTarget.id };
|
|
1913
2086
|
}
|
|
@@ -1916,22 +2089,25 @@ export function useTargetInSettings(
|
|
|
1916
2089
|
export function removeTargetFromSettings(settings: ClioSettings, id: string): boolean {
|
|
1917
2090
|
if (!settings.targets.some((entry) => entry.id === id)) return false;
|
|
1918
2091
|
settings.targets = settings.targets.filter((entry) => entry.id !== id);
|
|
1919
|
-
if (settings.
|
|
1920
|
-
settings.
|
|
1921
|
-
settings.
|
|
2092
|
+
if (settings.chat.target === id) {
|
|
2093
|
+
settings.chat.target = null;
|
|
2094
|
+
settings.chat.model = null;
|
|
1922
2095
|
}
|
|
1923
|
-
if (settings.
|
|
1924
|
-
settings.
|
|
1925
|
-
settings.
|
|
2096
|
+
if (settings.context.memory.target === id) {
|
|
2097
|
+
settings.context.memory.target = null;
|
|
2098
|
+
settings.context.memory.model = null;
|
|
1926
2099
|
}
|
|
1927
|
-
if (settings.
|
|
1928
|
-
settings.
|
|
1929
|
-
settings.
|
|
2100
|
+
if (settings.fleet.default.target === id) {
|
|
2101
|
+
settings.fleet.default.target = null;
|
|
2102
|
+
settings.fleet.default.model = null;
|
|
1930
2103
|
}
|
|
1931
|
-
for (const [name, profile] of Object.entries(settings.
|
|
1932
|
-
if (profile.target === id) delete settings.
|
|
2104
|
+
for (const [name, profile] of Object.entries(settings.fleet.profiles)) {
|
|
2105
|
+
if (profile.target === id) delete settings.fleet.profiles[name];
|
|
1933
2106
|
}
|
|
1934
|
-
settings.
|
|
2107
|
+
settings.chat.modelPicker.cycleSet = settings.chat.modelPicker.cycleSet.filter((entry) => entry.split("/")[0] !== id);
|
|
2108
|
+
settings.chat.modelPicker.favorites = settings.chat.modelPicker.favorites.filter(
|
|
2109
|
+
(entry) => entry.split("/")[0] !== id,
|
|
2110
|
+
);
|
|
1935
2111
|
return true;
|
|
1936
2112
|
}
|
|
1937
2113
|
|
|
@@ -1944,8 +2120,8 @@ export function setFleetProfileInSettings(
|
|
|
1944
2120
|
): boolean {
|
|
1945
2121
|
const target = settings.targets.find((entry) => entry.id === targetId);
|
|
1946
2122
|
if (!target) return false;
|
|
1947
|
-
const existing = settings.
|
|
1948
|
-
settings.
|
|
2123
|
+
const existing = settings.fleet.profiles[name];
|
|
2124
|
+
settings.fleet.profiles[name] = {
|
|
1949
2125
|
target: target.id,
|
|
1950
2126
|
model: options.model ?? target.defaultModel ?? null,
|
|
1951
2127
|
thinkingLevel: options.thinkingLevel ?? existing?.thinkingLevel ?? "off",
|
|
@@ -1955,11 +2131,11 @@ export function setFleetProfileInSettings(
|
|
|
1955
2131
|
|
|
1956
2132
|
/** Remove a fleet profile and its agent bindings; returns how many bindings went with it. */
|
|
1957
2133
|
export function removeFleetProfileFromSettings(settings: ClioSettings, name: string): number {
|
|
1958
|
-
delete settings.
|
|
2134
|
+
delete settings.fleet.profiles[name];
|
|
1959
2135
|
let removedBindings = 0;
|
|
1960
|
-
for (const [agentId, profileName] of Object.entries(settings.
|
|
2136
|
+
for (const [agentId, profileName] of Object.entries(settings.fleet.agentProfiles)) {
|
|
1961
2137
|
if (profileName !== name) continue;
|
|
1962
|
-
delete settings.
|
|
2138
|
+
delete settings.fleet.agentProfiles[agentId];
|
|
1963
2139
|
removedBindings += 1;
|
|
1964
2140
|
}
|
|
1965
2141
|
return removedBindings;
|
|
@@ -1967,7 +2143,7 @@ export function removeFleetProfileFromSettings(settings: ClioSettings, name: str
|
|
|
1967
2143
|
|
|
1968
2144
|
/** Bind a native agent to a profile. False for an ACP delegation agent, which ignores native routing. */
|
|
1969
2145
|
export function bindAgentProfileInSettings(settings: ClioSettings, agentId: string, profileName: string): boolean {
|
|
1970
|
-
if (settings.
|
|
1971
|
-
settings.
|
|
2146
|
+
if (settings.integrations.externalAgents.entries.some((agent) => agent.id === agentId)) return false;
|
|
2147
|
+
settings.fleet.agentProfiles[agentId] = profileName;
|
|
1972
2148
|
return true;
|
|
1973
2149
|
}
|