@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
|
@@ -0,0 +1,339 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
SKILL_INSTALL_OFFER_OPTION_NEVER,
|
|
4
|
+
SKILL_INSTALL_OFFER_OPTION_NOT_NOW,
|
|
5
|
+
SKILL_INSTALL_OFFER_OPTION_PROJECT,
|
|
6
|
+
SKILL_INSTALL_OFFER_OPTION_USER,
|
|
7
|
+
} from "../../core/skill-activation.js";
|
|
8
|
+
import { ToolNames } from "../../core/tool-names.js";
|
|
9
|
+
import type { MarketplaceSkill } from "../resources/skills/marketplace.js";
|
|
10
|
+
import {
|
|
11
|
+
assertPromotionInstallSource,
|
|
12
|
+
declineKey,
|
|
13
|
+
matchMarketplaceSkills,
|
|
14
|
+
type PromotionMatch,
|
|
15
|
+
readPromotionDeclines,
|
|
16
|
+
recordPromotionNeverDecline,
|
|
17
|
+
} from "../resources/skills/promotion.js";
|
|
18
|
+
import type { AutonomyLevel } from "../safety/autonomy.js";
|
|
19
|
+
import type { MiddlewareHookRegistration } from "./runtime.js";
|
|
20
|
+
import { isSubstantiveUserTurn } from "./skills-reminder.js";
|
|
21
|
+
import { type MiddlewareEffect, type MiddlewareHookInput, metadataNumber } from "./types.js";
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Marketplace self-promotion offer. When the operator's request needs
|
|
25
|
+
* expertise no installed skill covers and the local marketplace has a match,
|
|
26
|
+
* this registration teaches the model to put one tight ask_user question to
|
|
27
|
+
* the operator; the after_tool observer then acts on the answer — a consented
|
|
28
|
+
* install runs harness-side through the injected installer, "Not now" is a
|
|
29
|
+
* session-only decline, and "Never offer this skill" persists.
|
|
30
|
+
*
|
|
31
|
+
* Firing is need-driven, not count-budgeted: any substantive turn whose text
|
|
32
|
+
* matches an uninstalled, undeclined marketplace skill may fire, but each
|
|
33
|
+
* skill is offered at most once per session so a repeated request never
|
|
34
|
+
* becomes a nag. Detection is the local lexical matcher in
|
|
35
|
+
* resources/skills/promotion.ts — no request text leaves the machine.
|
|
36
|
+
*
|
|
37
|
+
* At full-auto the offer collapses into an autonomous install, but ONLY from
|
|
38
|
+
* Clio's own marketplace: every promotion-flow install (consented or
|
|
39
|
+
* autonomous) passes `assertPromotionInstallSource`, the hard runtime gate
|
|
40
|
+
* that rejects public registries in code rather than prompt text.
|
|
41
|
+
*
|
|
42
|
+
* Coordinator-only by wiring: this registration is created in the
|
|
43
|
+
* orchestrator entry and never in a dispatch worker's loop, so workers can
|
|
44
|
+
* neither see nor trigger offers.
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
export const MARKETPLACE_OFFER_REGISTRATION_ID = "observer.marketplace-offer";
|
|
48
|
+
|
|
49
|
+
export interface MarketplaceOfferInstallResult {
|
|
50
|
+
path: string;
|
|
51
|
+
/** The source the skill was fetched from; surfaced so an autonomous install stays auditable. */
|
|
52
|
+
sourceUrl: string;
|
|
53
|
+
/** Normalized content hash of what was written; the integrity record the consent overlay would have shown. */
|
|
54
|
+
installedHash: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface MarketplaceOfferDeps {
|
|
58
|
+
/** Installed skill names the matcher must exclude. */
|
|
59
|
+
listInstalledSkillNames(): ReadonlyArray<string>;
|
|
60
|
+
/** The local marketplace lookup (catalog + index); already local-only. */
|
|
61
|
+
listMarketplaceEntries(): ReadonlyArray<MarketplaceSkill>;
|
|
62
|
+
/** The session's effective autonomy at evaluation time. */
|
|
63
|
+
getAutonomy(): AutonomyLevel;
|
|
64
|
+
/** Perform the install; the registration gates the source before calling. */
|
|
65
|
+
installEntry(entry: MarketplaceSkill, scope: "user" | "project"): MarketplaceOfferInstallResult;
|
|
66
|
+
/** Decline persistence; defaults to the promotion module's config-dir store. Keyed by name+version. */
|
|
67
|
+
declines?: {
|
|
68
|
+
readNever(): Readonly<Record<string, string>>;
|
|
69
|
+
recordNever(name: string, version?: string): void;
|
|
70
|
+
};
|
|
71
|
+
/** Per-offer binding tag source; injectable for deterministic tests. Defaults to a random id. */
|
|
72
|
+
newOfferTag?: () => string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const NO_EFFECTS: ReadonlyArray<MiddlewareEffect> = [];
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The tag the model must echo verbatim in its ask_user question so the harness
|
|
79
|
+
* binds the answer to this specific offer and nothing else. The tag lives only
|
|
80
|
+
* in a system reminder (a trusted channel), so prompt-injected user or file
|
|
81
|
+
* content cannot forge it onto an unrelated question.
|
|
82
|
+
*/
|
|
83
|
+
export function offerBindingTag(offerTag: string): string {
|
|
84
|
+
return `[clio-install:${offerTag}]`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function marketplaceOfferReminder(entry: MarketplaceSkill, offerTag: string): string {
|
|
88
|
+
return (
|
|
89
|
+
`[Marketplace] This request may need expertise no installed skill covers. The local marketplace has ` +
|
|
90
|
+
`"${entry.name}" (not installed): ${entry.description} ` +
|
|
91
|
+
`First check the installed side with context(scope="skills"); if ${entry.name} is genuinely the right fit ` +
|
|
92
|
+
`and nothing installed serves, ask the operator with ask_user (mode=single_question, header "Install skill") ` +
|
|
93
|
+
`whether to install ${entry.name}, offering exactly these options in this order: ` +
|
|
94
|
+
`"${SKILL_INSTALL_OFFER_OPTION_PROJECT}", "${SKILL_INSTALL_OFFER_OPTION_USER}", ` +
|
|
95
|
+
`"${SKILL_INSTALL_OFFER_OPTION_NOT_NOW}", "${SKILL_INSTALL_OFFER_OPTION_NEVER}". ` +
|
|
96
|
+
`Include the exact tag ${offerBindingTag(offerTag)} verbatim in that question's text so the harness can bind ` +
|
|
97
|
+
`the answer to this offer; the harness acts only on an answer carrying this tag. ` +
|
|
98
|
+
`Then continue the task in the same turn. The harness handles the answer; never install or load a skill yourself. ` +
|
|
99
|
+
`If it is not actually needed, do not mention it.`
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function marketplaceAutoInstallReminder(entry: MarketplaceSkill, result: MarketplaceOfferInstallResult): string {
|
|
104
|
+
// Full-auto skips the operator's yes/no, never the integrity record. The
|
|
105
|
+
// consent overlay states the source and the SHA-256 of what it writes; the
|
|
106
|
+
// autonomous path computes the same hash and must surface it here, so an
|
|
107
|
+
// install nobody was asked about is still traceable to its source and bytes.
|
|
108
|
+
return (
|
|
109
|
+
`[Marketplace] Installed skill "${entry.name}" from Clio's own local marketplace (full-auto) to ${result.path}. ` +
|
|
110
|
+
`Source ${result.sourceUrl}, sha256 ${result.installedHash}. ${entry.description} ` +
|
|
111
|
+
`It is installed but not active — activation stays operator-gated. If it fits this task, ` +
|
|
112
|
+
`suggest /skill ${entry.name} to the operator; never load it yourself.`
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
interface AnswerLike {
|
|
117
|
+
question: string;
|
|
118
|
+
answer: string;
|
|
119
|
+
options?: ReadonlyArray<string>;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function answersFromDetails(details: Readonly<Record<string, unknown>> | undefined): AnswerLike[] {
|
|
123
|
+
const raw = details?.answers;
|
|
124
|
+
if (!Array.isArray(raw)) return [];
|
|
125
|
+
return raw.filter(
|
|
126
|
+
(entry): entry is AnswerLike =>
|
|
127
|
+
typeof entry === "object" &&
|
|
128
|
+
entry !== null &&
|
|
129
|
+
typeof (entry as AnswerLike).question === "string" &&
|
|
130
|
+
typeof (entry as AnswerLike).answer === "string",
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** The offer option this answer chose, matched by exact label in options or by label substring in the one-line answer. */
|
|
135
|
+
function chosenOfferOption(answer: AnswerLike): string | null {
|
|
136
|
+
const labels = [
|
|
137
|
+
SKILL_INSTALL_OFFER_OPTION_PROJECT,
|
|
138
|
+
SKILL_INSTALL_OFFER_OPTION_USER,
|
|
139
|
+
SKILL_INSTALL_OFFER_OPTION_NOT_NOW,
|
|
140
|
+
SKILL_INSTALL_OFFER_OPTION_NEVER,
|
|
141
|
+
];
|
|
142
|
+
for (const label of labels) {
|
|
143
|
+
if (answer.options?.includes(label)) return label;
|
|
144
|
+
}
|
|
145
|
+
for (const label of labels) {
|
|
146
|
+
if (answer.answer.includes(label)) return label;
|
|
147
|
+
}
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function createMarketplaceOfferRegistration(deps: MarketplaceOfferDeps): MiddlewareHookRegistration {
|
|
152
|
+
const declines = deps.declines ?? {
|
|
153
|
+
readNever: () => readPromotionDeclines().never,
|
|
154
|
+
recordNever: (name: string, version?: string) => recordPromotionNeverDecline(name, version),
|
|
155
|
+
};
|
|
156
|
+
const newOfferTag = deps.newOfferTag ?? (() => randomUUID());
|
|
157
|
+
// Per-session state, reset on a session id change (same adoption rule as
|
|
158
|
+
// the skills reminder: a fresh session's opening turn fires with a null id
|
|
159
|
+
// that the created session then fills in).
|
|
160
|
+
let lastSeenSessionId: string | null | undefined;
|
|
161
|
+
let offeredNames = new Set<string>();
|
|
162
|
+
let sessionDeclines = new Set<string>();
|
|
163
|
+
// Discovery parses and hashes every installed/catalog SKILL.md. On a real
|
|
164
|
+
// home with 31 entries per side that cost 36-92ms on every turn_start, while
|
|
165
|
+
// the lexical match itself stayed below 2ms. Inventory is stable within a
|
|
166
|
+
// session except for installs, so keep one session snapshot and revalidate
|
|
167
|
+
// installed names only when cached data is about to produce an offer.
|
|
168
|
+
let installedNamesCache: Set<string> | null = null;
|
|
169
|
+
let marketplaceEntriesCache: ReadonlyArray<MarketplaceSkill> | null = null;
|
|
170
|
+
// The offer put to the operator, consumed by the ask_user after_tool
|
|
171
|
+
// observer. Each offer carries a unique binding tag the model must echo in
|
|
172
|
+
// its question; the observer acts only on an answer whose question carries
|
|
173
|
+
// that tag, so an unrelated (or prompt-injected) ask_user answer can never
|
|
174
|
+
// bind the pending offer.
|
|
175
|
+
let pendingOffer: { entry: MarketplaceSkill; tag: string } | null = null;
|
|
176
|
+
|
|
177
|
+
const trackSession = (input: MiddlewareHookInput): void => {
|
|
178
|
+
const sessionId = input.sessionId ?? null;
|
|
179
|
+
if (lastSeenSessionId === undefined || (lastSeenSessionId === null && sessionId !== null)) {
|
|
180
|
+
lastSeenSessionId = sessionId;
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
if (sessionId !== lastSeenSessionId) {
|
|
184
|
+
lastSeenSessionId = sessionId;
|
|
185
|
+
offeredNames = new Set<string>();
|
|
186
|
+
sessionDeclines = new Set<string>();
|
|
187
|
+
pendingOffer = null;
|
|
188
|
+
installedNamesCache = null;
|
|
189
|
+
marketplaceEntriesCache = null;
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
const bestMatch = (text: string): PromotionMatch | null => {
|
|
194
|
+
const installedWasCached = installedNamesCache !== null;
|
|
195
|
+
let never: Readonly<Record<string, string>>;
|
|
196
|
+
try {
|
|
197
|
+
installedNamesCache ??= new Set(deps.listInstalledSkillNames());
|
|
198
|
+
marketplaceEntriesCache ??= deps.listMarketplaceEntries();
|
|
199
|
+
never = declines.readNever();
|
|
200
|
+
} catch {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
// offeredNames caps to one offer per skill per session, regardless of
|
|
204
|
+
// version. Declines are version-scoped: a "never"/"not now" on one catalog
|
|
205
|
+
// version does not suppress a later version of the same skill.
|
|
206
|
+
const declinedKeys = new Set<string>([...Object.keys(never), ...sessionDeclines]);
|
|
207
|
+
const findMatch = (): PromotionMatch | null =>
|
|
208
|
+
matchMarketplaceSkills(
|
|
209
|
+
text,
|
|
210
|
+
marketplaceEntriesCache ?? [],
|
|
211
|
+
new Set<string>([...(installedNamesCache ?? []), ...offeredNames]),
|
|
212
|
+
).find((candidate) => !declinedKeys.has(declineKey(candidate.entry.name, candidate.entry.version))) ?? null;
|
|
213
|
+
let match = findMatch();
|
|
214
|
+
if (match && installedWasCached) {
|
|
215
|
+
// A skills-hub or external install can happen after the session snapshot.
|
|
216
|
+
// Pay the installed-tree refresh only on the rare path that would surface
|
|
217
|
+
// an offer, then match again so an already-installed skill is never nagged.
|
|
218
|
+
try {
|
|
219
|
+
installedNamesCache = new Set(deps.listInstalledSkillNames());
|
|
220
|
+
} catch {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
match = findMatch();
|
|
224
|
+
}
|
|
225
|
+
return match;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const installGated = (entry: MarketplaceSkill, scope: "user" | "project"): MarketplaceOfferInstallResult => {
|
|
229
|
+
assertPromotionInstallSource(entry);
|
|
230
|
+
const result = deps.installEntry(entry, scope);
|
|
231
|
+
installedNamesCache?.add(entry.name);
|
|
232
|
+
return result;
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const evaluateAskUserAnswers = (input: MiddlewareHookInput): ReadonlyArray<MiddlewareEffect> => {
|
|
236
|
+
const offer = pendingOffer;
|
|
237
|
+
if (!offer) return NO_EFFECTS;
|
|
238
|
+
const marker = offerBindingTag(offer.tag);
|
|
239
|
+
if (input.toolResultDetails?.cancelled === true) {
|
|
240
|
+
// A cancelled interview carries no question text, so it cannot be
|
|
241
|
+
// attributed to this offer. Do not bind it: an unrelated cancel must
|
|
242
|
+
// not decline our offer, and the offer stays armed for a later answer.
|
|
243
|
+
return NO_EFFECTS;
|
|
244
|
+
}
|
|
245
|
+
for (const answer of answersFromDetails(input.toolResultDetails)) {
|
|
246
|
+
// The answer binds only when its question carries this offer's tag.
|
|
247
|
+
if (!answer.question.includes(marker)) continue;
|
|
248
|
+
const option = chosenOfferOption(answer);
|
|
249
|
+
if (option === null) continue;
|
|
250
|
+
pendingOffer = null;
|
|
251
|
+
const offerKey = declineKey(offer.entry.name, offer.entry.version);
|
|
252
|
+
if (option === SKILL_INSTALL_OFFER_OPTION_NOT_NOW) {
|
|
253
|
+
sessionDeclines.add(offerKey);
|
|
254
|
+
return NO_EFFECTS;
|
|
255
|
+
}
|
|
256
|
+
if (option === SKILL_INSTALL_OFFER_OPTION_NEVER) {
|
|
257
|
+
sessionDeclines.add(offerKey);
|
|
258
|
+
try {
|
|
259
|
+
declines.recordNever(offer.entry.name, offer.entry.version);
|
|
260
|
+
} catch {
|
|
261
|
+
// Persistence failure degrades to a session decline.
|
|
262
|
+
}
|
|
263
|
+
return NO_EFFECTS;
|
|
264
|
+
}
|
|
265
|
+
const scope = option === SKILL_INSTALL_OFFER_OPTION_USER ? "user" : "project";
|
|
266
|
+
try {
|
|
267
|
+
const result = installGated(offer.entry, scope);
|
|
268
|
+
return [
|
|
269
|
+
{
|
|
270
|
+
kind: "annotate_tool_result",
|
|
271
|
+
severity: "info",
|
|
272
|
+
message:
|
|
273
|
+
`[Marketplace] Installed skill "${offer.entry.name}" (${scope} scope) to ${result.path}. ` +
|
|
274
|
+
`It is installed but not active; the operator activates it with /skill ${offer.entry.name}.`,
|
|
275
|
+
},
|
|
276
|
+
];
|
|
277
|
+
} catch (error) {
|
|
278
|
+
sessionDeclines.add(offerKey);
|
|
279
|
+
return [
|
|
280
|
+
{
|
|
281
|
+
kind: "annotate_tool_result",
|
|
282
|
+
severity: "warn",
|
|
283
|
+
message: `[Marketplace] Install of "${offer.entry.name}" failed: ${
|
|
284
|
+
error instanceof Error ? error.message : String(error)
|
|
285
|
+
}. Continue the task without it.`,
|
|
286
|
+
},
|
|
287
|
+
];
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return NO_EFFECTS;
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
id: MARKETPLACE_OFFER_REGISTRATION_ID,
|
|
295
|
+
description:
|
|
296
|
+
"offers an uninstalled marketplace skill via ask_user when the request matches it; consented installs run gated to Clio's own marketplace, declines are remembered",
|
|
297
|
+
hooks: ["turn_start", "after_tool"],
|
|
298
|
+
evaluate(input): ReadonlyArray<MiddlewareEffect> {
|
|
299
|
+
trackSession(input);
|
|
300
|
+
// The offer is deliberately NOT cleared at turn_end. The reminder asks
|
|
301
|
+
// the model to put the ask_user question in the same turn, but it often
|
|
302
|
+
// defers a turn; clearing here dropped that deferred answer on the floor,
|
|
303
|
+
// so the operator's "Install" installed nothing and the model got no
|
|
304
|
+
// signal to correct a likely success claim. The offer now stays armed
|
|
305
|
+
// until a tag-carrying answer binds it, a newer offer replaces it, or the
|
|
306
|
+
// session ends — safe because only a tagged answer can bind (see H4).
|
|
307
|
+
if (input.hook === "after_tool") {
|
|
308
|
+
return input.toolName === ToolNames.AskUser ? evaluateAskUserAnswers(input) : NO_EFFECTS;
|
|
309
|
+
}
|
|
310
|
+
if (input.hook !== "turn_start") return NO_EFFECTS;
|
|
311
|
+
if (!isSubstantiveUserTurn(input.text)) return NO_EFFECTS;
|
|
312
|
+
// The operator already chose a skill for this turn; an offer on top of
|
|
313
|
+
// that choice is noise.
|
|
314
|
+
const pendingSkillRequests = metadataNumber(input, "pendingSkillRequests");
|
|
315
|
+
if (pendingSkillRequests !== null && pendingSkillRequests > 0) return NO_EFFECTS;
|
|
316
|
+
const match = bestMatch(input.text ?? "");
|
|
317
|
+
if (!match) return NO_EFFECTS;
|
|
318
|
+
offeredNames.add(match.entry.name);
|
|
319
|
+
if (deps.getAutonomy() === "full-auto") {
|
|
320
|
+
try {
|
|
321
|
+
const result = installGated(match.entry, "project");
|
|
322
|
+
return [
|
|
323
|
+
{
|
|
324
|
+
kind: "inject_reminder",
|
|
325
|
+
severity: "info",
|
|
326
|
+
message: marketplaceAutoInstallReminder(match.entry, result),
|
|
327
|
+
},
|
|
328
|
+
];
|
|
329
|
+
} catch {
|
|
330
|
+
// Gate refusal or install failure: fall back to the consent offer
|
|
331
|
+
// so the operator still hears about the match.
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
const tag = newOfferTag();
|
|
335
|
+
pendingOffer = { entry: match.entry, tag };
|
|
336
|
+
return [{ kind: "inject_reminder", severity: "info", message: marketplaceOfferReminder(match.entry, tag) }];
|
|
337
|
+
},
|
|
338
|
+
};
|
|
339
|
+
}
|
|
@@ -102,7 +102,7 @@ export type MiddlewareDiagnosticSink = (diagnostic: MiddlewareDiagnostic) => voi
|
|
|
102
102
|
export function writeMiddlewareDiagnosticToStderr(diagnostic: MiddlewareDiagnostic): void {
|
|
103
103
|
if (diagnostic.kind === "hook_failed") {
|
|
104
104
|
process.stderr.write(
|
|
105
|
-
`[clio:middleware] registration '${diagnostic.registrationId}' failed on '${diagnostic.hook}': ${diagnostic.message}\n`,
|
|
105
|
+
`[clio-coder:middleware] registration '${diagnostic.registrationId}' failed on '${diagnostic.hook}': ${diagnostic.message}\n`,
|
|
106
106
|
);
|
|
107
107
|
return;
|
|
108
108
|
}
|
|
@@ -115,7 +115,7 @@ export function writeMiddlewareDiagnosticToStderr(diagnostic: MiddlewareDiagnost
|
|
|
115
115
|
? ` (slow on ${stats.overCount}/${stats.window} recent ${diagnostic.hook} calls, p95 ${stats.p95Ms.toFixed(1)}ms)`
|
|
116
116
|
: "";
|
|
117
117
|
process.stderr.write(
|
|
118
|
-
`[clio:middleware] registration '${diagnostic.registrationId}' exceeded budget on '${diagnostic.hook}': ` +
|
|
118
|
+
`[clio-coder:middleware] registration '${diagnostic.registrationId}' exceeded budget on '${diagnostic.hook}': ` +
|
|
119
119
|
`${diagnostic.elapsedMs.toFixed(1)}ms > ${diagnostic.budgetMs}ms${trend}\n`,
|
|
120
120
|
);
|
|
121
121
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* fallback value rather than rejecting.
|
|
9
9
|
*
|
|
10
10
|
* Two ownership rules run through the whole file. Clio acts only on panes it
|
|
11
|
-
* created, tracked in `pane-registry.ts` and tagged with the `
|
|
11
|
+
* created, tracked in `pane-registry.ts` and tagged with the `clio_coder_owner`
|
|
12
12
|
* metadata token so `clio doctor` can find orphans. The one documented
|
|
13
13
|
* exception is Clio's own hosting pane in guest mode, which `reportSelf`
|
|
14
14
|
* writes to under SA-3 of the v0.4.0 cycle plan.
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
|
|
26
26
|
import type { DomainContract } from "../../core/domain-loader.js";
|
|
27
27
|
import type { MuxDetection } from "./detect.js";
|
|
28
|
+
import { createDockController, type DockController, type DockSlot, type DockState } from "./dock-controller.js";
|
|
28
29
|
import { createPaneRegistry, type MuxPaneRegistry, paneRecord } from "./pane-registry.js";
|
|
29
30
|
import { muxSupportsMethod } from "./protocol.js";
|
|
30
31
|
import type {
|
|
@@ -46,12 +47,15 @@ import {
|
|
|
46
47
|
} from "./types.js";
|
|
47
48
|
|
|
48
49
|
/** Metadata source for everything Clio's pane layer writes. */
|
|
49
|
-
const METADATA_SOURCE = "clio:mux";
|
|
50
|
+
const METADATA_SOURCE = "clio-coder:mux";
|
|
50
51
|
/** Agent-authority source for Clio's own hosting pane, per SA-3. */
|
|
51
|
-
const SELF_AGENT_SOURCE = "clio:coder";
|
|
52
|
+
const SELF_AGENT_SOURCE = "clio-coder:coder";
|
|
52
53
|
/** Token every Clio-created pane carries so orphans are findable. */
|
|
53
|
-
const OWNER_TOKEN_KEY = "
|
|
54
|
-
const OWNER_TOKEN_VALUE = "clio:mux";
|
|
54
|
+
const OWNER_TOKEN_KEY = "clio_coder_owner";
|
|
55
|
+
const OWNER_TOKEN_VALUE = "clio-coder:mux";
|
|
56
|
+
/** Old-host cleanup bridge; ownership readers retain this indefinitely. */
|
|
57
|
+
const LEGACY_OWNER_TOKEN_KEY = "clio_owner";
|
|
58
|
+
const LEGACY_OWNER_TOKEN_VALUE = "clio:mux";
|
|
55
59
|
/** herdr caps a metadata token value at 80 characters. */
|
|
56
60
|
const TOKEN_VALUE_MAX = 80;
|
|
57
61
|
/** How long `available()` stays false after a transport failure before probing again. */
|
|
@@ -71,6 +75,13 @@ export interface MuxOpenUtilityPaneRequest {
|
|
|
71
75
|
* such as Yazi reopen `/dev/tty` for their interface when stdout is not a tty.
|
|
72
76
|
*/
|
|
73
77
|
stdoutPath?: string;
|
|
78
|
+
/**
|
|
79
|
+
* Managed placement: the pane becomes this slot's dock, split from the
|
|
80
|
+
* anchor with the slot's direction and sized to `share` of the axis
|
|
81
|
+
* (clamped, cell-floored). Requires the layout tier (protocol 17); below
|
|
82
|
+
* the floor the request degrades to a plain split and `direction` applies.
|
|
83
|
+
*/
|
|
84
|
+
dock?: { slot: DockSlot; share?: number };
|
|
74
85
|
}
|
|
75
86
|
|
|
76
87
|
export interface MuxNotifyRequest {
|
|
@@ -99,7 +110,19 @@ export interface MuxContract extends DomainContract {
|
|
|
99
110
|
* This is what stops a restarted session's Enter-in-the-workers-view from
|
|
100
111
|
* opening a second watch pane beside the surviving one.
|
|
101
112
|
*/
|
|
102
|
-
adoptPane(request: { purpose: MuxPanePurpose; label: string }): Promise<MuxPaneRef | null>;
|
|
113
|
+
adoptPane(request: { purpose: MuxPanePurpose; label: string; dock?: DockSlot }): Promise<MuxPaneRef | null>;
|
|
114
|
+
/**
|
|
115
|
+
* Focus one Clio-created pane, switching the user's view to it (pane.focus
|
|
116
|
+
* moves the focused tab too). Explicit-request only; refuses foreign panes
|
|
117
|
+
* and servers below the pane.focus floor.
|
|
118
|
+
*/
|
|
119
|
+
focusPane(paneId: string): Promise<boolean>;
|
|
120
|
+
/** Zoom one Clio-created pane on, off, or toggled. Zooming steals focus; same rule as focusPane. */
|
|
121
|
+
zoomPane(paneId: string, mode: "on" | "off" | "toggle"): Promise<boolean>;
|
|
122
|
+
/** Set a dock's share of its axis. False when the slot has no dock or the tier is absent. */
|
|
123
|
+
resizeDock(slot: DockSlot, share: number): Promise<boolean>;
|
|
124
|
+
/** Live dock geometry states, for `/panes` status. */
|
|
125
|
+
docks(): ReadonlyArray<DockState>;
|
|
103
126
|
notify(request: MuxNotifyRequest): Promise<void>;
|
|
104
127
|
/** Optional compete storage route. Protocol-gated with a native Git fallback at the caller. */
|
|
105
128
|
worktreeCreate(request: MuxWorktreeCreateRequest): Promise<MuxWorktreeCreatedResult | null>;
|
|
@@ -147,11 +170,25 @@ function token(value: string | undefined | null): string | null {
|
|
|
147
170
|
return value.length > TOKEN_VALUE_MAX ? value.slice(0, TOKEN_VALUE_MAX) : value;
|
|
148
171
|
}
|
|
149
172
|
|
|
173
|
+
function hasOwnedPaneToken(tokens: Readonly<Record<string, string>>): boolean {
|
|
174
|
+
return tokens[OWNER_TOKEN_KEY] === OWNER_TOKEN_VALUE || tokens[LEGACY_OWNER_TOKEN_KEY] === LEGACY_OWNER_TOKEN_VALUE;
|
|
175
|
+
}
|
|
176
|
+
|
|
150
177
|
export function createMuxRuntime(options: MuxRuntimeOptions): MuxRuntime {
|
|
151
178
|
const { detection, client } = options;
|
|
152
179
|
const log = options.log ?? ((): void => undefined);
|
|
153
180
|
const now = options.now ?? Date.now;
|
|
154
181
|
const registry = createPaneRegistry();
|
|
182
|
+
const anchorPaneId = detection.self.paneId;
|
|
183
|
+
/**
|
|
184
|
+
* The managed dock tier needs an anchor to split from and the protocol-17
|
|
185
|
+
* layout methods to converge with. Absent either, dock requests degrade to
|
|
186
|
+
* plain utility splits and the rest of the tier answers false/empty.
|
|
187
|
+
*/
|
|
188
|
+
const docks: DockController | null =
|
|
189
|
+
client !== null && anchorPaneId !== null && muxSupportsMethod(detection.server, "layout.export")
|
|
190
|
+
? createDockController({ client, anchorPaneId, log })
|
|
191
|
+
: null;
|
|
155
192
|
|
|
156
193
|
let healthy = client !== null;
|
|
157
194
|
let probeHealthAt = 0;
|
|
@@ -201,11 +238,30 @@ export function createMuxRuntime(options: MuxRuntimeOptions): MuxRuntime {
|
|
|
201
238
|
paneId,
|
|
202
239
|
source: METADATA_SOURCE,
|
|
203
240
|
...(title === undefined ? {} : { title }),
|
|
204
|
-
tokens: {
|
|
241
|
+
tokens: {
|
|
242
|
+
[OWNER_TOKEN_KEY]: OWNER_TOKEN_VALUE,
|
|
243
|
+
[LEGACY_OWNER_TOKEN_KEY]: LEGACY_OWNER_TOKEN_VALUE,
|
|
244
|
+
...tokens,
|
|
245
|
+
},
|
|
205
246
|
});
|
|
206
247
|
};
|
|
207
248
|
|
|
208
249
|
const onEvent = (event: MuxEvent): void => {
|
|
250
|
+
if (event.kind === "layout.updated") {
|
|
251
|
+
docks?.noteLayoutUpdated(event.geometry);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (event.kind === "pane.moved") {
|
|
255
|
+
// herdr rewrites the pane id on a move; the registry and dock state
|
|
256
|
+
// follow it so ownership is not lost to the user reorganizing.
|
|
257
|
+
const held = registry.forget(event.previousPaneId);
|
|
258
|
+
if (held) {
|
|
259
|
+
registry.record({ ...held, ref: { paneId: event.paneId, tabId: event.tabId, workspaceId: event.workspaceId } });
|
|
260
|
+
docks?.notePaneMoved(event.previousPaneId, event.paneId, event.tabId);
|
|
261
|
+
}
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
docks?.notePaneGone(event.paneId);
|
|
209
265
|
const dropped = registry.forget(event.paneId);
|
|
210
266
|
if (dropped) {
|
|
211
267
|
log("debug", `mux pane ${event.paneId} left on ${event.kind}; dropped from the registry`);
|
|
@@ -231,38 +287,57 @@ export function createMuxRuntime(options: MuxRuntimeOptions): MuxRuntime {
|
|
|
231
287
|
},
|
|
232
288
|
|
|
233
289
|
async openUtilityPane(request: MuxOpenUtilityPaneRequest): Promise<MuxPaneRef | null> {
|
|
290
|
+
// Idempotence for docks: a slot that already has a pane answers with it.
|
|
291
|
+
const dockSlot = request.dock?.slot;
|
|
292
|
+
if (dockSlot && docks) {
|
|
293
|
+
const existing = docks.stateFor(dockSlot);
|
|
294
|
+
const held = existing ? registry.byPaneId(existing.paneId) : null;
|
|
295
|
+
if (held) return held.ref;
|
|
296
|
+
}
|
|
234
297
|
return await attempt(
|
|
235
298
|
"openUtilityPane",
|
|
236
299
|
async (live) => {
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
300
|
+
let ref: MuxPaneRef;
|
|
301
|
+
if (request.dock && docks) {
|
|
302
|
+
const placed = await docks.open(request.dock.slot, {
|
|
303
|
+
...(request.dock.share === undefined ? {} : { share: request.dock.share }),
|
|
304
|
+
cwd: request.cwd,
|
|
305
|
+
...(request.env ? { env: request.env } : {}),
|
|
306
|
+
});
|
|
307
|
+
// A refusal (anchor too small) is an answer, not a failure.
|
|
308
|
+
if (placed === null) return null;
|
|
309
|
+
ref = placed;
|
|
310
|
+
} else {
|
|
311
|
+
const anchor = anchorPaneId ?? (await live.paneCurrent()).paneId;
|
|
312
|
+
const pane = await live.paneSplit({
|
|
313
|
+
direction: request.direction ?? "right",
|
|
314
|
+
targetPaneId: anchor,
|
|
315
|
+
cwd: request.cwd,
|
|
316
|
+
focus: false,
|
|
317
|
+
...(request.env ? { env: request.env } : {}),
|
|
318
|
+
});
|
|
319
|
+
ref = { paneId: pane.paneId, tabId: pane.tabId, workspaceId: pane.workspaceId };
|
|
320
|
+
}
|
|
246
321
|
const purpose = request.purpose ?? "utility";
|
|
247
322
|
registry.record(paneRecord(ref, { purpose, label: request.label, openedAt: now() }));
|
|
248
323
|
const title = request.title;
|
|
249
324
|
const titleSupported = title !== undefined && muxSupportsMethod(detection.server, "pane.rename");
|
|
250
325
|
if (titleSupported) {
|
|
251
326
|
try {
|
|
252
|
-
await live.paneRename(
|
|
327
|
+
await live.paneRename(ref.paneId, title);
|
|
253
328
|
} catch {
|
|
254
329
|
// Presentation is optional. A server that advertises the floor but
|
|
255
330
|
// lacks or refuses rename must not strand an otherwise healthy pane.
|
|
256
331
|
}
|
|
257
332
|
}
|
|
258
333
|
// The `role` token is what adoptPane finds again after a restart.
|
|
259
|
-
await tagOwner(live,
|
|
334
|
+
await tagOwner(live, ref.paneId, { role: token(purpose) }, titleSupported ? title : undefined);
|
|
260
335
|
if (request.argv.length > 0) {
|
|
261
336
|
// herdr has no argv parameter on pane.split, so the command goes in
|
|
262
337
|
// through the pane's shell. `exec` replaces the shell so the pane
|
|
263
338
|
// exits with the program and emits pane.exited for reconciliation.
|
|
264
339
|
const redirect = request.stdoutPath ? ` > ${shellQuote([request.stdoutPath])}` : "";
|
|
265
|
-
await live.paneSendText(
|
|
340
|
+
await live.paneSendText(ref.paneId, `exec ${shellQuote(request.argv)}${redirect}\n`);
|
|
266
341
|
}
|
|
267
342
|
return ref;
|
|
268
343
|
},
|
|
@@ -280,13 +355,18 @@ export function createMuxRuntime(options: MuxRuntimeOptions): MuxRuntime {
|
|
|
280
355
|
async (live) => {
|
|
281
356
|
await live.paneClose(paneId);
|
|
282
357
|
registry.forget(paneId);
|
|
358
|
+
// Dock state must fall with the registry entry, not ride on the async
|
|
359
|
+
// pane.closed push: if the event subscription failed at start(), no
|
|
360
|
+
// push ever comes, and a slot pointing at a dead pane makes every
|
|
361
|
+
// later dock open return the corpse.
|
|
362
|
+
docks?.notePaneGone(paneId);
|
|
283
363
|
return true;
|
|
284
364
|
},
|
|
285
365
|
false,
|
|
286
366
|
);
|
|
287
367
|
},
|
|
288
368
|
|
|
289
|
-
async adoptPane(request: { purpose: MuxPanePurpose; label: string }): Promise<MuxPaneRef | null> {
|
|
369
|
+
async adoptPane(request: { purpose: MuxPanePurpose; label: string; dock?: DockSlot }): Promise<MuxPaneRef | null> {
|
|
290
370
|
const existing = registry.byPurpose(request.purpose);
|
|
291
371
|
if (existing) return existing.ref;
|
|
292
372
|
return await attempt(
|
|
@@ -294,7 +374,7 @@ export function createMuxRuntime(options: MuxRuntimeOptions): MuxRuntime {
|
|
|
294
374
|
async (live) => {
|
|
295
375
|
const snapshot = await live.snapshot();
|
|
296
376
|
for (const pane of snapshot.panes) {
|
|
297
|
-
if (pane.tokens
|
|
377
|
+
if (!hasOwnedPaneToken(pane.tokens)) continue;
|
|
298
378
|
if (pane.tokens.role !== request.purpose) continue;
|
|
299
379
|
if (registry.owns(pane.paneId)) continue;
|
|
300
380
|
// Another workspace's pane belongs to another session's screen;
|
|
@@ -304,6 +384,9 @@ export function createMuxRuntime(options: MuxRuntimeOptions): MuxRuntime {
|
|
|
304
384
|
registry.record(
|
|
305
385
|
paneRecord(ref, { purpose: request.purpose, label: request.label, openedAt: now(), adopted: true }),
|
|
306
386
|
);
|
|
387
|
+
// Crash recovery for a dock: the surviving pane takes the slot back so
|
|
388
|
+
// geometry management resumes instead of a second dock opening.
|
|
389
|
+
if (request.dock && docks) docks.adopt(request.dock, ref);
|
|
307
390
|
log("info", `mux adopted a ${request.purpose} pane left open by a previous session`);
|
|
308
391
|
return ref;
|
|
309
392
|
}
|
|
@@ -313,6 +396,44 @@ export function createMuxRuntime(options: MuxRuntimeOptions): MuxRuntime {
|
|
|
313
396
|
);
|
|
314
397
|
},
|
|
315
398
|
|
|
399
|
+
async focusPane(paneId: string): Promise<boolean> {
|
|
400
|
+
// Ownership first: focusing the user's own panes stays off the table
|
|
401
|
+
// even though the wire allows it.
|
|
402
|
+
if (!registry.owns(paneId)) return false;
|
|
403
|
+
if (!muxSupportsMethod(detection.server, "pane.focus")) return false;
|
|
404
|
+
return await attempt(
|
|
405
|
+
"focusPane",
|
|
406
|
+
async (live) => {
|
|
407
|
+
await live.paneFocus(paneId);
|
|
408
|
+
return true;
|
|
409
|
+
},
|
|
410
|
+
false,
|
|
411
|
+
);
|
|
412
|
+
},
|
|
413
|
+
|
|
414
|
+
async zoomPane(paneId: string, mode: "on" | "off" | "toggle"): Promise<boolean> {
|
|
415
|
+
if (!registry.owns(paneId)) return false;
|
|
416
|
+
if (!muxSupportsMethod(detection.server, "pane.zoom")) return false;
|
|
417
|
+
return await attempt(
|
|
418
|
+
"zoomPane",
|
|
419
|
+
async (live) => {
|
|
420
|
+
const result = await live.paneZoom(paneId, mode);
|
|
421
|
+
// A toggle always changes state; on/off report false when already there.
|
|
422
|
+
return result.changed;
|
|
423
|
+
},
|
|
424
|
+
false,
|
|
425
|
+
);
|
|
426
|
+
},
|
|
427
|
+
|
|
428
|
+
async resizeDock(slot: DockSlot, share: number): Promise<boolean> {
|
|
429
|
+
if (!docks) return false;
|
|
430
|
+
return await attempt("resizeDock", () => docks.resize(slot, share), false);
|
|
431
|
+
},
|
|
432
|
+
|
|
433
|
+
docks(): ReadonlyArray<DockState> {
|
|
434
|
+
return docks?.states() ?? [];
|
|
435
|
+
},
|
|
436
|
+
|
|
316
437
|
async notify(request: MuxNotifyRequest): Promise<void> {
|
|
317
438
|
if (!muxSupportsMethod(detection.server, "notification.show")) {
|
|
318
439
|
log("debug", `mux notify skipped, server protocol is below the notification.show floor: ${request.title}`);
|
|
@@ -404,9 +525,17 @@ export function createMuxRuntime(options: MuxRuntimeOptions): MuxRuntime {
|
|
|
404
525
|
subscription?.close();
|
|
405
526
|
subscription = null;
|
|
406
527
|
paneGoneHandlers.clear();
|
|
407
|
-
//
|
|
408
|
-
//
|
|
409
|
-
//
|
|
528
|
+
// Docks close with the session: a native application takes its windows
|
|
529
|
+
// with it. A crash never reaches this method, which is exactly what
|
|
530
|
+
// leaves dock panes behind for the next session's adoption path.
|
|
531
|
+
// Unmanaged utility panes are the operator's and stay open.
|
|
532
|
+
if (client && docks) {
|
|
533
|
+
for (const paneId of docks.paneIds()) {
|
|
534
|
+
await client.paneClose(paneId).catch(() => undefined);
|
|
535
|
+
registry.forget(paneId);
|
|
536
|
+
}
|
|
537
|
+
docks.clear();
|
|
538
|
+
}
|
|
410
539
|
if (client) await client.close().catch(() => undefined);
|
|
411
540
|
},
|
|
412
541
|
};
|
|
@@ -414,12 +543,15 @@ export function createMuxRuntime(options: MuxRuntimeOptions): MuxRuntime {
|
|
|
414
543
|
const start = async (): Promise<void> => {
|
|
415
544
|
if (detection.mode === "none" || client === null) return;
|
|
416
545
|
try {
|
|
417
|
-
|
|
546
|
+
const kinds: ("pane.closed" | "pane.exited" | "pane.moved" | "layout.updated")[] = ["pane.closed", "pane.exited"];
|
|
547
|
+
if (docks) kinds.push("pane.moved", "layout.updated");
|
|
548
|
+
subscription = await client.subscribe(kinds, onEvent, {
|
|
418
549
|
onResync: (snapshot) => {
|
|
419
550
|
// A reconnect means events were missed. The snapshot is the authority
|
|
420
551
|
// on which panes still exist, so anything Clio thinks it owns that is
|
|
421
552
|
// absent from it is gone.
|
|
422
553
|
const dropped = registry.reconcile(snapshot.panes.map((pane) => pane.paneId));
|
|
554
|
+
for (const record of dropped) docks?.notePaneGone(record.ref.paneId);
|
|
423
555
|
if (dropped.length > 0) {
|
|
424
556
|
log("info", `mux reconciled ${dropped.length} pane(s) closed while the socket was down`);
|
|
425
557
|
}
|