@iowarp/clio-coder 0.3.8 → 0.4.0
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 +102 -0
- package/NOTICE +33 -0
- package/README.md +12 -3
- package/dist/{acp-U67UHUK2.js → acp-G5WJBNCT.js} +13 -12
- package/dist/{agents-YU6SGALZ.js → agents-FMV2Q5G4.js} +41 -36
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-5ZPJOIVG.js → auth-3IDSJEIK.js} +18 -18
- package/dist/{builtins-C6JMZVV6.js → builtins-XCZWXSC7.js} +5 -5
- package/dist/{chunk-IFBNV6H6.js → chunk-2ANTL7MR.js} +3 -3
- package/dist/{chunk-5FR74PWO.js → chunk-2JDWVJND.js} +2 -2
- package/dist/{chunk-XWSF374K.js → chunk-2OQE55CK.js} +3 -3
- package/dist/{chunk-5H3GB5BO.js → chunk-32KWKNSF.js} +8 -384
- package/dist/{chunk-KTYTFRMB.js → chunk-36EJLSQQ.js} +34 -36
- package/dist/{chunk-FHJEP5SW.js → chunk-3BT2XMV4.js} +19 -13
- package/dist/chunk-3DPEIQKN.js +113 -0
- package/dist/{chunk-GU2UIAFZ.js → chunk-3URVFKWK.js} +7 -7
- package/dist/{chunk-WEH5XRJQ.js → chunk-3XML7CDN.js} +3 -3
- package/dist/chunk-42FMPA75.js +101 -0
- package/dist/{chunk-A2NJGIB3.js → chunk-5LXZXPKX.js} +2 -2
- package/dist/{chunk-VCBR6CU7.js → chunk-5PSMVOLM.js} +2 -2
- package/dist/{chunk-NMPKI6XL.js → chunk-6DB53AJS.js} +203 -25
- package/dist/{chunk-3BINW3FP.js → chunk-76ONBSIA.js} +2 -2
- package/dist/{chunk-26LEYJZH.js → chunk-7MCTRUCE.js} +2 -2
- package/dist/{chunk-TYPGUK6W.js → chunk-AB44T6BB.js} +111 -5
- package/dist/{chunk-K4XHGFR5.js → chunk-B7OBL7PK.js} +317 -721
- package/dist/{chunk-B5CSFE7B.js → chunk-BBVJUZHB.js} +2 -2
- package/dist/{chunk-EQ63NRB7.js → chunk-BBVYXMFO.js} +2 -2
- package/dist/{chunk-ZVJ5BLO2.js → chunk-BKFJHQCA.js} +154 -16
- package/dist/chunk-BKFM6EJV.js +462 -0
- package/dist/chunk-BMS5RKQY.js +27 -0
- package/dist/{chunk-ZNLWCMVZ.js → chunk-BPKCPIL7.js} +2 -2
- package/dist/{chunk-TLQJPP24.js → chunk-BUMFYQFY.js} +1394 -1349
- package/dist/{chunk-BNAZZHFG.js → chunk-BYP5D4HI.js} +1 -1
- package/dist/chunk-C2LTL2W6.js +2447 -0
- package/dist/{chunk-ME6CCNFO.js → chunk-CODPRO7Q.js} +8 -8
- package/dist/{chunk-E77JEWSD.js → chunk-CTJ4RNAA.js} +7 -37
- package/dist/{chunk-ODFEOB4F.js → chunk-CY6FY24N.js} +26 -8
- package/dist/{chunk-7RFXX52T.js → chunk-DQITNCXG.js} +642 -172
- package/dist/{chunk-TTHACPOM.js → chunk-DYJP44XW.js} +578 -117
- package/dist/{chunk-2HFZQUHL.js → chunk-F4CKPOEQ.js} +18 -8
- package/dist/{chunk-DGSYXYMX.js → chunk-FEFIFZTL.js} +3 -3
- package/dist/{chunk-GWS3VEIW.js → chunk-FWDFM5ZU.js} +24 -3
- package/dist/{chunk-MV3K5QF2.js → chunk-GCSMB2KY.js} +2 -2
- package/dist/chunk-GKF55TAZ.js +391 -0
- package/dist/{chunk-VWZOAB7K.js → chunk-GR5G2PVF.js} +9 -8
- package/dist/{chunk-4DGYLA73.js → chunk-GXNLGKAB.js} +80 -9
- package/dist/chunk-HHV2GANA.js +88 -0
- package/dist/{chunk-IIZWH4XA.js → chunk-HI63TFOG.js} +5 -4
- package/dist/{chunk-WLFILSD5.js → chunk-HJJTYUHX.js} +113 -83
- package/dist/{chunk-TT36MB5S.js → chunk-HLW2MRKE.js} +3 -1
- package/dist/{chunk-PMDBGQSJ.js → chunk-HWHKMHUA.js} +7 -7
- package/dist/chunk-HZHHCK24.js +1631 -0
- package/dist/{chunk-WSB3FPX7.js → chunk-I5VEOC6I.js} +39 -143
- package/dist/chunk-IBEBSCYA.js +564 -0
- package/dist/chunk-IQ7KR472.js +362 -0
- package/dist/{chunk-A3WNZD3P.js → chunk-J4W7KFM7.js} +949 -972
- package/dist/{chunk-TB5666IT.js → chunk-JDG2WCRO.js} +5 -5
- package/dist/{chunk-N22QMJKY.js → chunk-K5C3NCBD.js} +4 -4
- package/dist/{chunk-CGKSTWHD.js → chunk-K6BSR66V.js} +2 -1
- package/dist/{chunk-5C3AQNDW.js → chunk-KFZI4NIL.js} +216 -38
- package/dist/chunk-KMVISBZR.js +132 -0
- package/dist/{chunk-WXY7KU3G.js → chunk-LDQ2ZF2M.js} +2 -2
- package/dist/{chunk-XN3L4EYL.js → chunk-LQ3DZAMX.js} +3 -3
- package/dist/{chunk-U6MBIEMB.js → chunk-LY4S7GJC.js} +173 -144
- package/dist/{chunk-4SPRNWDE.js → chunk-MLKNTWH2.js} +19 -19
- package/dist/{chunk-MVVUPGPW.js → chunk-MXHC5QYU.js} +6 -6
- package/dist/chunk-NEKRRTYW.js +56 -0
- package/dist/{chunk-SPULKLCF.js → chunk-NHCZP4K7.js} +3 -3
- package/dist/chunk-NHLBIGRH.js +1506 -0
- package/dist/chunk-NQQH3YT7.js +302 -0
- package/dist/chunk-NYS75XW5.js +15 -0
- package/dist/{chunk-GN57SG4G.js → chunk-O4XIVISU.js} +10 -8
- package/dist/{chunk-EMYUUSFG.js → chunk-O6TL7WWY.js} +6 -6
- package/dist/chunk-OQBA45DZ.js +97 -0
- package/dist/{chunk-LU7P4LHA.js → chunk-P3FOHJT4.js} +2 -2
- package/dist/chunk-PMZCIOCJ.js +25 -0
- package/dist/{chunk-I4HZDVNP.js → chunk-PQEFIJ36.js} +2 -2
- package/dist/{chunk-J3YUBZWY.js → chunk-QBJA7R7N.js} +62 -6
- package/dist/chunk-QDC3K2U3.js +262 -0
- package/dist/{chunk-2HEJ2F35.js → chunk-QLFS5GO2.js} +22 -10
- package/dist/{chunk-7RGZWPB6.js → chunk-QLL7ILRG.js} +95 -32
- package/dist/{chunk-YS5VLNH5.js → chunk-QREDIESB.js} +6 -6
- package/dist/chunk-QSNYB6ZV.js +195 -0
- package/dist/{chunk-GOXNB3AO.js → chunk-RAPCMZL4.js} +75 -4
- package/dist/chunk-RKKLTLYB.js +45 -0
- package/dist/{chunk-P43ETTHK.js → chunk-SJ5ZKQ4S.js} +2 -2
- package/dist/{chunk-GPIEI3LY.js → chunk-SP2RXXYO.js} +6 -54
- package/dist/chunk-SUCTJL45.js +45 -0
- package/dist/{chunk-DYIM5TJT.js → chunk-SUW5DORT.js} +263 -7
- package/dist/chunk-T56WDKA5.js +183 -0
- package/dist/chunk-TVHHYFHE.js +255 -0
- package/dist/{chunk-FJ3H4MN5.js → chunk-TZ3SGWZZ.js} +3 -3
- package/dist/{chunk-MXKJU4JB.js → chunk-U77AMWDL.js} +91 -10
- package/dist/{chunk-5DHKRSMQ.js → chunk-ULC6OTWO.js} +11 -7
- package/dist/{chunk-RWSI4YD7.js → chunk-UM7N4G5A.js} +33 -12
- package/dist/{chunk-FCSXB6T2.js → chunk-UOSL25KY.js} +14 -2
- package/dist/{chunk-IGWKHNIQ.js → chunk-UXMFQ54G.js} +44 -37
- package/dist/{chunk-HFSBBKSQ.js → chunk-V5DHCITQ.js} +171 -3
- package/dist/{chunk-5WIGXA4T.js → chunk-VAZSBTKF.js} +111 -4
- package/dist/{chunk-VHN4MY6O.js → chunk-VEO4AP2K.js} +2 -2
- package/dist/{chunk-IJ7RPIYJ.js → chunk-VFA6GDY5.js} +65 -4
- package/dist/{chunk-PT7HYKEM.js → chunk-VO2LKSTM.js} +2 -2
- package/dist/chunk-VO67MWHC.js +75 -0
- package/dist/{chunk-XK56QHLX.js → chunk-VPKWYKEY.js} +19 -5
- package/dist/{chunk-TANS5ZJS.js → chunk-VYMXRQI6.js} +36 -22
- package/dist/{chunk-WWCZ5F23.js → chunk-W5VSYASO.js} +77 -16
- package/dist/{chunk-WNIJTQQK.js → chunk-WZR7K7ZX.js} +72 -116
- package/dist/{chunk-5Q2VVUKB.js → chunk-X3YGUTOB.js} +4 -4
- package/dist/chunk-X75E3D2N.js +686 -0
- package/dist/{chunk-VAWNZU7Z.js → chunk-YDFRH54B.js} +4 -4
- package/dist/chunk-YJX4SHTD.js +40 -0
- package/dist/{chunk-ZI647VB5.js → chunk-YPI3QQCF.js} +2 -2
- package/dist/chunk-Z2RR6MAK.js +127 -0
- package/dist/{chunk-FBVTI2TJ.js → chunk-Z4TXYIEG.js} +12 -131
- package/dist/cli/index.js +47 -36
- package/dist/{clio-QVTYJ57A.js → clio-2JXHBBY5.js} +7 -7
- package/dist/{code-nav-FGGFIE7L.js → code-nav-3YYRMYNF.js} +8 -8
- package/dist/{compile-cache-CVJMMODC.js → compile-cache-7FPE6PS3.js} +3 -3
- package/dist/{components-ZFA3SAER.js → components-RYZV4JGP.js} +5 -5
- package/dist/{config-LW5IJFQN.js → config-QZPCMYSO.js} +99 -63
- package/dist/{configure-7XIZCOU4.js → configure-TEGEBYCA.js} +23 -22
- package/dist/{context-Y6Y7QPR6.js → context-AV7OEZ4D.js} +12 -12
- package/dist/{context-L3WL3X7K.js → context-E6H5RNMC.js} +56 -47
- package/dist/{context-N52ZA626.js → context-GSXUE4CT.js} +29 -27
- package/dist/{context-clear-MBQRLSDQ.js → context-clear-SHIBYK6T.js} +55 -46
- package/dist/{context-index-HVMFQHK3.js → context-index-HNG3MOME.js} +2 -2
- package/dist/{context-working-set-GS6DSO7F.js → context-working-set-5ZGKPGZQ.js} +13 -13
- package/dist/{dispatch-runner-22ZCNOM3.js → dispatch-runner-EFMJT4LD.js} +93 -64
- package/dist/{docs-7LQ23DLM.js → docs-23KQS3XK.js} +5 -5
- package/dist/doctor-QOA5FNY5.js +313 -0
- package/dist/{eval-BEC2WHDA.js → eval-TFBYQH4H.js} +2032 -156
- package/dist/eval-inventory-SXH7PDKX.js +316 -0
- package/dist/{evidence-REJUMSKM.js → evidence-ERGESKGN.js} +203 -48
- package/dist/{evolve-PY5ZBA5K.js → evolve-VDXTSYCJ.js} +52 -43
- package/dist/{extensions-HVKU65YU.js → extensions-7BGBHN57.js} +13 -7
- package/dist/{fleet-7WZEWRFA.js → fleet-2RRVDF2V.js} +228 -108
- package/dist/{fleet-commands-UVHWM76J.js → fleet-commands-VJ726XIA.js} +11 -11
- package/dist/fleet-decisions-EPAPM3XJ.js +157 -0
- package/dist/{fleet-graph-6ULH7PES.js → fleet-graph-JF5QOATM.js} +18 -15
- package/dist/fleet-inspect-VLY4S7QM.js +442 -0
- package/dist/{fleet-preflight-J53T6CCE.js → fleet-preflight-AIZUEJOY.js} +5 -5
- package/dist/{fleet-validate-72PC4SLA.js → fleet-validate-AJRPDMDV.js} +22 -19
- package/dist/fleet-verify-JFEL2L3H.js +175 -0
- package/dist/fleet-view-ZCON35AG.js +102 -0
- package/dist/{init-OG3TPGQG.js → init-DN2WWLFE.js} +72 -62
- package/dist/install-XGLBQY5E.js +13 -0
- package/dist/interop-OZBKXAYL.js +114 -0
- package/dist/{library-CNTMPLRF.js → library-YWZG7IMW.js} +21 -18
- package/dist/{memory-6IS7F275.js → memory-I4C4HMLW.js} +54 -45
- package/dist/{models-ENRJDA5W.js → models-CEYXJBO6.js} +33 -30
- package/dist/{monitor-XLDVO7TN.js → monitor-NZ6GCI3P.js} +59 -52
- package/dist/{orchestrator-6KSPYRHA.js → orchestrator-GCGQ4N5I.js} +7827 -7328
- package/dist/panes-HMABYVO4.js +58 -0
- package/dist/panes-KY6W3V2E.js +103 -0
- package/dist/{paths-DBXMZMDU.js → paths-II4K7DNR.js} +5 -5
- package/dist/{reset-RZ4ER727.js → reset-DQ6FGCSH.js} +13 -11
- package/dist/resources-BB3MVJMD.js +111 -0
- package/dist/{run-Y2CNK5RU.js → run-H2GQDUER.js} +119 -87
- package/dist/{share-A55GYP6Z.js → share-GTJN6A5O.js} +20 -17
- package/dist/{skills-ALC5J6AT.js → skills-L55TEW6R.js} +33 -24
- package/dist/{skills-eval-JPBEBYQU.js → skills-eval-XVXPH2JI.js} +67 -56
- package/dist/skills-inventory-S4MXPJFV.js +126 -0
- package/dist/slash-commands-ZSGASKJC.js +77 -0
- package/dist/{steer-GGWFUJUD.js → steer-RZGSCY4R.js} +4 -4
- package/dist/{support-MIETYA5E.js → support-PKEUNNQL.js} +6 -6
- package/dist/{targets-VGNXIR3S.js → targets-NCPZ644J.js} +68 -40
- package/dist/{terminal-lease-WOBR64YA.js → terminal-lease-44SV3YCN.js} +6 -4
- package/dist/tools-DAF3DI3C.js +27 -0
- package/dist/{trace-PNCASAXC.js → trace-FYVW2MQA.js} +207 -10
- package/dist/tui-primitives-2AKXQNZK.js +13 -0
- package/dist/{uninstall-ZJF5H5ZN.js → uninstall-DW2PNOIC.js} +5 -5
- package/dist/{upgrade-FUSUAGHR.js → upgrade-3XPP6OQL.js} +27 -24
- package/dist/{usage-N4MKVHKD.js → usage-3NLHGTU2.js} +114 -62
- package/dist/{verifiers-YAWOJ3H2.js → verifiers-SSQONKRT.js} +172 -13
- package/dist/{verify-LTDHYBGY.js → verify-3U6J7FZI.js} +10 -10
- package/dist/{web-fetch-2YHJ3KTG.js → web-fetch-S7RR6GZ7.js} +3 -3
- package/dist/{wiki-generate-6M7GHTBJ.js → wiki-generate-CEHYGPGQ.js} +75 -65
- package/dist/with-panes-MKB46MPQ.js +782 -0
- package/dist/worker/entry.js +106 -75
- package/docs/README.md +3 -2
- package/docs/acp.md +24 -3
- package/docs/alcf-provider.md +1 -1
- package/docs/architecture.md +2 -2
- package/docs/artifact-versions.md +9 -1
- package/docs/built-in-agents.md +1 -1
- package/docs/capacity-and-scheduling.md +62 -3
- package/docs/commands-and-modes.md +31 -2
- package/docs/configuration-and-targets.md +69 -12
- package/docs/context-engine.md +63 -4
- package/docs/development-pipeline.md +19 -0
- package/docs/dispatch-typed-intent.md +385 -0
- package/docs/documentation-coverage.md +5 -5
- package/docs/documentation-guide.md +1 -1
- package/docs/environment-variables.md +3 -0
- package/docs/eval-runner.md +262 -11
- package/docs/evals-internal.md +72 -2
- package/docs/evidence-and-memory.md +12 -11
- package/docs/evolution.md +1 -1
- package/docs/exit-codes-and-output.md +1 -1
- package/docs/extensions-and-sharing.md +27 -1
- package/docs/fleet-dispatch.md +25 -4
- package/docs/installation-and-lifecycle.md +15 -2
- package/docs/middleware-and-components.md +1 -1
- package/docs/model-catalog.md +10 -1
- package/docs/observability.md +55 -4
- package/docs/proactive-memory.md +127 -14
- package/docs/prompt-envelope-and-tools.md +19 -1
- package/docs/provider-adapter-cookbook.md +1 -1
- package/docs/release-cut-checklist.md +19 -3
- package/docs/safety-model.md +2 -2
- package/docs/scientific-validation.md +3 -3
- package/docs/session-lifecycle.md +1 -1
- package/docs/skills-marketplace.md +1 -1
- package/docs/tool-usage.md +18 -8
- package/docs/trace-store.md +1 -1
- package/docs/troubleshooting.md +88 -1
- package/docs/tui-design.md +1 -1
- package/docs/worker-dispatch-mechanics.md +1 -1
- package/package.json +5 -2
- package/src/cli/acp.ts +6 -2
- package/src/cli/agents.ts +1 -1
- package/src/cli/argv.ts +25 -0
- package/src/cli/config-inspect.ts +33 -6
- package/src/cli/config.ts +1 -1
- package/src/cli/configure.ts +23 -21
- package/src/cli/doctor-panes.ts +124 -0
- package/src/cli/doctor-state-size.ts +82 -0
- package/src/cli/doctor-toolchain.ts +57 -0
- package/src/cli/doctor.ts +22 -1
- package/src/cli/eval-inventory.ts +436 -0
- package/src/cli/eval.ts +93 -16
- package/src/cli/evidence-detail.ts +88 -0
- package/src/cli/evidence-inventory.ts +183 -0
- package/src/cli/evidence.ts +30 -5
- package/src/cli/extensions.ts +5 -1
- package/src/cli/fleet-decisions.ts +69 -0
- package/src/cli/fleet-inspect.ts +334 -0
- package/src/cli/fleet-verify.ts +133 -0
- package/src/cli/fleet-view.ts +810 -0
- package/src/cli/fleet.ts +179 -39
- package/src/cli/index.ts +14 -2
- package/src/cli/interop-inspect.ts +128 -0
- package/src/cli/interop.ts +34 -0
- package/src/cli/panes.ts +35 -0
- package/src/cli/reset.ts +5 -2
- package/src/cli/run.ts +58 -0
- package/src/cli/skills-inventory.ts +185 -0
- package/src/cli/skills.ts +16 -13
- package/src/cli/targets.ts +44 -13
- package/src/cli/tools.ts +321 -0
- package/src/cli/trace-inspect.ts +252 -0
- package/src/cli/trace.ts +85 -5
- package/src/cli/usage.ts +63 -14
- package/src/cli/verifiers-inspect.ts +347 -0
- package/src/cli/verifiers.ts +9 -0
- package/src/core/bus-events.ts +33 -1
- package/src/core/cache-telemetry.ts +42 -0
- package/src/core/config.ts +67 -0
- package/src/core/defaults.ts +124 -8
- package/src/core/endpoint-key.ts +27 -0
- package/src/core/residency-target-key.ts +25 -0
- package/src/core/response-schema.ts +80 -6
- package/src/core/theme-token-hex.ts +43 -0
- package/src/core/tool-names.ts +2 -1
- package/src/core/xdg.ts +1 -1
- package/src/domains/agents/fleets/build-review.md +0 -3
- package/src/domains/agents/fleets/build-test.md +0 -3
- package/src/domains/agents/result-contract-filesystem.ts +32 -0
- package/src/domains/agents/result-contract.ts +164 -35
- package/src/domains/config/classify.ts +6 -0
- package/src/domains/context/codewiki/coordinator.ts +12 -4
- package/src/domains/dispatch/admission-error.ts +9 -0
- package/src/domains/dispatch/admission.ts +52 -14
- package/src/domains/dispatch/capacity-lease.ts +118 -9
- package/src/domains/dispatch/contract.ts +11 -0
- package/src/domains/dispatch/council-topology.ts +398 -0
- package/src/domains/dispatch/execution-plan.ts +44 -4
- package/src/domains/dispatch/extension.ts +378 -82
- package/src/domains/dispatch/fleet-node-prompt.ts +62 -0
- package/src/domains/dispatch/fleet-plan.ts +7 -2
- package/src/domains/dispatch/fleet-run.ts +87 -4
- package/src/domains/dispatch/gate-decisions.ts +11 -1
- package/src/domains/dispatch/gate-role-prompts.ts +9 -0
- package/src/domains/dispatch/gate-topology.ts +289 -0
- package/src/domains/dispatch/heartbeat.ts +32 -8
- package/src/domains/dispatch/index.ts +22 -0
- package/src/domains/dispatch/intent-compatibility.ts +330 -0
- package/src/domains/dispatch/intent.ts +85 -1
- package/src/domains/dispatch/orphan-recovery.ts +5 -0
- package/src/domains/dispatch/reservation-store.ts +139 -11
- package/src/domains/dispatch/run-event-journal-bridge.ts +149 -0
- package/src/domains/dispatch/run-event-journal.ts +598 -0
- package/src/domains/dispatch/state.ts +49 -1
- package/src/domains/dispatch/types.ts +13 -0
- package/src/domains/dispatch/validation.ts +33 -8
- package/src/domains/dispatch/worker-spawn.ts +25 -11
- package/src/domains/dispatch/write-boundary-enforcer.ts +20 -3
- package/src/domains/dispatch/write-boundary.ts +62 -1
- package/src/domains/eval/artifacts/store.ts +62 -0
- package/src/domains/eval/compare/behavioral.ts +224 -0
- package/src/domains/eval/compare/compare.ts +342 -2
- package/src/domains/eval/compare/envelope.ts +128 -0
- package/src/domains/eval/compare/gates.ts +24 -6
- package/src/domains/eval/compare/thresholds.ts +30 -3
- package/src/domains/eval/execution-provenance.ts +240 -0
- package/src/domains/eval/inventory.ts +113 -0
- package/src/domains/eval/metrics/aggregate.ts +136 -0
- package/src/domains/eval/metrics/call-ledger-stream.ts +112 -0
- package/src/domains/eval/metrics/tracked.ts +413 -0
- package/src/domains/eval/provenance.ts +117 -0
- package/src/domains/eval/reports/comparison.ts +128 -0
- package/src/domains/eval/reports/junit.ts +17 -3
- package/src/domains/eval/reports/markdown.ts +3 -3
- package/src/domains/eval/reports/text.ts +14 -0
- package/src/domains/eval/run-compare.ts +20 -0
- package/src/domains/eval/runners/clio-run.ts +127 -0
- package/src/domains/eval/runners/external-command.ts +28 -3
- package/src/domains/eval/schema/adapter.ts +111 -0
- package/src/domains/eval/schema/artifact.ts +20 -0
- package/src/domains/eval/schema/behavioral-metrics.ts +204 -0
- package/src/domains/eval/schema/behavioral.ts +520 -0
- package/src/domains/eval/schema/execution-envelope.ts +194 -0
- package/src/domains/eval/schema/serving.ts +105 -0
- package/src/domains/eval/schema/suite.ts +38 -8
- package/src/domains/eval/schema/validate.ts +58 -3
- package/src/domains/eval/schema/verdict.ts +237 -0
- package/src/domains/eval/suites/resolve.ts +2 -0
- package/src/domains/eval/suites/run.ts +264 -33
- package/src/domains/eval/verifiers/command.ts +2 -1
- package/src/domains/eval/workspaces/temp-copy.ts +145 -13
- package/src/domains/evidence/build.ts +2 -13
- package/src/domains/evidence/eval.ts +2 -12
- package/src/domains/evidence/findings-markdown.ts +33 -0
- package/src/domains/evidence/run-trust.ts +7 -113
- package/src/domains/evidence/store.ts +6 -0
- package/src/domains/evidence/trust-projection.ts +2 -2
- package/src/domains/extensions/compatibility.ts +285 -0
- package/src/domains/extensions/discovery.ts +38 -3
- package/src/domains/extensions/resources.ts +1 -1
- package/src/domains/extensions/state.ts +12 -3
- package/src/domains/extensions/types.ts +2 -0
- package/src/domains/lifecycle/doctor.ts +69 -1
- package/src/domains/memory/index.ts +14 -1
- package/src/domains/memory/task-bank-promotion.ts +64 -0
- package/src/domains/memory/task-memory-policy.ts +82 -17
- package/src/domains/memory/task-memory-spend.ts +131 -0
- package/src/domains/memory/task-memory-status.ts +7 -0
- package/src/domains/memory/task-memory-telemetry.ts +3 -0
- package/src/domains/middleware/index.ts +1 -0
- package/src/domains/middleware/memory-intervention.ts +97 -21
- package/src/domains/middleware/memory-step-endpoint.ts +71 -0
- package/src/domains/mux/contract.ts +434 -0
- package/src/domains/mux/detect.ts +158 -0
- package/src/domains/mux/extension.ts +47 -0
- package/src/domains/mux/index.ts +96 -0
- package/src/domains/mux/manifest.ts +6 -0
- package/src/domains/mux/operations.ts +164 -0
- package/src/domains/mux/pane-registry.ts +90 -0
- package/src/domains/mux/protocol.ts +49 -0
- package/src/domains/mux/socket-client.ts +816 -0
- package/src/domains/mux/types.ts +222 -0
- package/src/domains/mux/viewer-command.ts +59 -0
- package/src/domains/mux/yazi/assets/init.lua +2 -0
- package/src/domains/mux/yazi/assets/plugins/git.yazi/LICENSE +21 -0
- package/src/domains/mux/yazi/assets/plugins/git.yazi/README.md +78 -0
- package/src/domains/mux/yazi/assets/plugins/git.yazi/main.lua +255 -0
- package/src/domains/mux/yazi/assets/plugins/git.yazi/types.lua +12 -0
- package/src/domains/mux/yazi/assets/yazi.toml +17 -0
- package/src/domains/mux/yazi/event-stream.ts +180 -0
- package/src/domains/mux/yazi/profile.ts +299 -0
- package/src/domains/mux/yazi/session.ts +228 -0
- package/src/domains/mux/yazi/theme.ts +30 -0
- package/src/domains/observability/background-memory-usage.ts +140 -0
- package/src/domains/observability/cost.ts +22 -1
- package/src/domains/observability/index.ts +9 -0
- package/src/domains/observability/out-of-turn-usage.ts +51 -2
- package/src/domains/observability/trace-store.ts +234 -2
- package/src/domains/prompts/compiler.ts +100 -13
- package/src/domains/providers/endpoint-capacity.ts +228 -0
- package/src/domains/providers/endpoint-slots-store.ts +189 -0
- package/src/domains/providers/extension.ts +20 -3
- package/src/domains/providers/index.ts +32 -0
- package/src/domains/providers/model-runtime-capabilities.ts +32 -0
- package/src/domains/providers/models/local-models/clio-local-coding-targets.yaml +243 -1
- package/src/domains/providers/runtime-resolution.ts +8 -1
- package/src/domains/providers/runtimes/boot-manifest.ts +1 -0
- package/src/domains/providers/runtimes/builtins.ts +2 -0
- package/src/domains/providers/runtimes/common/probe-helpers.ts +31 -9
- package/src/domains/providers/runtimes/local-native/llamacpp-anthropic.ts +1 -1
- package/src/domains/providers/runtimes/local-native/llamacpp-completion.ts +1 -1
- package/src/domains/providers/runtimes/local-native/llamacpp-embed.ts +1 -1
- package/src/domains/providers/runtimes/local-native/llamacpp-rerank.ts +1 -1
- package/src/domains/providers/runtimes/local-native/llamacpp.ts +4 -1
- package/src/domains/providers/runtimes/local-native/lmstudio.ts +4 -1
- package/src/domains/providers/runtimes/local-native/ollama-native.ts +6 -1
- package/src/domains/providers/runtimes/protocol/litellm.ts +375 -0
- package/src/domains/providers/support.ts +1 -0
- package/src/domains/providers/target-model-cache.ts +124 -0
- package/src/domains/providers/types/capability-flags.ts +2 -0
- package/src/domains/providers/types/target-descriptor.ts +2 -0
- package/src/domains/resources/index.ts +3 -0
- package/src/domains/resources/prompts/loader.ts +95 -33
- package/src/domains/resources/skills/loader.ts +33 -0
- package/src/domains/safety/action-classifier.ts +6 -0
- package/src/domains/safety/call-target.ts +52 -0
- package/src/domains/safety/run-effects.ts +35 -4
- package/src/domains/session/context-accounting.ts +52 -1
- package/src/domains/session/context-ledger.ts +37 -13
- package/src/domains/session/index.ts +6 -0
- package/src/domains/session/prompt-cache.ts +140 -0
- package/src/domains/session/prompt-manifest.ts +42 -0
- package/src/domains/toolchain/archive.ts +175 -0
- package/src/domains/toolchain/contract.ts +28 -0
- package/src/domains/toolchain/extension.ts +47 -0
- package/src/domains/toolchain/index.ts +39 -0
- package/src/domains/toolchain/install.ts +327 -0
- package/src/domains/toolchain/manifest.ts +8 -0
- package/src/domains/toolchain/paths.ts +34 -0
- package/src/domains/toolchain/registry.ts +265 -0
- package/src/domains/toolchain/remove.ts +218 -0
- package/src/domains/toolchain/resolve.ts +182 -0
- package/src/domains/toolchain/types.ts +113 -0
- package/src/domains/toolchain/version.ts +88 -0
- package/src/engine/acp/adapter.ts +18 -3
- package/src/engine/acp/server.ts +413 -70
- package/src/engine/acp/types.ts +19 -1
- package/src/engine/ai.ts +35 -0
- package/src/engine/apis/llamacpp-residency.ts +55 -3
- package/src/engine/apis/lmstudio.ts +25 -5
- package/src/engine/apis/ollama-native.ts +2 -1
- package/src/engine/apis/openai-completions.ts +80 -17
- package/src/engine/apis/residency-lock.ts +3 -1
- package/src/engine/apis/residency.ts +34 -1
- package/src/engine/claude/sdk-module.ts +98 -0
- package/src/engine/claude/sdk-runtime.ts +19 -11
- package/src/engine/provider-payload.ts +29 -1
- package/src/engine/tui-primitives.ts +21 -0
- package/src/engine/tui.ts +1 -0
- package/src/engine/worker-runtime.ts +2 -13
- package/src/entry/boot-options.ts +2 -0
- package/src/entry/orchestrator.ts +279 -35
- package/src/entry/panes-activation.ts +31 -0
- package/src/entry/with-panes.ts +20 -0
- package/src/interactive/chat-loop-messages.ts +26 -7
- package/src/interactive/chat-loop.ts +318 -41
- package/src/interactive/chat-panel.ts +62 -8
- package/src/interactive/clio-editor.ts +45 -8
- package/src/interactive/context-activity.ts +5 -1
- package/src/interactive/context-meter.ts +1 -1
- package/src/interactive/context-overlay.ts +41 -10
- package/src/interactive/cost-overlay.ts +66 -6
- package/src/interactive/council-grid.ts +1 -3
- package/src/interactive/council.ts +11 -0
- package/src/interactive/dispatch-board.ts +102 -20
- package/src/interactive/fleet-run-preview.ts +41 -15
- package/src/interactive/handoff-round.ts +41 -2
- package/src/interactive/interactive-application.ts +177 -7
- package/src/interactive/interactive-input-runtime.ts +15 -0
- package/src/interactive/interactive-presentation.ts +4 -0
- package/src/interactive/interactive-shell.ts +20 -17
- package/src/interactive/interactive-slash-runtime.ts +63 -10
- package/src/interactive/memory-overlay.ts +9 -0
- package/src/interactive/modal-marker.ts +170 -0
- package/src/interactive/mutation-preview.ts +295 -0
- package/src/interactive/mux-bridge.ts +214 -0
- package/src/interactive/overlay-frame.ts +58 -2
- package/src/interactive/overlay-general-openers.ts +17 -0
- package/src/interactive/overlay-key-routing.ts +52 -3
- package/src/interactive/overlay-lifecycle.ts +56 -7
- package/src/interactive/overlay-model-selectors.ts +40 -3
- package/src/interactive/overlay-permission-lifecycle.ts +112 -24
- package/src/interactive/overlay-session-lifecycle.ts +73 -9
- package/src/interactive/overlay-transitions.ts +18 -4
- package/src/interactive/overlays/agents.ts +1 -0
- package/src/interactive/overlays/ask-user.ts +227 -49
- package/src/interactive/overlays/auth-dialog.ts +1 -0
- package/src/interactive/overlays/context-reset.ts +1 -0
- package/src/interactive/overlays/cwd-fallback.ts +1 -0
- package/src/interactive/overlays/decisions.ts +11 -11
- package/src/interactive/overlays/extensions.ts +1 -0
- package/src/interactive/overlays/fleet-run-approval.ts +1 -0
- package/src/interactive/overlays/handoff-review.ts +1 -0
- package/src/interactive/overlays/help-reference.ts +6 -0
- package/src/interactive/overlays/interop.ts +1 -0
- package/src/interactive/overlays/library-install-confirm.ts +1 -0
- package/src/interactive/overlays/library-tabs.ts +28 -0
- package/src/interactive/overlays/list-overlay.ts +10 -1
- package/src/interactive/overlays/message-picker.ts +1 -0
- package/src/interactive/overlays/model-scope.ts +86 -0
- package/src/interactive/overlays/model-selector.ts +1 -0
- package/src/interactive/overlays/prompts.ts +12 -1
- package/src/interactive/overlays/session-selector.ts +1 -0
- package/src/interactive/overlays/settings-sections.ts +30 -0
- package/src/interactive/overlays/settings.ts +614 -45
- package/src/interactive/overlays/side-question.ts +1 -0
- package/src/interactive/overlays/skills-hub.ts +3 -11
- package/src/interactive/overlays/tree-selector.ts +1 -0
- package/src/interactive/pane-policy.ts +46 -0
- package/src/interactive/panes-runtime.ts +292 -0
- package/src/interactive/permission-hint.ts +34 -2
- package/src/interactive/permission-overlay.ts +159 -9
- package/src/interactive/prewarm.ts +197 -0
- package/src/interactive/render-trace.ts +162 -15
- package/src/interactive/renderers/compaction-summary.ts +29 -0
- package/src/interactive/renderers/tool-execution.ts +4 -0
- package/src/interactive/renderers/worker-entry.ts +122 -14
- package/src/interactive/side-question.ts +58 -1
- package/src/interactive/slash-commands.ts +251 -15
- package/src/interactive/status/controller.ts +11 -0
- package/src/interactive/status/state-machine.ts +54 -2
- package/src/interactive/status/types.ts +7 -0
- package/src/interactive/tasks-overlay.ts +1 -0
- package/src/interactive/terminal-lease.ts +2 -0
- package/src/interactive/theme/tokens.ts +3 -14
- package/src/interactive/turn-context.ts +346 -33
- package/src/interactive/turn-persistence.ts +14 -4
- package/src/interactive/turn-prewarm.ts +364 -0
- package/src/interactive/turn-queues.ts +7 -4
- package/src/interactive/turn-runtime.ts +8 -1
- package/src/interactive/turn-state.ts +23 -0
- package/src/interactive/view/artifacts.ts +109 -1
- package/src/interactive/view/view-overlay.ts +29 -3
- package/src/interactive/watch-pane.ts +152 -0
- package/src/interactive/worker-progress.ts +7 -1
- package/src/interactive/worker-receipts.ts +19 -1
- package/src/interactive/worker-stream.ts +5 -0
- package/src/interactive/yazi-bridge.ts +444 -0
- package/src/tools/ask-user.ts +43 -2
- package/src/tools/bootstrap.ts +26 -2
- package/src/tools/builtin-tool-catalog.ts +15 -0
- package/src/tools/compete-worktrees.ts +83 -2
- package/src/tools/core-bootstrap.ts +2 -1
- package/src/tools/dispatch-admission.ts +14 -3
- package/src/tools/dispatch-arguments.ts +20 -20
- package/src/tools/dispatch-plan.ts +17 -9
- package/src/tools/dispatch-run-events.ts +134 -19
- package/src/tools/dispatch-runner.ts +29 -7
- package/src/tools/dispatch-scout.ts +1 -1
- package/src/tools/dispatch-types.ts +15 -3
- package/src/tools/dispatch.ts +1 -1
- package/src/tools/executables.ts +17 -14
- package/src/tools/observation.ts +54 -4
- package/src/tools/panes-surface.ts +38 -0
- package/src/tools/panes.ts +112 -0
- package/src/tools/policy.ts +10 -1
- package/src/tools/presentation.ts +1 -0
- package/src/tools/registry.ts +16 -0
- package/dist/chunk-AOCYTWAV.js +0 -449
- package/dist/chunk-HLE42MG7.js +0 -37
- package/dist/chunk-HWUFFB6L.js +0 -83
- package/dist/chunk-JOZYP4GM.js +0 -279
- package/dist/doctor-M7YEDGAE.js +0 -91
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The outward signal that a modal overlay owns the keyboard.
|
|
3
|
+
*
|
|
4
|
+
* Every framed Clio overlay captures input for as long as it is up, and until
|
|
5
|
+
* now it announced that only by being on screen. An external driver feeding
|
|
6
|
+
* keys into the pane, and herdr deciding whether the pane is idle, working, or
|
|
7
|
+
* blocked, had to infer modality from whatever the overlay happened to draw:
|
|
8
|
+
* its box title, its hint row, a footer pill. Those are per-overlay strings
|
|
9
|
+
* that move on every reskin, and the tall modals cover the very rows a screen
|
|
10
|
+
* scraper reads, so the signal is weakest exactly when a modal is largest.
|
|
11
|
+
*
|
|
12
|
+
* The marker publishes the fact out of band instead, as an OSC 0 terminal
|
|
13
|
+
* title. Three properties make the title the reliable channel here:
|
|
14
|
+
*
|
|
15
|
+
* 1. It is not screen content. No overlay can composite over it, no
|
|
16
|
+
* transcript line can counterfeit it, and no terminal height changes
|
|
17
|
+
* whether it is readable.
|
|
18
|
+
* 2. herdr already consumes it. `region = "osc_title"` is a first-class
|
|
19
|
+
* detection region (herdr `src/detect/manifest.rs`), fed from the pane's
|
|
20
|
+
* own emulator, and its rules outrank every screen rule.
|
|
21
|
+
* 3. Clio has never written a title, so the channel carries exactly one
|
|
22
|
+
* meaning and nothing else contends for it.
|
|
23
|
+
*
|
|
24
|
+
* The grammar is fixed and machine-first:
|
|
25
|
+
*
|
|
26
|
+
* clio no modal owns the keys
|
|
27
|
+
* clio [modal:permission-confirm] one modal, named
|
|
28
|
+
* clio [modal:library-install+2] top modal named, two more beneath it
|
|
29
|
+
*
|
|
30
|
+
* Nothing is written until the first modal opens, so a Clio that never opens
|
|
31
|
+
* an overlay leaves the pane's inherited title alone. From the first modal
|
|
32
|
+
* onward the title toggles between the marker and the bare base, which is what
|
|
33
|
+
* makes the absence of a marker a positive statement rather than an unknown.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/** The write side of a terminal title. `ProcessTerminal` satisfies it structurally. */
|
|
37
|
+
export interface ModalMarkerSink {
|
|
38
|
+
setTitle(title: string): void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** What the title reads when nothing modal owns the keyboard. */
|
|
42
|
+
export const MODAL_MARKER_BASE_TITLE = "clio";
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* The one regular expression an external reader needs. Exported so Clio's own
|
|
46
|
+
* contract test and herdr's manifest rule are written against the same shape
|
|
47
|
+
* rather than two hand-copied spellings of it.
|
|
48
|
+
*/
|
|
49
|
+
export const MODAL_MARKER_TITLE_PATTERN = /^clio \[modal:([a-z0-9][a-z0-9-]*)(?:\+([1-9]\d*))?\]$/u;
|
|
50
|
+
|
|
51
|
+
/** Render the title for a modal stack, outermost first. */
|
|
52
|
+
export function formatModalMarkerTitle(stack: readonly string[]): string {
|
|
53
|
+
const top = stack.at(-1);
|
|
54
|
+
if (top === undefined) return MODAL_MARKER_BASE_TITLE;
|
|
55
|
+
const beneath = stack.length - 1;
|
|
56
|
+
return `${MODAL_MARKER_BASE_TITLE} [modal:${top}${beneath > 0 ? `+${beneath}` : ""}]`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Read a marker title back. Returns null for the base title and for anything else. */
|
|
60
|
+
export function parseModalMarkerTitle(title: string): { id: string; depth: number } | null {
|
|
61
|
+
const match = MODAL_MARKER_TITLE_PATTERN.exec(title);
|
|
62
|
+
const id = match?.[1];
|
|
63
|
+
if (id === undefined) return null;
|
|
64
|
+
const beneath = match?.[2];
|
|
65
|
+
return { id, depth: beneath === undefined ? 1 : 1 + Number.parseInt(beneath, 10) };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** One modal's claim on the keyboard, released when the overlay goes away. */
|
|
69
|
+
export interface ModalMarkerHandle {
|
|
70
|
+
/** Drop this modal from the stack. Idempotent; a second call is inert. */
|
|
71
|
+
release(): void;
|
|
72
|
+
/**
|
|
73
|
+
* Whether this modal currently owns keys. The engine's `setHidden` takes a
|
|
74
|
+
* visible overlay out of the focus order without destroying it, which is how
|
|
75
|
+
* a surface that shells out to a child process gets off the screen, so a
|
|
76
|
+
* hidden modal must stop claiming the keyboard without leaving the stack.
|
|
77
|
+
*/
|
|
78
|
+
setActive(active: boolean): void;
|
|
79
|
+
/** Bring this modal back to the top of the stack, as `handle.focus()` does on screen. */
|
|
80
|
+
raise(): void;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
interface ModalMarkerRegistry {
|
|
84
|
+
/**
|
|
85
|
+
* Push a modal. The sink is taken from the caller rather than installed at
|
|
86
|
+
* startup so the marker has no lifecycle of its own: it exists exactly when
|
|
87
|
+
* an overlay exists, and a stub terminal with no `setTitle` makes the whole
|
|
88
|
+
* mechanism inert instead of throwing.
|
|
89
|
+
*/
|
|
90
|
+
enter(id: string, sink?: ModalMarkerSink | null): ModalMarkerHandle;
|
|
91
|
+
/** The ids that currently own keys, outermost first. */
|
|
92
|
+
stack(): readonly string[];
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
interface ModalMarkerEntry {
|
|
96
|
+
readonly id: string;
|
|
97
|
+
active: boolean;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function createModalMarkerRegistry(): ModalMarkerRegistry {
|
|
101
|
+
const entries: ModalMarkerEntry[] = [];
|
|
102
|
+
let sink: ModalMarkerSink | null = null;
|
|
103
|
+
let published: string | null = null;
|
|
104
|
+
|
|
105
|
+
function activeIds(): string[] {
|
|
106
|
+
return entries.filter((entry) => entry.active).map((entry) => entry.id);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function publish(): void {
|
|
110
|
+
if (sink === null) return;
|
|
111
|
+
const title = formatModalMarkerTitle(activeIds());
|
|
112
|
+
// Before the first marker there is no stale claim to clear, so the pane
|
|
113
|
+
// keeps whatever title the shell or the multiplexer gave it.
|
|
114
|
+
if (published === null && title === MODAL_MARKER_BASE_TITLE) return;
|
|
115
|
+
if (title === published) return;
|
|
116
|
+
published = title;
|
|
117
|
+
sink.setTitle(title);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function enter(id: string, nextSink?: ModalMarkerSink | null): ModalMarkerHandle {
|
|
121
|
+
if (nextSink) sink = nextSink;
|
|
122
|
+
const entry: ModalMarkerEntry = { id, active: true };
|
|
123
|
+
entries.push(entry);
|
|
124
|
+
publish();
|
|
125
|
+
let released = false;
|
|
126
|
+
return {
|
|
127
|
+
release(): void {
|
|
128
|
+
if (released) return;
|
|
129
|
+
released = true;
|
|
130
|
+
const index = entries.indexOf(entry);
|
|
131
|
+
if (index !== -1) entries.splice(index, 1);
|
|
132
|
+
publish();
|
|
133
|
+
},
|
|
134
|
+
setActive(active: boolean): void {
|
|
135
|
+
if (released || entry.active === active) return;
|
|
136
|
+
entry.active = active;
|
|
137
|
+
publish();
|
|
138
|
+
},
|
|
139
|
+
raise(): void {
|
|
140
|
+
if (released) return;
|
|
141
|
+
entry.active = true;
|
|
142
|
+
const index = entries.indexOf(entry);
|
|
143
|
+
if (index !== -1 && index !== entries.length - 1) {
|
|
144
|
+
entries.splice(index, 1);
|
|
145
|
+
entries.push(entry);
|
|
146
|
+
}
|
|
147
|
+
publish();
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return { enter, stack: (): readonly string[] => activeIds() };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
let registry = createModalMarkerRegistry();
|
|
156
|
+
|
|
157
|
+
/** Push a modal onto the process-wide stack. See {@link ModalMarkerRegistry.enter}. */
|
|
158
|
+
export function enterModal(id: string, sink?: ModalMarkerSink | null): ModalMarkerHandle {
|
|
159
|
+
return registry.enter(id, sink);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** The ids that currently own keys, outermost first. */
|
|
163
|
+
export function modalMarkerStack(): readonly string[] {
|
|
164
|
+
return registry.stack();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Drop every claim and forget the sink. Tests use it to isolate; nothing in the app does. */
|
|
168
|
+
export function resetModalMarker(): void {
|
|
169
|
+
registry = createModalMarkerRegistry();
|
|
170
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local inspection of the exact mutation a parked `write` or `edit` would make.
|
|
3
|
+
*
|
|
4
|
+
* The approval card describes an unlisted argument by type and size, so a
|
|
5
|
+
* parked write read `content=<string 482 bytes>` and a parked edit read
|
|
6
|
+
* `edits=<array 3 items>`: the operator authorized bytes they had never seen,
|
|
7
|
+
* and the only way to read them was the external session ledger (issue #254).
|
|
8
|
+
*
|
|
9
|
+
* Two rules shape everything here. The preview is derived from the same
|
|
10
|
+
* arguments object the decision resumes, and it carries a digest of those
|
|
11
|
+
* arguments so the bytes on screen are verifiably the bytes being approved.
|
|
12
|
+
* And the mutation text is process-local: it is built lazily when the operator
|
|
13
|
+
* asks for it, it is handed only to the overlay body, and it is never placed on
|
|
14
|
+
* {@link import("./permission-overlay.js").ApprovalRequestView}, which is what
|
|
15
|
+
* crosses into the transcript, the notices, and the worker seam.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { createHash } from "node:crypto";
|
|
19
|
+
import { readFileSync } from "node:fs";
|
|
20
|
+
import { ToolNames } from "../core/tool-names.js";
|
|
21
|
+
import { sanitizeCallTargetText, sanitizeMultilineDisplayText } from "../domains/safety/call-target.js";
|
|
22
|
+
import {
|
|
23
|
+
applyEditsToNormalizedContent,
|
|
24
|
+
type Edit,
|
|
25
|
+
generateDiffString,
|
|
26
|
+
normalizeToLF,
|
|
27
|
+
stripBom,
|
|
28
|
+
} from "../tools/edit-diff.js";
|
|
29
|
+
import { resolveToCwd } from "../tools/path-utils.js";
|
|
30
|
+
|
|
31
|
+
export { MUTATION_PREVIEW_KEY } from "./permission-hint.js";
|
|
32
|
+
|
|
33
|
+
/** Rows of mutation shown at once. Anything longer scrolls, as `/handoff` review does. */
|
|
34
|
+
export const MUTATION_PREVIEW_VISIBLE_ROWS = 16;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Characters of proposed content the preview renders. The diff builder has its
|
|
38
|
+
* own byte backstop; this bounds the write path, whose payload is a whole file.
|
|
39
|
+
* Reaching it prints an explicit line naming what was withheld.
|
|
40
|
+
*/
|
|
41
|
+
export const MUTATION_PREVIEW_MAX_CHARS = 262_144;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Facts about a parked mutation that may cross every process boundary: what
|
|
45
|
+
* kind it is, how big it is, and the digest that binds a preview to it. No part
|
|
46
|
+
* of the mutation text appears here, which is why this is the shape the
|
|
47
|
+
* approval view, the transcript row, and any notice may carry.
|
|
48
|
+
*/
|
|
49
|
+
export interface MutationFacts {
|
|
50
|
+
kind: "write" | "edit";
|
|
51
|
+
/** Bytes of proposed content (write), or of the replacement text across every edit. */
|
|
52
|
+
bytes: number;
|
|
53
|
+
/** Replacements in the edit list. One for a write. */
|
|
54
|
+
replacements: number;
|
|
55
|
+
/** Truncated SHA-256 over the exact call arguments this decision applies to. */
|
|
56
|
+
digest: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface MutationPreview {
|
|
60
|
+
facts: MutationFacts;
|
|
61
|
+
/** What the body is, named honestly on the overlay's first line. */
|
|
62
|
+
heading: string;
|
|
63
|
+
/** The complete rendered mutation, already sanitized and one entry per display row. */
|
|
64
|
+
body: ReadonlyArray<string>;
|
|
65
|
+
/** A control byte or escape sequence in the payload was neutralized for display. */
|
|
66
|
+
neutralized: boolean;
|
|
67
|
+
/** A tab was expanded, so columns are display columns. */
|
|
68
|
+
tabsExpanded: boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Build the complete preview for the parked call, or explain why it cannot be
|
|
73
|
+
* built. Never throws: an unreadable file or an edit list that will not apply
|
|
74
|
+
* is itself something the operator needs to read before deciding.
|
|
75
|
+
*/
|
|
76
|
+
export type MutationInspector = () => MutationPreview;
|
|
77
|
+
|
|
78
|
+
function canonicalJson(value: unknown): string {
|
|
79
|
+
if (value === null || value === undefined) return "null";
|
|
80
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
81
|
+
if (typeof value === "object") {
|
|
82
|
+
const entries = Object.entries(value as Record<string, unknown>)
|
|
83
|
+
.filter(([, entry]) => entry !== undefined)
|
|
84
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
85
|
+
return `{${entries.map(([key, entry]) => `${JSON.stringify(key)}:${canonicalJson(entry)}`).join(",")}}`;
|
|
86
|
+
}
|
|
87
|
+
if (typeof value === "string") return JSON.stringify(value);
|
|
88
|
+
if (typeof value === "number") return Number.isFinite(value) ? JSON.stringify(value) : "null";
|
|
89
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
90
|
+
return "null";
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The identity a decision is bound to. Key order and absent optional fields do
|
|
95
|
+
* not change it, so the same call digests the same way wherever it is read;
|
|
96
|
+
* anything else about the arguments does, which is the point: a call that
|
|
97
|
+
* differs from the previewed one cannot inherit its preview.
|
|
98
|
+
*/
|
|
99
|
+
export function callArgumentsDigest(args: Record<string, unknown> | undefined): string {
|
|
100
|
+
return createHash("sha256")
|
|
101
|
+
.update(canonicalJson(args ?? {}), "utf8")
|
|
102
|
+
.digest("hex")
|
|
103
|
+
.slice(0, 16);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function readEdits(value: unknown): Edit[] | null {
|
|
107
|
+
if (!Array.isArray(value)) return null;
|
|
108
|
+
const edits: Edit[] = [];
|
|
109
|
+
for (const entry of value) {
|
|
110
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) return null;
|
|
111
|
+
const record = entry as Record<string, unknown>;
|
|
112
|
+
if (typeof record.oldText !== "string" || typeof record.newText !== "string") return null;
|
|
113
|
+
edits.push({ oldText: record.oldText, newText: record.newText });
|
|
114
|
+
}
|
|
115
|
+
return edits;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The size-and-digest facts for a parked call, or null when the tool is not a
|
|
120
|
+
* file mutation or its arguments are not the shape this can describe. The card
|
|
121
|
+
* keeps rendering its allowlisted target either way.
|
|
122
|
+
*/
|
|
123
|
+
export function mutationFacts(tool: string, args: Record<string, unknown> | undefined): MutationFacts | null {
|
|
124
|
+
if (!args || typeof args.path !== "string" || args.path.length === 0) return null;
|
|
125
|
+
if (tool === ToolNames.Write) {
|
|
126
|
+
if (typeof args.content !== "string") return null;
|
|
127
|
+
return {
|
|
128
|
+
kind: "write",
|
|
129
|
+
bytes: Buffer.byteLength(args.content, "utf8"),
|
|
130
|
+
replacements: 1,
|
|
131
|
+
digest: callArgumentsDigest(args),
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
if (tool === ToolNames.Edit) {
|
|
135
|
+
const edits = readEdits(args.edits);
|
|
136
|
+
if (edits === null || edits.length === 0) return null;
|
|
137
|
+
const bytes = edits.reduce((total, edit) => total + Buffer.byteLength(edit.newText, "utf8"), 0);
|
|
138
|
+
return { kind: "edit", bytes, replacements: edits.length, digest: callArgumentsDigest(args) };
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** `write · 482 B · sha256 3f9a1c22ee04b7d1`, the collapsed card's whole mutation line. */
|
|
144
|
+
export function mutationFactsLine(facts: MutationFacts): string {
|
|
145
|
+
const shape =
|
|
146
|
+
facts.kind === "write"
|
|
147
|
+
? `write ${facts.bytes} B`
|
|
148
|
+
: `edit ${facts.replacements} replacement${facts.replacements === 1 ? "" : "s"} · ${facts.bytes} B`;
|
|
149
|
+
return `${shape} · sha256 ${facts.digest}`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function numberedLines(text: string): string[] {
|
|
153
|
+
const raw = text.split("\n");
|
|
154
|
+
const width = String(raw.length).length;
|
|
155
|
+
return raw.map((line, index) => `${String(index + 1).padStart(width, " ")} ${line}`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function boundedContent(content: string): { text: string; withheld: number } {
|
|
159
|
+
if (content.length <= MUTATION_PREVIEW_MAX_CHARS) return { text: content, withheld: 0 };
|
|
160
|
+
return {
|
|
161
|
+
text: content.slice(0, MUTATION_PREVIEW_MAX_CHARS),
|
|
162
|
+
withheld: content.length - MUTATION_PREVIEW_MAX_CHARS,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function previewFrom(
|
|
167
|
+
facts: MutationFacts,
|
|
168
|
+
heading: string,
|
|
169
|
+
text: string,
|
|
170
|
+
options: { numbered?: boolean; trailer?: string } = {},
|
|
171
|
+
): MutationPreview {
|
|
172
|
+
const clean = sanitizeMultilineDisplayText(text);
|
|
173
|
+
const body = options.numbered ? numberedLines(clean.text) : clean.text.split("\n");
|
|
174
|
+
if (options.trailer !== undefined) body.push(options.trailer);
|
|
175
|
+
return {
|
|
176
|
+
facts,
|
|
177
|
+
heading,
|
|
178
|
+
body,
|
|
179
|
+
neutralized: clean.neutralized,
|
|
180
|
+
tabsExpanded: clean.tabsExpanded,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function refusal(facts: MutationFacts, heading: string, reason: string): MutationPreview {
|
|
185
|
+
return { facts, heading, body: [reason], neutralized: false, tabsExpanded: false };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* The requested replacements, rendered as a diff would render them, for the
|
|
190
|
+
* case where the effective diff cannot be computed. The operator still needs to
|
|
191
|
+
* read what the model asked for; the heading says this is the request rather
|
|
192
|
+
* than the effect.
|
|
193
|
+
*/
|
|
194
|
+
function replacementListing(edits: ReadonlyArray<Edit>): string {
|
|
195
|
+
const blocks: string[] = [];
|
|
196
|
+
for (const [index, edit] of edits.entries()) {
|
|
197
|
+
blocks.push(`@@ replacement ${index + 1} of ${edits.length}`);
|
|
198
|
+
for (const line of normalizeToLF(edit.oldText).split("\n")) blocks.push(`- ${line}`);
|
|
199
|
+
for (const line of normalizeToLF(edit.newText).split("\n")) blocks.push(`+ ${line}`);
|
|
200
|
+
}
|
|
201
|
+
return blocks.join("\n");
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function editPreview(facts: MutationFacts, path: string, edits: ReadonlyArray<Edit>, read: (path: string) => string) {
|
|
205
|
+
let current: string;
|
|
206
|
+
try {
|
|
207
|
+
current = read(resolveToCwd(path));
|
|
208
|
+
} catch (error) {
|
|
209
|
+
const message = sanitizeCallTargetText(error instanceof Error ? error.message : String(error));
|
|
210
|
+
return previewFrom(
|
|
211
|
+
facts,
|
|
212
|
+
`Requested replacements for ${sanitizeCallTargetText(path)} (the file could not be read: ${message})`,
|
|
213
|
+
replacementListing(edits),
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
const { text } = stripBom(current);
|
|
218
|
+
const applied = applyEditsToNormalizedContent(normalizeToLF(text), edits, path);
|
|
219
|
+
const diff = generateDiffString(applied.baseContent, applied.newContent);
|
|
220
|
+
return previewFrom(facts, `Effective diff for ${sanitizeCallTargetText(path)}`, diff.diff);
|
|
221
|
+
} catch (error) {
|
|
222
|
+
const message = sanitizeCallTargetText(error instanceof Error ? error.message : String(error));
|
|
223
|
+
return previewFrom(
|
|
224
|
+
facts,
|
|
225
|
+
`Requested replacements for ${sanitizeCallTargetText(path)} (they do not apply: ${message})`,
|
|
226
|
+
replacementListing(edits),
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export interface MutationInspectorOptions {
|
|
232
|
+
/** Reads the current file bytes for an edit's effective diff. Injected so the contract test owns the filesystem. */
|
|
233
|
+
readFile?: (path: string) => string;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Bind an inspector to one parked call. The returned closure reads the file and
|
|
238
|
+
* computes the diff only when the operator presses the inspect key, and it
|
|
239
|
+
* re-derives the digest from the same arguments it renders: a mismatch means
|
|
240
|
+
* the arguments changed under the decision and the preview refuses rather than
|
|
241
|
+
* showing bytes that are no longer the ones being approved.
|
|
242
|
+
*/
|
|
243
|
+
export function createMutationInspector(
|
|
244
|
+
tool: string,
|
|
245
|
+
args: Record<string, unknown> | undefined,
|
|
246
|
+
facts: MutationFacts,
|
|
247
|
+
options: MutationInspectorOptions = {},
|
|
248
|
+
): MutationInspector {
|
|
249
|
+
const read = options.readFile ?? ((path: string) => readFileSync(path, "utf8"));
|
|
250
|
+
return () => {
|
|
251
|
+
const current = mutationFacts(tool, args);
|
|
252
|
+
if (current === null || current.digest !== facts.digest) {
|
|
253
|
+
return refusal(
|
|
254
|
+
facts,
|
|
255
|
+
"Preview unavailable",
|
|
256
|
+
`These arguments no longer digest to sha256 ${facts.digest}. Deny this call rather than approving bytes that cannot be shown.`,
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
const path = typeof args?.path === "string" ? args.path : "";
|
|
260
|
+
if (facts.kind === "write") {
|
|
261
|
+
const content = typeof args?.content === "string" ? args.content : "";
|
|
262
|
+
const bounded = boundedContent(content);
|
|
263
|
+
return previewFrom(facts, `Proposed content for ${sanitizeCallTargetText(path)}`, bounded.text, {
|
|
264
|
+
numbered: true,
|
|
265
|
+
...(bounded.withheld > 0
|
|
266
|
+
? { trailer: `… ${bounded.withheld} more characters not shown (preview capped at ${MUTATION_PREVIEW_MAX_CHARS})` }
|
|
267
|
+
: {}),
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
const edits = readEdits(args?.edits) ?? [];
|
|
271
|
+
return editPreview(facts, path, edits, read);
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* The scrolled window of a preview, plus the position line that makes the cut
|
|
277
|
+
* visible. Mirrors the `/handoff` review overlay so both scrollable inspection
|
|
278
|
+
* surfaces read the same way.
|
|
279
|
+
*/
|
|
280
|
+
export function mutationPreviewWindow(
|
|
281
|
+
lines: ReadonlyArray<string>,
|
|
282
|
+
scroll: number,
|
|
283
|
+
rows = MUTATION_PREVIEW_VISIBLE_ROWS,
|
|
284
|
+
): { window: ReadonlyArray<string>; position: string | null; maxScroll: number } {
|
|
285
|
+
const visible = Math.max(1, rows);
|
|
286
|
+
const maxScroll = Math.max(0, lines.length - visible);
|
|
287
|
+
const start = Math.max(0, Math.min(scroll, maxScroll));
|
|
288
|
+
const window = lines.slice(start, start + visible);
|
|
289
|
+
if (lines.length <= visible) return { window, position: null, maxScroll };
|
|
290
|
+
return {
|
|
291
|
+
window,
|
|
292
|
+
position: `(${start + 1}-${start + window.length} of ${lines.length} lines)`,
|
|
293
|
+
maxScroll,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The dispatch-to-pane-host bridge.
|
|
3
|
+
*
|
|
4
|
+
* Two responsibilities, both projections of state the session already owns:
|
|
5
|
+
*
|
|
6
|
+
* - **Terminal-run notifications.** A finished dispatch raises a pane-host
|
|
7
|
+
* toast per the `panes.notifications` policy, and a failure additionally
|
|
8
|
+
* leaves a persistent transcript notice, which unlike a toast remains
|
|
9
|
+
* auditable in the TUI and does not depend on the pane host being alive.
|
|
10
|
+
*
|
|
11
|
+
* - **Self-report (SA-3).** Clio's own hosting pane, in guest mode, carries
|
|
12
|
+
* the interactive status machine's phase as a semantic agent state, on a
|
|
13
|
+
* trailing-edge throttle: herdr answers one request per connection, and an
|
|
14
|
+
* operator turn passes through several phases inside one window, of which
|
|
15
|
+
* only the last is worth a wire call.
|
|
16
|
+
*
|
|
17
|
+
* What this bridge deliberately no longer does is open panes. The phase 3/4
|
|
18
|
+
* integration projected every dispatch onto a viewer pane in a hidden Fleet
|
|
19
|
+
* tab (an `agents` policy knob, a keep-failed close policy, resume adoption,
|
|
20
|
+
* role-vocabulary sidebar labels). All of it duplicated the native fleet
|
|
21
|
+
* surfaces into panes the operator had not asked for; the one pane that
|
|
22
|
+
* renders runs is now the operator-pulled watch pane (`watch-pane.ts`), and
|
|
23
|
+
* the policy line lives in `pane-policy.ts`.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { type AgentStatusChangedPayload, BusChannels, type DispatchRunIdentity } from "../core/bus-events.js";
|
|
27
|
+
import type { SafeEventBus } from "../core/event-bus.js";
|
|
28
|
+
import type { MuxContract, MuxLog } from "../domains/mux/index.js";
|
|
29
|
+
import { sanitizeCallTargetText } from "../domains/safety/call-target.js";
|
|
30
|
+
|
|
31
|
+
/** Trailing-edge window for the self-report; matches `WAIT_POLL_MS`. */
|
|
32
|
+
export const MUX_BRIDGE_THROTTLE_MS = 250;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A success worth a sound. Below it a finished run is one of many and a chime
|
|
36
|
+
* per run is noise; above it the operator has moved on and wants telling.
|
|
37
|
+
*/
|
|
38
|
+
const LONG_RUN_MS = 60_000;
|
|
39
|
+
|
|
40
|
+
/** How much terminal detail may reach a toast body or a notice line. */
|
|
41
|
+
const DETAIL_MAX_CHARS = 96;
|
|
42
|
+
|
|
43
|
+
export type PanesNotificationsPolicy = "failures" | "all" | "off";
|
|
44
|
+
|
|
45
|
+
/** Terminal outcomes that read as failure for the notification policy. */
|
|
46
|
+
type BridgeRunOutcome = "succeeded" | "failed" | "canceled" | "timed_out";
|
|
47
|
+
|
|
48
|
+
export interface MuxBridgeDeps {
|
|
49
|
+
bus: SafeEventBus;
|
|
50
|
+
mux: MuxContract;
|
|
51
|
+
/** Read live so a `/settings` change takes effect on the next terminal run. */
|
|
52
|
+
notificationsPolicy: () => PanesNotificationsPolicy;
|
|
53
|
+
/** Persistent transcript notice; unlike a herdr toast, this remains auditable in the TUI. */
|
|
54
|
+
notice?: (level: "error", text: string) => void;
|
|
55
|
+
log?: MuxLog;
|
|
56
|
+
now?: () => number;
|
|
57
|
+
throttleMs?: number;
|
|
58
|
+
setTimeoutFn?: typeof setTimeout;
|
|
59
|
+
clearTimeoutFn?: typeof clearTimeout;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface MuxBridge {
|
|
63
|
+
/** Settle every in-flight notification and self-report. Tests await it. */
|
|
64
|
+
flush(): Promise<void>;
|
|
65
|
+
dispose(): void;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function isFailure(outcome: BridgeRunOutcome): boolean {
|
|
69
|
+
return outcome === "failed" || outcome === "timed_out";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Clio's own pane state (SA-3), mapped from the interactive status machine. */
|
|
73
|
+
function selfStateFor(phase: AgentStatusChangedPayload["phase"]): "working" | "blocked" | "idle" {
|
|
74
|
+
if (phase === "tool_blocked") return "blocked";
|
|
75
|
+
// `ended` is the settle step on the way back to idle, so both read idle.
|
|
76
|
+
return phase === "idle" || phase === "ended" ? "idle" : "working";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Clio's seven-member run outcome taxonomy narrowed to the four states the
|
|
81
|
+
* notification policy distinguishes. `stalled`, `denied_by_policy`,
|
|
82
|
+
* `spawn_failed`, and the synthetic `retry_denied` all read as failure, which
|
|
83
|
+
* is the safe direction: an unrecognized terminal state must not silently
|
|
84
|
+
* skip the failure notice.
|
|
85
|
+
*/
|
|
86
|
+
function terminalOutcome(outcome: string): BridgeRunOutcome {
|
|
87
|
+
if (outcome === "succeeded") return "succeeded";
|
|
88
|
+
if (outcome === "canceled") return "canceled";
|
|
89
|
+
if (outcome === "timed_out") return "timed_out";
|
|
90
|
+
return "failed";
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function createMuxBridge(deps: MuxBridgeDeps): MuxBridge {
|
|
94
|
+
const log = deps.log ?? ((): void => undefined);
|
|
95
|
+
const now = deps.now ?? Date.now;
|
|
96
|
+
const throttleMs = deps.throttleMs ?? MUX_BRIDGE_THROTTLE_MS;
|
|
97
|
+
const setTimeoutFn = deps.setTimeoutFn ?? setTimeout;
|
|
98
|
+
const clearTimeoutFn = deps.clearTimeoutFn ?? clearTimeout;
|
|
99
|
+
|
|
100
|
+
/** Start stamps, so a terminal event without a duration still gets one. */
|
|
101
|
+
const startedAt = new Map<string, number>();
|
|
102
|
+
let disposed = false;
|
|
103
|
+
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
104
|
+
/** Serializes wire work: notifies and self-reports must not interleave. */
|
|
105
|
+
let draining: Promise<void> = Promise.resolve();
|
|
106
|
+
/** Pending self-report state, or null when nothing changed since the last flush. */
|
|
107
|
+
let pendingSelf: "working" | "blocked" | "idle" | null = null;
|
|
108
|
+
/** The last state actually reported, so a repeated phase costs no wire call. */
|
|
109
|
+
let reportedSelf: "working" | "blocked" | "idle" | null = null;
|
|
110
|
+
|
|
111
|
+
const schedule = (): void => {
|
|
112
|
+
if (disposed || timer !== null) return;
|
|
113
|
+
timer = setTimeoutFn(() => {
|
|
114
|
+
timer = null;
|
|
115
|
+
void drainSelf();
|
|
116
|
+
}, throttleMs);
|
|
117
|
+
timer.unref?.();
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const drainSelf = (): Promise<void> => {
|
|
121
|
+
draining = draining.then(async () => {
|
|
122
|
+
if (disposed) return;
|
|
123
|
+
const self = pendingSelf;
|
|
124
|
+
pendingSelf = null;
|
|
125
|
+
if (self !== null && self !== reportedSelf) {
|
|
126
|
+
reportedSelf = self;
|
|
127
|
+
await deps.mux.reportSelf({ state: self }).catch(() => false);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
return draining;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const boundedDetail = (value: string | null | undefined): string | null => {
|
|
134
|
+
if (typeof value !== "string" || value.length === 0) return null;
|
|
135
|
+
return sanitizeCallTargetText(value).slice(0, DETAIL_MAX_CHARS);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const onTerminal = (
|
|
139
|
+
identity: DispatchRunIdentity,
|
|
140
|
+
rawOutcome: string,
|
|
141
|
+
fields: { durationMs?: number | undefined; detail?: string | null | undefined },
|
|
142
|
+
): void => {
|
|
143
|
+
const outcome = terminalOutcome(rawOutcome);
|
|
144
|
+
const begun = startedAt.get(identity.runId);
|
|
145
|
+
startedAt.delete(identity.runId);
|
|
146
|
+
const policy = deps.notificationsPolicy();
|
|
147
|
+
const failed = isFailure(outcome);
|
|
148
|
+
const qualifies = policy === "all" || (policy === "failures" && failed);
|
|
149
|
+
if (!qualifies) return;
|
|
150
|
+
const detail = boundedDetail(fields.detail);
|
|
151
|
+
const durationMs =
|
|
152
|
+
typeof fields.durationMs === "number" ? fields.durationMs : begun === undefined ? null : now() - begun;
|
|
153
|
+
if (failed) {
|
|
154
|
+
deps.notice?.("error", `${identity.agentId} ${outcome} (${identity.runId})${detail ? `: ${detail}` : ""}`);
|
|
155
|
+
}
|
|
156
|
+
if (!deps.mux.available()) return;
|
|
157
|
+
draining = draining.then(async () => {
|
|
158
|
+
if (disposed) return;
|
|
159
|
+
try {
|
|
160
|
+
await deps.mux.notify({
|
|
161
|
+
title: `${identity.agentId} ${outcome}`,
|
|
162
|
+
...(detail ? { body: detail } : {}),
|
|
163
|
+
// A failure interrupts; a long success announces. A short success
|
|
164
|
+
// in `all` mode gets the toast without the chime.
|
|
165
|
+
sound: failed ? "request" : durationMs !== null && durationMs >= LONG_RUN_MS ? "done" : "none",
|
|
166
|
+
});
|
|
167
|
+
} catch (error) {
|
|
168
|
+
// The contract swallows its own failures; anything arriving here is a
|
|
169
|
+
// bug rather than a dead socket, and must still not kill the chain.
|
|
170
|
+
log("debug", `mux bridge notify failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const unsubscribers = [
|
|
176
|
+
deps.bus.on(BusChannels.DispatchStarted, (payload) => {
|
|
177
|
+
startedAt.set(payload.runId, now());
|
|
178
|
+
}),
|
|
179
|
+
deps.bus.on(BusChannels.DispatchCompleted, (payload) => {
|
|
180
|
+
onTerminal(payload, payload.outcome, { durationMs: payload.durationMs, detail: payload.outcomeDetail });
|
|
181
|
+
}),
|
|
182
|
+
deps.bus.on(BusChannels.DispatchFailed, (payload) => {
|
|
183
|
+
onTerminal(payload, payload.reason, { durationMs: payload.durationMs, detail: payload.outcomeDetail });
|
|
184
|
+
}),
|
|
185
|
+
// SA-3. The interactive status machine is the one surface that already
|
|
186
|
+
// knows when a turn starts, when it ends, and when a tool call is parked
|
|
187
|
+
// on an approval, so the self-report rides its transitions rather than
|
|
188
|
+
// growing a second turn-lifecycle listener.
|
|
189
|
+
deps.bus.on(BusChannels.AgentStatusChanged, (payload) => {
|
|
190
|
+
const state = selfStateFor(payload.phase);
|
|
191
|
+
if (state === reportedSelf && pendingSelf === null) return;
|
|
192
|
+
pendingSelf = state;
|
|
193
|
+
schedule();
|
|
194
|
+
}),
|
|
195
|
+
];
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
flush(): Promise<void> {
|
|
199
|
+
if (timer !== null) {
|
|
200
|
+
clearTimeoutFn(timer);
|
|
201
|
+
timer = null;
|
|
202
|
+
}
|
|
203
|
+
return drainSelf();
|
|
204
|
+
},
|
|
205
|
+
dispose(): void {
|
|
206
|
+
if (disposed) return;
|
|
207
|
+
disposed = true;
|
|
208
|
+
if (timer !== null) clearTimeoutFn(timer);
|
|
209
|
+
timer = null;
|
|
210
|
+
for (const unsubscribe of unsubscribers) unsubscribe();
|
|
211
|
+
startedAt.clear();
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
}
|