@iowarp/clio-coder 0.4.1 → 0.4.3
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 +127 -0
- package/CONTRIBUTING.md +142 -52
- package/README.md +434 -473
- package/SECURITY.md +2 -1
- package/dist/{acp-ZILU3AUO.js → acp-H2NGRPWO.js} +12 -12
- package/dist/{agents-HYWGBGQR.js → agents-TL5LLUQP.js} +56 -55
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-N3QT7CBO.js → auth-E5SW4HMS.js} +23 -21
- package/dist/builtins-IA7V7FUC.js +22 -0
- package/dist/{chunk-7RY5VZPH.js → chunk-2APPQIER.js} +8 -8
- package/dist/{chunk-72GZI5EV.js → chunk-2JH2WHGE.js} +2 -2
- package/dist/{chunk-JA5QWE4Z.js → chunk-2UG5F4C5.js} +1973 -1664
- package/dist/{chunk-5YHDIDBP.js → chunk-2UH2KFUP.js} +2 -2
- package/dist/{chunk-CTJ4RNAA.js → chunk-2VIKGWFZ.js} +2 -2
- package/dist/{chunk-I66EAJFY.js → chunk-2WZ546HR.js} +267 -232
- package/dist/{chunk-GIZNH63R.js → chunk-35MSIRKH.js} +9 -4
- package/dist/chunk-3EBYEESD.js +314 -0
- package/dist/{chunk-J5LZHVIT.js → chunk-3M6DQK6S.js} +113 -35
- package/dist/{chunk-RKSR6VSF.js → chunk-4IUZQIJ3.js} +29 -1
- package/dist/{chunk-6FN3E6KX.js → chunk-4O6MANBS.js} +2 -2
- package/dist/chunk-4UVU7BJ5.js +39 -0
- package/dist/{chunk-VKRH2TCS.js → chunk-4WR7VSYB.js} +2 -2
- package/dist/{chunk-BBTJOK6Y.js → chunk-54CBCGIR.js} +5 -5
- package/dist/{chunk-AP73CFDC.js → chunk-5ICU3EUH.js} +2 -2
- package/dist/chunk-5MEZN6CB.js +1334 -0
- package/dist/{chunk-O42A54GG.js → chunk-5OIVVPHF.js} +2 -2
- package/dist/{chunk-ABLSQ6JX.js → chunk-64I3JVYM.js} +8 -2
- package/dist/{chunk-AFKWHWXF.js → chunk-6PTFB5VS.js} +39 -22
- package/dist/{chunk-VN3SHNBN.js → chunk-7DICMOS6.js} +2 -2
- package/dist/chunk-7DRAWPTZ.js +360 -0
- package/dist/chunk-7E7I3WLS.js +3762 -0
- package/dist/{chunk-BJGUKIG4.js → chunk-7ZYNNDKC.js} +7 -7
- package/dist/{chunk-XKA2ICR3.js → chunk-AF4YM7Z4.js} +652 -252
- package/dist/{chunk-GVQJ5CCZ.js → chunk-AX2THNSA.js} +12 -12
- package/dist/{chunk-IG7BCQBA.js → chunk-B4OAX3SI.js} +65 -3
- package/dist/{chunk-TD3PGPQA.js → chunk-B4VEBZKF.js} +3 -3
- package/dist/{chunk-74YWRRU5.js → chunk-BEPZRGGU.js} +10 -10
- package/dist/{chunk-FEFIFZTL.js → chunk-CE5AX47J.js} +2 -2
- package/dist/{chunk-UAPGZHYC.js → chunk-DWUOQKRU.js} +25 -11
- package/dist/{chunk-THYWACCR.js → chunk-E3TPLWFX.js} +3 -3
- package/dist/{chunk-7EPLI7VL.js → chunk-EKCHAPYA.js} +2 -2
- package/dist/{chunk-HLW2MRKE.js → chunk-F4EKGO4N.js} +3 -1
- package/dist/{chunk-PJJ6MY27.js → chunk-F5JHEYZM.js} +7 -7
- package/dist/{chunk-6CCS4G3W.js → chunk-FTMGRKEF.js} +3 -3
- package/dist/{chunk-SINK3QR6.js → chunk-G76U63X4.js} +17 -17
- package/dist/{chunk-EIMVLWB3.js → chunk-GHS5EBTQ.js} +64 -9
- package/dist/{chunk-QMXC4JB7.js → chunk-GI7YYQ3F.js} +187 -1419
- package/dist/{chunk-TZSKNMZG.js → chunk-GTUD2WMY.js} +2 -1
- package/dist/{chunk-6HMJX2VU.js → chunk-GWZNEVM2.js} +44 -12
- package/dist/chunk-GYV6VZOC.js +26 -0
- package/dist/{chunk-MQXIVJ35.js → chunk-HAXOFFRH.js} +5 -5
- package/dist/{chunk-UXN6JT4W.js → chunk-HEQY7ZFI.js} +3 -3
- package/dist/{chunk-7PWAODYW.js → chunk-I7XBWTYH.js} +2 -2
- package/dist/{chunk-GCSMB2KY.js → chunk-I7ZPNEJM.js} +145 -102
- package/dist/{chunk-WNP7O5WZ.js → chunk-ID64D7PE.js} +4 -4
- package/dist/{chunk-QTFGO774.js → chunk-IGLP3ODT.js} +29 -16
- package/dist/chunk-IJNZMHLA.js +101 -0
- package/dist/{chunk-BDPT6GTK.js → chunk-INY6HTFL.js} +7 -7
- package/dist/{chunk-PBP4B7XR.js → chunk-IUE3Y34X.js} +2 -2
- package/dist/{chunk-6NJQITNH.js → chunk-IWT4SF4R.js} +6 -3
- package/dist/{chunk-R23Z6K6I.js → chunk-JDAY6FIL.js} +19 -19
- package/dist/chunk-JEQ3XTHC.js +42 -0
- package/dist/{chunk-FSP7CMNU.js → chunk-JGRC33J2.js} +50 -4
- package/dist/{chunk-TVH4ONAM.js → chunk-JKKCYP3C.js} +10 -10
- package/dist/{chunk-HJWWJ6IL.js → chunk-JSC3U7TI.js} +16 -4
- package/dist/{chunk-C537JADH.js → chunk-KK4JZPBQ.js} +19 -141
- package/dist/{chunk-K6BF4U2H.js → chunk-KKOJXO6R.js} +62 -14
- package/dist/{chunk-IHXBNWMM.js → chunk-KXDSS5WJ.js} +7 -3
- package/dist/{chunk-6DWBAZ5U.js → chunk-L47TF46W.js} +5 -7
- package/dist/{chunk-HUAS7ITX.js → chunk-LDJG7DW3.js} +91 -42
- package/dist/{chunk-CDNVLKUX.js → chunk-LLDJM5XK.js} +13 -7
- package/dist/{chunk-YPI3QQCF.js → chunk-MCEPRMZW.js} +2 -4
- package/dist/{chunk-Y4CAGMM6.js → chunk-MNJGS2IN.js} +5 -6
- package/dist/{chunk-VKFQTNDV.js → chunk-MUW2BDDH.js} +4 -4
- package/dist/{chunk-E67WX76H.js → chunk-MWUZBSAQ.js} +104 -152
- package/dist/{chunk-OJTRZGR3.js → chunk-N2Z7HLVY.js} +21 -21
- package/dist/{chunk-TVHHYFHE.js → chunk-NEDJ26B5.js} +2 -2
- package/dist/{chunk-FYUN5KZ3.js → chunk-NIQJ66N4.js} +21 -21
- package/dist/{chunk-U2WB7TZS.js → chunk-NMJXSHBJ.js} +97 -85
- package/dist/{chunk-CWVRRIEI.js → chunk-NZMNUPZZ.js} +2 -2
- package/dist/{chunk-VEGN6WIQ.js → chunk-O5CVSAG5.js} +3 -3
- package/dist/{chunk-MOPSG2X7.js → chunk-OML5D5V5.js} +8 -8
- package/dist/{chunk-2VG7KLYV.js → chunk-PAJQJ7BS.js} +5816 -3255
- package/dist/{chunk-ZW55JB7N.js → chunk-PUVDKJ2Y.js} +2 -2
- package/dist/{chunk-BTGG6BG2.js → chunk-QWGDJJYJ.js} +158 -19
- package/dist/chunk-R6Q67RJH.js +134 -0
- package/dist/{chunk-ZJLUDYFY.js → chunk-RRNP2ANY.js} +6 -6
- package/dist/{chunk-PVAMAVBB.js → chunk-RSJ25QSL.js} +102 -2
- package/dist/{chunk-NLFAQR7Z.js → chunk-S66XZJOF.js} +3 -23
- package/dist/chunk-SKHCAU7K.js +385 -0
- package/dist/chunk-SZAA6XDG.js +30 -0
- package/dist/{chunk-J4HBWF6Y.js → chunk-TM6LQDI3.js} +131 -28
- package/dist/chunk-UOIZ7DA4.js +41 -0
- package/dist/{chunk-MA3H6DM5.js → chunk-UPZU6GE4.js} +25 -3
- package/dist/{chunk-BWW4HLO4.js → chunk-UXCU4E3T.js} +8 -6
- package/dist/{chunk-N5UK64DP.js → chunk-V2ANDPVT.js} +4 -4
- package/dist/{chunk-AK5XEFVZ.js → chunk-VA5FNYMT.js} +26 -13
- package/dist/{chunk-6VC4OV3Z.js → chunk-VIA6RFQZ.js} +3 -11
- package/dist/{chunk-ZAZB4JMW.js → chunk-VKPAQYEB.js} +27 -8
- package/dist/{chunk-QKIFBZKT.js → chunk-VW6DOEDG.js} +497 -81
- package/dist/{chunk-SCYB3HA4.js → chunk-W6RRQCPQ.js} +63 -19
- package/dist/{chunk-2NM363SV.js → chunk-WBKFA554.js} +10 -10
- package/dist/{chunk-R32CLGZ6.js → chunk-WCXUNS7U.js} +82 -21
- package/dist/{chunk-GPPB3JBE.js → chunk-WRBAGUNF.js} +3 -3
- package/dist/{chunk-IXJT6DCX.js → chunk-XIVNBFZS.js} +85 -30
- package/dist/{chunk-UEDMSP56.js → chunk-XPWWI35G.js} +417 -201
- package/dist/chunk-XRZT5WY5.js +47 -0
- package/dist/{chunk-3QSOM6PA.js → chunk-Y3CBHOR6.js} +2 -2
- package/dist/{chunk-VXMFAE2W.js → chunk-YPC6ZR5L.js} +19 -6
- package/dist/{chunk-AKB4GYDL.js → chunk-YQWYVTMC.js} +5 -5
- package/dist/{chunk-6I5ILFOF.js → chunk-ZA4VCIGV.js} +3 -3
- package/dist/{chunk-7OBGU7UB.js → chunk-ZDN3Y73Y.js} +12 -18
- package/dist/{chunk-3I5NY75V.js → chunk-ZWPRK62N.js} +8 -5
- package/dist/cli/index.js +41 -39
- package/dist/{clio-IT3G3VQH.js → clio-CMMK4KRR.js} +9 -9
- package/dist/{code-nav-RK6S7F6E.js → code-nav-MDZNQS33.js} +89 -21
- package/dist/{components-UBWCQSRW.js → components-UCUQ4QXW.js} +4 -4
- package/dist/{config-3QZRWZJF.js → config-SVM5P5YI.js} +131 -84
- package/dist/{configure-FL7Y3KJF.js → configure-LE3IK2TJ.js} +28 -26
- package/dist/{context-5HE7ODYK.js → context-2OHRKS42.js} +69 -64
- package/dist/{context-KYQFRVDC.js → context-E3VC7RX5.js} +15 -11
- package/dist/{context-XNHL75JV.js → context-VNCR7KAG.js} +93 -65
- package/dist/{context-clear-N545L53A.js → context-clear-BW4O37TG.js} +64 -60
- package/dist/context-map-COB37XXN.js +505 -0
- package/dist/{context-working-set-QHKXSV2F.js → context-working-set-VDS25HXZ.js} +19 -18
- package/dist/{dispatch-runner-RGIE5PCT.js → dispatch-runner-5AHT53RF.js} +93 -82
- package/dist/{docs-5NAF6AU7.js → docs-PD3EXDKU.js} +21 -20
- package/dist/{doctor-ZGPEGHIP.js → doctor-WNNVO6FY.js} +48 -47
- package/dist/{eval-GXLL44RD.js → eval-7G7SGAYO.js} +287 -115
- package/dist/{eval-inventory-HBWSWQOK.js → eval-inventory-Y6QRFOH5.js} +4 -4
- package/dist/{evidence-HWLBRH3Q.js → evidence-VD6736FQ.js} +67 -64
- package/dist/{evolve-FTZBMNVW.js → evolve-AL3NGVRL.js} +65 -62
- package/dist/{extensions-VHRBEID7.js → extensions-MOVJ32NM.js} +9 -7
- package/dist/{fleet-CKZHJWZJ.js → fleet-QZHUMAGI.js} +114 -111
- package/dist/{fleet-commands-EXDXBMV6.js → fleet-commands-BAYT5FJZ.js} +10 -10
- package/dist/{fleet-decisions-OTHB6KRL.js → fleet-decisions-IREVMRU4.js} +7 -6
- package/dist/{fleet-graph-YTEZUCUT.js → fleet-graph-YCTT3HTI.js} +22 -19
- package/dist/{fleet-inspect-SS6YMDCK.js → fleet-inspect-QVJTDAVB.js} +58 -55
- package/dist/{fleet-preflight-PBY4VYOM.js → fleet-preflight-25QAFPK4.js} +4 -4
- package/dist/{fleet-validate-KMEM5L3S.js → fleet-validate-5O57AAJ7.js} +26 -23
- package/dist/{fleet-verify-QD5M7E7Q.js → fleet-verify-CPH2W2T6.js} +59 -56
- package/dist/{fleet-view-WAMJYNDT.js → fleet-view-SWBR3VGQ.js} +58 -55
- package/dist/{init-5XQRBOFV.js → init-J477LKZH.js} +82 -79
- package/dist/{interop-34TVO25M.js → interop-3FCM6XLG.js} +11 -11
- package/dist/{library-3QY6KF57.js → library-QUQEIUG6.js} +30 -27
- package/dist/{memory-L4UTIIIW.js → memory-SGGSEP65.js} +67 -64
- package/dist/{models-ZVX3QOWE.js → models-HEKUAXXK.js} +53 -46
- package/dist/{monitor-CEKVSYTS.js → monitor-HKU57TYQ.js} +63 -60
- package/dist/{orchestrator-77BAP6BC.js → orchestrator-VDFAEFAI.js} +1831 -1057
- package/dist/{panes-7STHOAUJ.js → panes-DN2SSFOH.js} +5 -5
- package/dist/{panes-SHAUIRXY.js → panes-TALGNPZT.js} +29 -14
- package/dist/{paths-L7LGY6RN.js → paths-NBMFAIEZ.js} +5 -5
- package/dist/reset-EAJFFJVB.js +344 -0
- package/dist/{resources-74GKTLSF.js → resources-OVKSEFVE.js} +29 -20
- package/dist/{run-HBAUJNNZ.js → run-7DP7ZF2J.js} +120 -115
- package/dist/{share-G3APVLVP.js → share-WML67FT3.js} +32 -27
- package/dist/{skills-35HHUKCR.js → skills-SG662R2K.js} +41 -31
- package/dist/{skills-eval-QN4HSHDC.js → skills-eval-VVZEUU46.js} +78 -77
- package/dist/{skills-inventory-J357J34F.js → skills-inventory-I2E23GET.js} +23 -20
- package/dist/{slash-commands-JZZCQA32.js → slash-commands-S7MBJDQK.js} +40 -36
- package/dist/{steer-XAVHJM22.js → steer-2LQOMCPB.js} +3 -3
- package/dist/{support-U7QOWY26.js → support-CC2UJBJ6.js} +6 -6
- package/dist/{targets-DSM6CY3M.js → targets-4QC3HIEW.js} +54 -54
- package/dist/{terminal-lease-JOPFUVEM.js → terminal-lease-TUHIJ6Y2.js} +5 -5
- package/dist/{tools-MKNWVPBH.js → tools-TFGJICCU.js} +10 -10
- package/dist/{trace-ECQ7TIYZ.js → trace-FXMXUZUF.js} +55 -7
- package/dist/uninstall-5PEVOE5B.js +408 -0
- package/dist/upgrade-M4WXY6KN.js +303 -0
- package/dist/{usage-X52N3IDJ.js → usage-N7ZNVLEM.js} +151 -104
- package/dist/{verifiers-EJTVVSMA.js → verifiers-DJTP4XX6.js} +15 -15
- package/dist/{verify-YJL6XET2.js → verify-RWE4PPEK.js} +9 -9
- package/dist/{web-fetch-MPIFL3LL.js → web-fetch-MPARV2K7.js} +2 -2
- package/dist/{wiki-generate-4NDZTQ4B.js → wiki-generate-C7IQOXSP.js} +89 -86
- package/dist/{with-panes-OBOBFIIR.js → with-panes-4GCGSL7J.js} +53 -257
- package/dist/worker/entry.js +90 -74
- package/docs/README.md +176 -81
- package/docs/{acp.md → architecture/acp.md} +36 -20
- package/docs/{alcf-provider.md → architecture/alcf-provider.md} +8 -5
- package/docs/{architecture.md → architecture/architecture.md} +43 -22
- package/docs/{artifact-placement.md → architecture/artifact-placement.md} +27 -23
- package/docs/architecture/artifact-versions.md +90 -0
- package/docs/{capacity-and-scheduling.md → architecture/capacity-and-scheduling.md} +26 -13
- package/docs/{context-engine.md → architecture/context-engine.md} +29 -25
- package/docs/{context-working-set.md → architecture/context-working-set.md} +13 -10
- package/docs/{dispatch-architecture-rationale.md → architecture/dispatch-architecture-rationale.md} +12 -9
- package/docs/{dispatch-typed-intent.md → architecture/dispatch-typed-intent.md} +68 -46
- package/docs/{evidence-and-memory.md → architecture/evidence-and-memory.md} +23 -16
- package/docs/{middleware-and-components.md → architecture/middleware-and-components.md} +11 -5
- package/docs/{model-catalog.md → architecture/model-catalog.md} +61 -27
- package/docs/{observability.md → architecture/observability.md} +38 -14
- package/docs/{pi-boundary.md → architecture/pi-boundary.md} +24 -11
- package/docs/{prompt-envelope-and-tools.md → architecture/prompt-envelope-and-tools.md} +57 -20
- package/docs/{provider-adapter-cookbook.md → architecture/provider-adapter-cookbook.md} +99 -25
- package/docs/{safety-model.md → architecture/safety-model.md} +35 -20
- package/docs/{session-lifecycle.md → architecture/session-lifecycle.md} +8 -5
- package/docs/architecture/time-conventions.md +125 -0
- package/docs/{trace-store.md → architecture/trace-store.md} +13 -5
- package/docs/{tui-design.md → architecture/tui-design.md} +13 -13
- package/docs/{worker-dispatch-mechanics.md → architecture/worker-dispatch-mechanics.md} +27 -30
- package/docs/{built-in-agents.md → guide/built-in-agents.md} +65 -35
- package/docs/{commands-and-modes.md → guide/commands-and-modes.md} +66 -61
- package/docs/{configuration-and-targets.md → guide/configuration-and-targets.md} +323 -297
- package/docs/guide/configuration-reference.md +1163 -0
- package/docs/{environment-variables.md → guide/environment-variables.md} +33 -28
- package/docs/{exit-codes-and-output.md → guide/exit-codes-and-output.md} +6 -3
- package/docs/{extensions-and-sharing.md → guide/extensions-and-sharing.md} +41 -14
- package/docs/{fleet-dispatch.md → guide/fleet-dispatch.md} +39 -43
- package/docs/{glossary.md → guide/glossary.md} +14 -11
- package/docs/{installation-and-lifecycle.md → guide/installation-and-lifecycle.md} +81 -17
- package/docs/guide/panes-and-files.md +290 -0
- package/docs/{proactive-memory.md → guide/proactive-memory.md} +131 -107
- package/docs/{resource-library.md → guide/resource-library.md} +13 -4
- package/docs/{skills-marketplace.md → guide/skills-marketplace.md} +25 -3
- package/docs/{tool-usage.md → guide/tool-usage.md} +87 -23
- package/docs/{troubleshooting.md → guide/troubleshooting.md} +9 -4
- package/docs/{config-knobs-audit.md → history/config-knobs-audit.md} +11 -11
- package/docs/{release-cut-checklist.md → history/release-cut-checklist.md} +29 -2
- package/docs/process/development-pipeline.md +152 -0
- package/docs/process/documentation-coverage.md +100 -0
- package/docs/process/documentation-guide.md +187 -0
- package/docs/{eval-runner.md → process/eval-runner.md} +108 -53
- package/docs/{evals-internal.md → process/evals-internal.md} +10 -10
- package/docs/{evolution.md → process/evolution.md} +2 -2
- package/docs/{fleet-demo-runbook.md → process/fleet-demo-runbook.md} +11 -7
- package/docs/{git-commit-provenance.md → process/git-commit-provenance.md} +11 -4
- package/docs/{performance-methodology.md → process/performance-methodology.md} +87 -69
- package/docs/{scientific-validation.md → process/scientific-validation.md} +4 -4
- package/evals/README.md +2 -2
- package/evals/behavioral-model.yaml +3 -2
- package/package.json +10 -8
- package/skills/README.md +52 -41
- package/skills/coding/ast-grep/SKILL.md +102 -31
- package/skills/coding/ast-grep/evals.md +26 -0
- package/skills/coding/coding-standards/SKILL.md +41 -6
- package/skills/coding/coding-standards/evals.md +23 -0
- package/skills/coding/prototype/SKILL.md +88 -29
- package/skills/coding/prototype/evals.md +19 -0
- package/skills/coding/tdd/SKILL.md +81 -54
- package/skills/coding/tdd/evals.md +20 -0
- package/skills/context/context-handoff/SKILL.md +44 -3
- package/skills/context/context-handoff/evals.md +44 -0
- package/skills/context/context-prime/SKILL.md +46 -16
- package/skills/context/context-prime/evals.md +45 -0
- package/skills/git/branch-closeout/SKILL.md +132 -0
- package/skills/git/branch-closeout/evals.md +133 -0
- package/skills/git/branch-closeout/references/closeout-checklist.md +81 -0
- package/skills/git/file-ticket/SKILL.md +78 -64
- package/skills/git/file-ticket/assets/issue-template.md +22 -0
- package/skills/git/file-ticket/evals.md +31 -26
- package/skills/git/file-ticket/references/issue-discovery.md +49 -0
- package/skills/git/fix-issue/SKILL.md +88 -65
- package/skills/git/fix-issue/evals.md +35 -31
- package/skills/git/fix-issue/references/diagnosis-and-rca.md +46 -0
- package/skills/git/resolve-merge-conflicts/SKILL.md +101 -52
- package/skills/git/resolve-merge-conflicts/evals.md +52 -25
- package/skills/git/resolve-merge-conflicts/references/conflict-matrix.md +126 -0
- package/skills/git/ship/SKILL.md +103 -67
- package/skills/git/ship/assets/pr-template.md +21 -0
- package/skills/git/ship/evals.md +44 -28
- package/skills/git/ship/references/remote-and-branch-policy.md +62 -0
- package/skills/git/worktree-create/SKILL.md +80 -50
- package/skills/git/worktree-create/evals.md +40 -33
- package/skills/git/worktree-create/references/worktree-setup.md +62 -66
- package/skills/git/worktree-merge/SKILL.md +112 -65
- package/skills/git/worktree-merge/evals.md +42 -34
- package/skills/git/worktree-merge/references/merge-strategies.md +52 -0
- package/skills/meta/clio-coder-dev/SKILL.md +9 -5
- package/skills/meta/clio-coder-dev/evals.md +3 -2
- package/skills/meta/clio-coder-test/SKILL.md +102 -95
- package/skills/meta/clio-coder-test/evals.md +9 -4
- package/skills/meta/clio-coder-test/references/harness.md +100 -124
- package/skills/meta/clio-coder-test/references/test-map.md +77 -50
- package/skills/meta/credentials/SKILL.md +2 -2
- package/skills/meta/find-skills/SKILL.md +2 -2
- package/skills/meta/herdr/SKILL.md +2 -2
- package/skills/meta/skill-craft/SKILL.md +22 -16
- package/skills/planning/archify/SKILL.md +196 -0
- package/skills/planning/archify/evals.md +65 -0
- package/skills/planning/architecture/SKILL.md +62 -13
- package/skills/planning/architecture/evals.md +65 -0
- package/skills/planning/backlog/SKILL.md +131 -15
- package/skills/planning/backlog/evals.md +142 -0
- package/skills/planning/prd/SKILL.md +47 -7
- package/skills/planning/prd/evals.md +54 -0
- package/skills/planning/product-intent/SKILL.md +58 -3
- package/skills/planning/product-intent/evals.md +70 -0
- package/skills/planning/tech-spec/SKILL.md +54 -3
- package/skills/planning/tech-spec/evals.md +73 -0
- package/skills/registry.yaml +70 -62
- package/skills/remote.yaml +13 -0
- package/skills/research/arxiv-literature/SKILL.md +77 -19
- package/skills/research/arxiv-literature/evals.md +50 -0
- package/skills/research/experiment-protocol/SKILL.md +21 -2
- package/skills/research/experiment-protocol/evals.md +23 -0
- package/skills/research/scientific-debugging/SKILL.md +24 -2
- package/skills/research/scientific-debugging/evals.md +18 -0
- package/skills/research/scientific-modernization/SKILL.md +27 -2
- package/skills/research/scientific-modernization/evals.md +27 -0
- package/skills/skill-marketplace.json +97 -62
- package/skills/workflow/cut-it/SKILL.md +66 -6
- package/skills/workflow/cut-it/evals.md +101 -0
- package/skills/workflow/design-council/SKILL.md +118 -28
- package/skills/workflow/design-council/evals.md +161 -0
- package/skills/workflow/grill-me/SKILL.md +87 -11
- package/skills/workflow/grill-me/evals.md +153 -0
- package/skills/workflow/workflow-distiller/SKILL.md +77 -18
- package/skills/workflow/workflow-distiller/evals.md +118 -0
- package/src/cli/args.ts +2 -2
- package/src/cli/bootstrap-generate.ts +1 -1
- package/src/cli/config-inspect.ts +65 -12
- package/src/cli/configure-interop.ts +105 -13
- package/src/cli/configure-oauth.ts +57 -0
- package/src/cli/configure-onboarding.ts +980 -0
- package/src/cli/configure-target.ts +594 -0
- package/src/cli/configure.ts +1082 -532
- package/src/cli/context-map.ts +114 -0
- package/src/cli/context.ts +4 -0
- package/src/cli/docs.ts +22 -14
- package/src/cli/doctor-naming.ts +5 -5
- package/src/cli/doctor-toolchain.ts +3 -3
- package/src/cli/eval.ts +1 -2
- package/src/cli/extensions.ts +2 -1
- package/src/cli/fleet.ts +1 -1
- package/src/cli/index.ts +3 -1
- package/src/cli/internal-dispatch.ts +3 -4
- package/src/cli/lifecycle-presenter.ts +436 -0
- package/src/cli/models.ts +10 -2
- package/src/cli/modes/print.ts +5 -1
- package/src/cli/panes.ts +19 -5
- package/src/cli/reset.ts +228 -106
- package/src/cli/run.ts +9 -4
- package/src/cli/select.ts +664 -0
- package/src/cli/share.ts +5 -1
- package/src/cli/skills-eval.ts +3 -3
- package/src/cli/skills.ts +9 -2
- package/src/cli/targets.ts +5 -6
- package/src/cli/trace.ts +55 -4
- package/src/cli/uninstall.ts +233 -165
- package/src/cli/upgrade.ts +204 -149
- package/src/cli/usage.ts +86 -27
- package/src/cli/validate-model.ts +3 -3
- package/src/cli/wiki-generate.ts +1 -1
- package/src/core/artifact-paths.ts +1 -1
- package/src/core/bash-exec.ts +131 -86
- package/src/core/bus-events.ts +51 -6
- package/src/core/config.ts +61 -1
- package/src/core/defaults.ts +7 -4
- package/src/core/dispatch-outcome.ts +16 -0
- package/src/core/external-diagnostic.ts +44 -0
- package/src/core/gateway-routing.ts +157 -0
- package/src/core/guardrails.ts +10 -49
- package/src/core/prompt-hint.ts +9 -0
- package/src/core/safe-exec.ts +17 -2
- package/src/core/skill-activation.ts +89 -2
- package/src/domains/agents/builtins/architect.md +2 -3
- package/src/domains/agents/builtins/coder.md +3 -2
- package/src/domains/agents/builtins/debugger.md +2 -2
- package/src/domains/agents/builtins/documenter.md +2 -2
- package/src/domains/agents/builtins/git-master.md +1 -1
- package/src/domains/agents/builtins/oracle.md +1 -1
- package/src/domains/agents/builtins/provenance.md +1 -1
- package/src/domains/agents/builtins/researcher.md +1 -1
- package/src/domains/agents/builtins/scout.md +1 -1
- package/src/domains/agents/builtins/tester.md +2 -2
- package/src/domains/agents/builtins/verifier.md +2 -2
- package/src/domains/agents/builtins/wiki-writer.md +1 -1
- package/src/domains/agents/builtins/world-knowledge.md +31 -0
- package/src/domains/agents/catalog.ts +13 -15
- package/src/domains/agents/contract.ts +2 -0
- package/src/domains/agents/extension.ts +23 -1
- package/src/domains/agents/result-contract.ts +70 -0
- package/src/domains/config/keybindings.ts +8 -0
- package/src/domains/context/extension.ts +0 -3
- package/src/domains/context/wiki/map-seed.ts +589 -0
- package/src/domains/context/wiki/plan.ts +2 -2
- package/src/domains/context/working-set/path-index.ts +1 -0
- package/src/domains/dispatch/admission.ts +29 -0
- package/src/domains/dispatch/agent-candidates.ts +10 -0
- package/src/domains/dispatch/budget-envelope.ts +86 -1
- package/src/domains/dispatch/capability-match.ts +11 -0
- package/src/domains/dispatch/capacity-lease.ts +17 -0
- package/src/domains/dispatch/contract.ts +11 -1
- package/src/domains/dispatch/extension.ts +237 -49
- package/src/domains/dispatch/host-verification.ts +435 -39
- package/src/domains/dispatch/intent-requirements.ts +10 -0
- package/src/domains/dispatch/intent.ts +18 -1
- package/src/domains/dispatch/path-scope.ts +235 -24
- package/src/domains/dispatch/run-event-journal.ts +4 -15
- package/src/domains/dispatch/state.ts +2 -3
- package/src/domains/dispatch/transport.ts +45 -21
- package/src/domains/dispatch/types.ts +58 -3
- package/src/domains/dispatch/worker-model-metadata.ts +38 -0
- package/src/domains/eval/artifacts/store.ts +5 -0
- package/src/domains/eval/metrics/call-ledger-stream.ts +34 -11
- package/src/domains/eval/metrics/token-stream.ts +201 -31
- package/src/domains/eval/metrics/tracked.ts +40 -4
- package/src/domains/eval/runners/clio-run.ts +5 -2
- package/src/domains/eval/schema/suite.ts +28 -0
- package/src/domains/eval/schema/verdict.ts +2 -2
- package/src/domains/eval/store.ts +8 -1
- package/src/domains/eval/suites/resolve.ts +13 -1
- package/src/domains/eval/suites/run.ts +24 -3
- package/src/domains/evidence/trust-status.ts +10 -1
- package/src/domains/extensions/contract.ts +15 -1
- package/src/domains/extensions/discovery.ts +238 -41
- package/src/domains/extensions/extension.ts +105 -6
- package/src/domains/extensions/index.ts +24 -0
- package/src/domains/extensions/integrity.ts +189 -0
- package/src/domains/extensions/manager.ts +17 -1
- package/src/domains/extensions/resource-path.ts +27 -0
- package/src/domains/extensions/resources.ts +18 -38
- package/src/domains/extensions/snapshot-store.ts +39 -0
- package/src/domains/extensions/snapshot.ts +180 -0
- package/src/domains/extensions/state.ts +385 -57
- package/src/domains/extensions/types.ts +118 -1
- package/src/domains/interop/registry.ts +6 -2
- package/src/domains/interop/types.ts +4 -0
- package/src/domains/lifecycle/migrations/2026-09-01-extension-install-digests.ts +27 -0
- package/src/domains/lifecycle/migrations/index.ts +6 -0
- package/src/domains/lifecycle/naming-resources.ts +19 -4
- package/src/domains/lifecycle/naming-yazi.ts +10 -5
- package/src/domains/memory/task-memory-policy.ts +70 -26
- package/src/domains/memory/task-memory-telemetry.ts +1 -0
- package/src/domains/middleware/contract.ts +26 -0
- package/src/domains/middleware/extension.ts +24 -24
- package/src/domains/middleware/hook-receipts.ts +27 -4
- package/src/domains/middleware/hooks-io.ts +65 -32
- package/src/domains/middleware/hooks.ts +64 -0
- package/src/domains/middleware/index.ts +28 -5
- package/src/domains/middleware/marketplace-offer.ts +3 -35
- package/src/domains/middleware/memory-intervention.ts +127 -32
- package/src/domains/middleware/memory-step-endpoint.ts +3 -2
- package/src/domains/middleware/registrations.ts +326 -0
- package/src/domains/middleware/runtime.ts +28 -0
- package/src/domains/middleware/skills-reminder.ts +31 -2
- package/src/domains/middleware/snapshot.ts +20 -7
- package/src/domains/mux/contract.ts +38 -0
- package/src/domains/mux/detect.ts +6 -13
- package/src/domains/mux/index.ts +1 -1
- package/src/domains/mux/operations.ts +44 -5
- package/src/domains/mux/yazi/assets/yazi.toml +2 -2
- package/src/domains/mux/yazi/session.ts +53 -4
- package/src/domains/mux/yazi/theme.ts +117 -17
- package/src/domains/observability/compaction-usage.ts +118 -0
- package/src/domains/observability/contract.ts +10 -11
- package/src/domains/observability/cost.ts +1 -1
- package/src/domains/observability/extension.ts +17 -4
- package/src/domains/observability/out-of-turn-usage.ts +52 -21
- package/src/domains/observability/projection.ts +14 -90
- package/src/domains/observability/trace-store.ts +43 -7
- package/src/domains/prompts/compiler.ts +73 -53
- package/src/domains/prompts/contract.ts +15 -3
- package/src/domains/prompts/extension.ts +97 -9
- package/src/domains/prompts/fragments/identity/clio-worker.md +1 -3
- package/src/domains/prompts/fragments/identity/clio.md +6 -12
- package/src/domains/prompts/fragments/identity/docs-routing.md +1 -2
- package/src/domains/prompts/fragments/identity/self-awareness.md +3 -11
- package/src/domains/prompts/fragments/operating/contract.md +7 -15
- package/src/domains/prompts/fragments/operating/delegation.md +32 -34
- package/src/domains/prompts/fragments/operating/skills.md +10 -24
- package/src/domains/prompts/fragments/operating/worker.md +1 -8
- package/src/domains/providers/contract.ts +4 -1
- package/src/domains/providers/extension.ts +40 -9
- package/src/domains/providers/index.ts +1 -1
- package/src/domains/providers/model-capabilities.ts +9 -0
- package/src/domains/providers/model-discovery.ts +2 -0
- package/src/domains/providers/model-runtime-capabilities.ts +99 -25
- package/src/domains/providers/models/local-models/clio-coder-local-coding-targets.yaml +699 -114
- package/src/domains/providers/runtime-resolution.ts +31 -0
- package/src/domains/providers/runtimes/antigravity/antigravity-code.ts +225 -45
- package/src/domains/providers/runtimes/common/lmstudio-http.ts +6 -2
- package/src/domains/providers/runtimes/common/local-synth.ts +2 -0
- package/src/domains/providers/runtimes/common/probe-helpers.ts +7 -2
- package/src/domains/providers/runtimes/local-native/llamacpp.ts +9 -1
- package/src/domains/providers/runtimes/protocol/litellm.ts +119 -29
- package/src/domains/providers/support.ts +11 -5
- package/src/domains/providers/target-model-cache.ts +25 -2
- package/src/domains/providers/types/capability-flags.ts +2 -0
- package/src/domains/providers/types/cost-provenance.ts +19 -0
- package/src/domains/providers/types/local-model-quirks.ts +85 -37
- package/src/domains/providers/types/runtime-descriptor.ts +20 -1
- package/src/domains/providers/types/target-descriptor.ts +19 -0
- package/src/domains/resources/index.ts +3 -0
- package/src/domains/resources/skills/install.ts +72 -7
- package/src/domains/resources/skills/loader.ts +23 -19
- package/src/domains/resources/skills/marketplace.ts +63 -11
- package/src/domains/safety/autonomy.ts +15 -0
- package/src/domains/safety/call-target.ts +1 -1
- package/src/domains/safety/index.ts +1 -0
- package/src/domains/safety/loop-detector.ts +7 -4
- package/src/domains/safety/path-policy.ts +1 -1
- package/src/domains/safety/policy-engine.ts +34 -11
- package/src/domains/safety/protected-artifacts.ts +191 -88
- package/src/domains/safety/run-effects.ts +2 -22
- package/src/domains/safety/skill-authority.ts +55 -0
- package/src/domains/session/compaction/compact.ts +72 -22
- package/src/domains/session/entries.ts +6 -0
- package/src/domains/session/task-board.ts +10 -9
- package/src/domains/session/usage.ts +3 -3
- package/src/domains/share/archive.ts +164 -7
- package/src/engine/acp/server.ts +62 -9
- package/src/engine/agent.ts +13 -3
- package/src/engine/ai.ts +26 -8
- package/src/engine/antigravity/subprocess-runtime.ts +386 -120
- package/src/engine/api-registry.ts +3 -0
- package/src/engine/apis/llamacpp-residency.ts +3 -4
- package/src/engine/apis/lmstudio.ts +3 -3
- package/src/engine/apis/ollama-native.ts +6 -6
- package/src/engine/apis/openai-completions.ts +145 -39
- package/src/engine/apis/output-budget.ts +8 -18
- package/src/engine/apis/residency.ts +8 -27
- package/src/engine/external-subprocess.ts +114 -6
- package/src/engine/gemma-channel-filter.ts +19 -0
- package/src/engine/loop-guard.ts +92 -12
- package/src/engine/worker-runtime.ts +40 -11
- package/src/engine/worker-tools.ts +3 -1
- package/src/entry/background-model-metadata.ts +18 -0
- package/src/entry/compaction-prompt.ts +57 -0
- package/src/entry/extension-hook-sources.ts +28 -0
- package/src/entry/extension-reload.ts +309 -0
- package/src/entry/orchestrator.ts +464 -251
- package/src/entry/task-memory-lifecycle.ts +35 -0
- package/src/interactive/application-controller.ts +2 -1
- package/src/interactive/bus-notices.ts +8 -1
- package/src/interactive/chat-loop-messages.ts +16 -17
- package/src/interactive/chat-loop.ts +75 -3
- package/src/interactive/chat-panel.ts +36 -13
- package/src/interactive/chat-renderer.ts +72 -7
- package/src/interactive/cost-overlay.ts +26 -2
- package/src/interactive/dispatch-board.ts +6 -11
- package/src/interactive/footer/widgets.ts +13 -0
- package/src/interactive/interactive-application.ts +39 -4
- package/src/interactive/interactive-input-runtime.ts +4 -0
- package/src/interactive/interactive-presentation.ts +2 -2
- package/src/interactive/interactive-slash-runtime.ts +4 -1
- package/src/interactive/overlays/extensions.ts +9 -1
- package/src/interactive/overlays/help-reference.ts +13 -0
- package/src/interactive/overlays/settings.ts +27 -16
- package/src/interactive/panes-runtime.ts +111 -35
- package/src/interactive/prompt-cache-identity.ts +88 -0
- package/src/interactive/renderers/worker-entry.ts +32 -0
- package/src/interactive/slash-commands.ts +153 -20
- package/src/interactive/stream-pacing-policy.ts +0 -23
- package/src/interactive/theme/labels.ts +19 -13
- package/src/interactive/turn-context.ts +39 -20
- package/src/interactive/turn-recovery.ts +8 -0
- package/src/interactive/turn-runtime.ts +27 -11
- package/src/interactive/turn-state.ts +7 -0
- package/src/interactive/worker-receipts.ts +1 -0
- package/src/interactive/worker-stream.ts +6 -1
- package/src/interactive/yazi-bridge.ts +60 -6
- package/src/tools/agent-tools.ts +30 -1
- package/src/tools/artifact.ts +2 -2
- package/src/tools/ask-user.ts +3 -3
- package/src/tools/bash.ts +1 -1
- package/src/tools/bootstrap.ts +4 -0
- package/src/tools/builtin-tool-catalog.ts +52 -22
- package/src/tools/codewiki/code-nav-surface.ts +6 -0
- package/src/tools/codewiki/code-nav.ts +99 -13
- package/src/tools/context/docs-engine.ts +20 -7
- package/src/tools/context/index.ts +59 -21
- package/src/tools/core-bootstrap.ts +28 -6
- package/src/tools/credential-present.ts +1 -2
- package/src/tools/dispatch-arguments.ts +6 -1
- package/src/tools/dispatch-event-text.ts +10 -0
- package/src/tools/dispatch-plan.ts +49 -4
- package/src/tools/dispatch-run-events.ts +1 -1
- package/src/tools/dispatch-runner.ts +12 -0
- package/src/tools/dispatch-schema.ts +338 -0
- package/src/tools/dispatch-types.ts +3 -0
- package/src/tools/dispatch.ts +9 -254
- package/src/tools/ledger.ts +3 -5
- package/src/tools/monitor-surface.ts +5 -13
- package/src/tools/observation.ts +4 -5
- package/src/tools/panes-surface.ts +4 -11
- package/src/tools/panes.ts +4 -2
- package/src/tools/policy.ts +15 -2
- package/src/tools/read.ts +5 -6
- package/src/tools/registry.ts +41 -12
- package/src/tools/result-shaping.ts +18 -14
- package/src/tools/steer-surface.ts +1 -1
- package/src/tools/tasks.ts +1 -1
- package/src/tools/truncate.ts +6 -5
- package/src/tools/verify/surface.ts +6 -12
- package/src/tools/web-fetch-surface.ts +1 -3
- package/src/tools/worker-evidence.ts +3 -1
- package/src/worker/spec-contract.ts +4 -0
- package/dist/builtins-UJLMOVOV.js +0 -17
- package/dist/chunk-5QIAJV2D.js +0 -48
- package/dist/chunk-JZWT5J3Y.js +0 -814
- package/dist/chunk-K7VKOLQQ.js +0 -15
- package/dist/chunk-PMZCIOCJ.js +0 -25
- package/dist/chunk-SUW5DORT.js +0 -819
- package/dist/chunk-UOV2BYIW.js +0 -107
- package/dist/chunk-WR6U3OVP.js +0 -45
- package/dist/chunk-Y45G3AXC.js +0 -1558
- package/dist/reset-EOLM7GVE.js +0 -230
- package/dist/uninstall-N34PCTGJ.js +0 -331
- package/dist/upgrade-H7TOM7YL.js +0 -323
- package/docs/artifact-versions.md +0 -67
- package/docs/development-pipeline.md +0 -121
- package/docs/documentation-coverage.md +0 -46
- package/docs/documentation-guide.md +0 -167
- package/docs/time-conventions.md +0 -101
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
import { createRequire as __clioCreateRequire } from "node:module"; const require = __clioCreateRequire(import.meta.url);
|
|
2
|
-
import {
|
|
3
|
-
hashToolCall
|
|
4
|
-
} from "./chunk-UOV2BYIW.js";
|
|
5
2
|
import {
|
|
6
3
|
TASK_MEMORY_DEFAULT_PROCEDURAL_CAP,
|
|
7
4
|
TASK_MEMORY_POLICY_DEFAULT_TIMEOUT_MS,
|
|
8
5
|
runTaskMemoryPolicy,
|
|
9
6
|
taskMemoryBankDelta
|
|
10
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-2WZ546HR.js";
|
|
11
8
|
import {
|
|
12
9
|
isTerminalRunEnvelope
|
|
13
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-PAJQJ7BS.js";
|
|
14
11
|
import {
|
|
15
12
|
taskBoardCounts
|
|
16
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-L47TF46W.js";
|
|
14
|
+
import {
|
|
15
|
+
hashToolCall
|
|
16
|
+
} from "./chunk-RSJ25QSL.js";
|
|
17
|
+
import {
|
|
18
|
+
legacyToolResultDigest,
|
|
19
|
+
sanitizeToolResultDigest
|
|
20
|
+
} from "./chunk-JDAY6FIL.js";
|
|
17
21
|
import {
|
|
18
22
|
SKILL_INSTALL_OFFER_OPTION_NEVER,
|
|
19
23
|
SKILL_INSTALL_OFFER_OPTION_NOT_NOW,
|
|
@@ -21,23 +25,19 @@ import {
|
|
|
21
25
|
SKILL_INSTALL_OFFER_OPTION_USER,
|
|
22
26
|
SKILL_SUGGESTION_ANCHOR,
|
|
23
27
|
SKILL_SUGGESTION_PREFIX
|
|
24
|
-
} from "./chunk-
|
|
25
|
-
import {
|
|
26
|
-
legacyToolResultDigest,
|
|
27
|
-
sanitizeToolResultDigest
|
|
28
|
-
} from "./chunk-R23Z6K6I.js";
|
|
28
|
+
} from "./chunk-JGRC33J2.js";
|
|
29
29
|
import {
|
|
30
30
|
ToolNames
|
|
31
31
|
} from "./chunk-K6BSR66V.js";
|
|
32
32
|
import {
|
|
33
33
|
registerForegroundStream
|
|
34
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-XPWWI35G.js";
|
|
35
35
|
import {
|
|
36
36
|
ceilChars
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-NZMNUPZZ.js";
|
|
38
38
|
import {
|
|
39
39
|
BusChannels
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-F4EKGO4N.js";
|
|
41
41
|
import {
|
|
42
42
|
reportCommitAttributionDiagnostic,
|
|
43
43
|
withManagedGitCommitAttributionEnvironment
|
|
@@ -45,12 +45,12 @@ import {
|
|
|
45
45
|
import {
|
|
46
46
|
require_dist
|
|
47
47
|
} from "./chunk-APJ265NV.js";
|
|
48
|
-
import {
|
|
49
|
-
clioConfigDir
|
|
50
|
-
} from "./chunk-BYP5D4HI.js";
|
|
51
48
|
import {
|
|
52
49
|
safeResourceWrite
|
|
53
50
|
} from "./chunk-WEPFGWHJ.js";
|
|
51
|
+
import {
|
|
52
|
+
clioConfigDir
|
|
53
|
+
} from "./chunk-BYP5D4HI.js";
|
|
54
54
|
import {
|
|
55
55
|
__toESM,
|
|
56
56
|
init_esm_shims
|
|
@@ -436,6 +436,11 @@ function writeMiddlewareDiagnosticToStderr(diagnostic) {
|
|
|
436
436
|
);
|
|
437
437
|
return;
|
|
438
438
|
}
|
|
439
|
+
if (diagnostic.kind === "registration_conflict") {
|
|
440
|
+
process.stderr.write(`[clio-coder:middleware] ${formatRegistrationConflict(diagnostic)}
|
|
441
|
+
`);
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
439
444
|
if (!diagnostic.steadyStateWarn && process.env.CLIO_CODER_HOOK_BUDGET_DEBUG !== "1") return;
|
|
440
445
|
const stats = diagnostic.stats;
|
|
441
446
|
const trend = stats.window > 0 ? ` (slow on ${stats.overCount}/${stats.window} recent ${diagnostic.hook} calls, p95 ${stats.p95Ms.toFixed(1)}ms)` : "";
|
|
@@ -444,6 +449,9 @@ function writeMiddlewareDiagnosticToStderr(diagnostic) {
|
|
|
444
449
|
`
|
|
445
450
|
);
|
|
446
451
|
}
|
|
452
|
+
function formatRegistrationConflict(diagnostic) {
|
|
453
|
+
return diagnostic.action === "dropped" ? `${diagnostic.owner} registration '${diagnostic.registrationId}' (generation ${diagnostic.generation}) dropped: id is held by a ${diagnostic.conflictsWith} registration` : `${diagnostic.owner} registration '${diagnostic.registrationId}' (generation ${diagnostic.generation}) evicted by a ${diagnostic.conflictsWith} registration with the same id`;
|
|
454
|
+
}
|
|
447
455
|
function registrationFromRuleDefinition(definition) {
|
|
448
456
|
const registration = {
|
|
449
457
|
id: definition.rule.id,
|
|
@@ -906,6 +914,7 @@ function createDetachedDispatchNudgeRegistration(options) {
|
|
|
906
914
|
|
|
907
915
|
// src/domains/middleware/hook-receipts.ts
|
|
908
916
|
init_esm_shims();
|
|
917
|
+
import { readFileSync } from "node:fs";
|
|
909
918
|
var HOOK_RECEIPT_LOG_CAPACITY = 200;
|
|
910
919
|
var PERSIST_THROTTLE_MS = 2e3;
|
|
911
920
|
function createHookReceiptLog(options = {}) {
|
|
@@ -943,1806 +952,2103 @@ function createHookReceiptLog(options = {}) {
|
|
|
943
952
|
}
|
|
944
953
|
};
|
|
945
954
|
}
|
|
955
|
+
function readPersistedHookReceipts(persistPath) {
|
|
956
|
+
let raw;
|
|
957
|
+
try {
|
|
958
|
+
raw = readFileSync(persistPath, "utf8");
|
|
959
|
+
} catch (error) {
|
|
960
|
+
if (error.code === "ENOENT") return [];
|
|
961
|
+
throw error;
|
|
962
|
+
}
|
|
963
|
+
const parsed = JSON.parse(raw);
|
|
964
|
+
if (parsed.version !== 1 || !Array.isArray(parsed.receipts)) {
|
|
965
|
+
throw new Error(`${persistPath}: unrecognized hook receipt log shape`);
|
|
966
|
+
}
|
|
967
|
+
return parsed.receipts;
|
|
968
|
+
}
|
|
946
969
|
|
|
947
|
-
// src/domains/middleware/
|
|
970
|
+
// src/domains/middleware/skills-reminder.ts
|
|
948
971
|
init_esm_shims();
|
|
949
|
-
var
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
972
|
+
var SKILLS_REMINDER_REGISTRATION_ID = "observer.skills-reminder";
|
|
973
|
+
function skillsReminderMessage(installed, installable = 0, modelActivation = false) {
|
|
974
|
+
const counts = installable > 0 ? `${installed} installed, ${installable} installable from the marketplace` : `${installed} installed`;
|
|
975
|
+
if (modelActivation) {
|
|
976
|
+
return `[Skills] ${counts}. Start this task by listing them with context(scope="skills") and checking for a match; if one matches, load it with context(scope="skills", name="<name>") and continue the task in the same turn. A marketplace skill is not installed and is offered for install when the operator runs it. If none match, do not mention skills and continue with the task.`;
|
|
977
|
+
}
|
|
978
|
+
return `[Skills] ${counts}. Start this task by listing them with context(scope="skills") and checking for a match; if one matches, open your reply with the line \`${SKILL_SUGGESTION_ANCHOR}\` (a comma-separated sequence, in order, when several compose) and then continue the task in the same turn without the skill. Only the operator loads a skill, and a marketplace skill is offered for install when the operator runs it. If none match, do not mention skills and continue with the task.`;
|
|
979
|
+
}
|
|
980
|
+
var NO_EFFECTS = [];
|
|
981
|
+
var GREETING_WORDS = /* @__PURE__ */ new Set([
|
|
982
|
+
"hi",
|
|
983
|
+
"hello",
|
|
984
|
+
"hey",
|
|
985
|
+
"heya",
|
|
986
|
+
"hiya",
|
|
987
|
+
"howdy",
|
|
988
|
+
"yo",
|
|
989
|
+
"sup",
|
|
990
|
+
"hola",
|
|
991
|
+
"greetings",
|
|
992
|
+
"gm",
|
|
993
|
+
"gn",
|
|
994
|
+
"morning",
|
|
995
|
+
"afternoon",
|
|
996
|
+
"evening",
|
|
997
|
+
"good",
|
|
998
|
+
"day",
|
|
999
|
+
"night",
|
|
1000
|
+
"there",
|
|
1001
|
+
"you",
|
|
1002
|
+
"clio",
|
|
1003
|
+
"thanks",
|
|
1004
|
+
"thank",
|
|
1005
|
+
"thankyou",
|
|
1006
|
+
"thx",
|
|
1007
|
+
"ty",
|
|
1008
|
+
"ok",
|
|
1009
|
+
"okay",
|
|
1010
|
+
"k",
|
|
1011
|
+
"cool",
|
|
1012
|
+
"nice",
|
|
1013
|
+
"test",
|
|
1014
|
+
"testing",
|
|
1015
|
+
"ping",
|
|
1016
|
+
"pong"
|
|
1017
|
+
]);
|
|
1018
|
+
function isSubstantiveUserTurn(text) {
|
|
1019
|
+
const normalized = (text ?? "").toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, " ").replace(/\s+/g, " ").trim();
|
|
1020
|
+
if (normalized.length === 0) return false;
|
|
1021
|
+
const tokens = normalized.split(" ");
|
|
1022
|
+
if (tokens.length > 4) return true;
|
|
1023
|
+
return tokens.some((token) => !GREETING_WORDS.has(token));
|
|
1024
|
+
}
|
|
1025
|
+
function createSkillsReminderRegistration(deps) {
|
|
1026
|
+
let lastSeenSessionId;
|
|
1027
|
+
let spentForActiveSession = false;
|
|
1028
|
+
let observedFreshStart = false;
|
|
1029
|
+
let observedAnyTurn = false;
|
|
1030
|
+
return {
|
|
1031
|
+
id: SKILLS_REMINDER_REGISTRATION_ID,
|
|
1032
|
+
description: "once per session, on the first substantive turn, teaches the skill-suggestion reply protocol; keeps a turn going that stopped on the suggestion",
|
|
1033
|
+
hooks: ["turn_start", "turn_end"],
|
|
1034
|
+
evaluate(input) {
|
|
1035
|
+
if (input.hook === "turn_end") {
|
|
1036
|
+
return isSkillSuggestionWait(input) ? [{ kind: "request_continuation", message: SKILL_SUGGESTION_WAIT_CONTINUATION_MESSAGE }] : NO_EFFECTS;
|
|
1037
|
+
}
|
|
1038
|
+
const sessionId = input.sessionId ?? null;
|
|
1039
|
+
if (lastSeenSessionId === void 0) {
|
|
1040
|
+
lastSeenSessionId = sessionId;
|
|
1041
|
+
} else if (lastSeenSessionId === null && sessionId !== null) {
|
|
1042
|
+
lastSeenSessionId = sessionId;
|
|
1043
|
+
} else if (sessionId !== lastSeenSessionId) {
|
|
1044
|
+
lastSeenSessionId = sessionId;
|
|
1045
|
+
spentForActiveSession = false;
|
|
1046
|
+
observedFreshStart = false;
|
|
1047
|
+
observedAnyTurn = false;
|
|
1048
|
+
}
|
|
1049
|
+
if (spentForActiveSession) return NO_EFFECTS;
|
|
1050
|
+
const conversationMessages = metadataNumber(input, "conversationMessages");
|
|
1051
|
+
if (!observedAnyTurn) {
|
|
1052
|
+
observedAnyTurn = true;
|
|
1053
|
+
if (conversationMessages === null || conversationMessages > 0) {
|
|
1054
|
+
spentForActiveSession = true;
|
|
1055
|
+
return NO_EFFECTS;
|
|
1056
|
+
}
|
|
1057
|
+
observedFreshStart = true;
|
|
1058
|
+
}
|
|
1059
|
+
if (!observedFreshStart) {
|
|
1060
|
+
spentForActiveSession = true;
|
|
1061
|
+
return NO_EFFECTS;
|
|
1062
|
+
}
|
|
1063
|
+
if (!isSubstantiveUserTurn(input.text)) return NO_EFFECTS;
|
|
1064
|
+
spentForActiveSession = true;
|
|
1065
|
+
const pendingSkillRequests = metadataNumber(input, "pendingSkillRequests");
|
|
1066
|
+
if (pendingSkillRequests !== null && pendingSkillRequests > 0) return NO_EFFECTS;
|
|
1067
|
+
let installed = 0;
|
|
1068
|
+
let installable = 0;
|
|
1069
|
+
try {
|
|
1070
|
+
installed = deps.countModelVisibleSkills();
|
|
1071
|
+
installable = deps.countInstallableSkills?.() ?? 0;
|
|
1072
|
+
} catch {
|
|
1073
|
+
return NO_EFFECTS;
|
|
1074
|
+
}
|
|
1075
|
+
if (installed <= 0 && installable <= 0) return NO_EFFECTS;
|
|
1076
|
+
let modelActivation = false;
|
|
1077
|
+
try {
|
|
1078
|
+
modelActivation = deps.modelMayActivateSkills?.() ?? false;
|
|
1079
|
+
} catch {
|
|
1080
|
+
}
|
|
1081
|
+
return [
|
|
1082
|
+
{
|
|
1083
|
+
kind: "inject_reminder",
|
|
1084
|
+
severity: "info",
|
|
1085
|
+
message: skillsReminderMessage(installed, installable, modelActivation)
|
|
1086
|
+
}
|
|
1087
|
+
];
|
|
1088
|
+
}
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
953
1091
|
|
|
954
|
-
// src/domains/middleware/
|
|
1092
|
+
// src/domains/middleware/marketplace-offer.ts
|
|
955
1093
|
init_esm_shims();
|
|
956
|
-
import {
|
|
957
|
-
import { isAbsolute, resolve } from "node:path";
|
|
1094
|
+
import { randomUUID } from "node:crypto";
|
|
958
1095
|
|
|
959
|
-
// src/domains/
|
|
1096
|
+
// src/domains/resources/skills/promotion.ts
|
|
960
1097
|
init_esm_shims();
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1098
|
+
import { existsSync, readFileSync as readFileSync2 } from "node:fs";
|
|
1099
|
+
import path from "node:path";
|
|
1100
|
+
var TOKEN_STOPWORDS = /* @__PURE__ */ new Set([
|
|
1101
|
+
"the",
|
|
1102
|
+
"a",
|
|
1103
|
+
"an",
|
|
1104
|
+
"and",
|
|
1105
|
+
"or",
|
|
1106
|
+
"for",
|
|
1107
|
+
"with",
|
|
1108
|
+
"this",
|
|
1109
|
+
"that",
|
|
1110
|
+
"when",
|
|
1111
|
+
"what",
|
|
1112
|
+
"how",
|
|
1113
|
+
"into",
|
|
1114
|
+
"from",
|
|
1115
|
+
"use",
|
|
1116
|
+
"used",
|
|
1117
|
+
"using",
|
|
1118
|
+
"one",
|
|
1119
|
+
"not",
|
|
1120
|
+
"skill",
|
|
1121
|
+
"skills",
|
|
1122
|
+
"clio",
|
|
1123
|
+
"please",
|
|
1124
|
+
"can",
|
|
1125
|
+
"you",
|
|
1126
|
+
"should",
|
|
1127
|
+
"would",
|
|
1128
|
+
"about",
|
|
1129
|
+
"need",
|
|
1130
|
+
"want",
|
|
1131
|
+
"help",
|
|
1132
|
+
"make",
|
|
1133
|
+
"file",
|
|
1134
|
+
"files",
|
|
1135
|
+
"code"
|
|
1136
|
+
]);
|
|
1137
|
+
function normalize(text) {
|
|
1138
|
+
return text.toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, " ").replace(/\s+/g, " ").trim();
|
|
966
1139
|
}
|
|
967
|
-
function
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
1140
|
+
function distinctiveTokens(text) {
|
|
1141
|
+
return new Set(
|
|
1142
|
+
normalize(text).split(" ").filter((token) => token.length >= 4 && !TOKEN_STOPWORDS.has(token))
|
|
1143
|
+
);
|
|
1144
|
+
}
|
|
1145
|
+
var TRIGGER_SCORE = 4;
|
|
1146
|
+
var TOKEN_FIRE_THRESHOLD = 3;
|
|
1147
|
+
function scorePromotionEntry(userText, entry) {
|
|
1148
|
+
const haystack = ` ${normalize(userText)} `;
|
|
1149
|
+
if (haystack.trim().length === 0) return null;
|
|
1150
|
+
for (const trigger of entry.triggers ?? []) {
|
|
1151
|
+
const phrase = normalize(trigger);
|
|
1152
|
+
if (phrase.length >= 4 && haystack.includes(` ${phrase} `)) {
|
|
1153
|
+
return { entry, score: TRIGGER_SCORE, matchedTrigger: trigger };
|
|
1154
|
+
}
|
|
976
1155
|
}
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
case "lock_tools":
|
|
991
|
-
rejectUnexpectedFields(value, path2, ["kind"], issues);
|
|
992
|
-
return { kind: "lock_tools" };
|
|
1156
|
+
const requestTokens = distinctiveTokens(userText);
|
|
1157
|
+
if (requestTokens.size === 0) return null;
|
|
1158
|
+
const entryTokens = distinctiveTokens(`${entry.name.replace(/-/g, " ")} ${entry.description}`);
|
|
1159
|
+
let overlap = 0;
|
|
1160
|
+
for (const token of requestTokens) if (entryTokens.has(token)) overlap += 1;
|
|
1161
|
+
return overlap >= TOKEN_FIRE_THRESHOLD ? { entry, score: overlap } : null;
|
|
1162
|
+
}
|
|
1163
|
+
function matchMarketplaceSkills(userText, entries, excludedNames) {
|
|
1164
|
+
const matches = [];
|
|
1165
|
+
for (const entry of entries) {
|
|
1166
|
+
if (entry.kind !== "skill" || excludedNames.has(entry.name)) continue;
|
|
1167
|
+
const match = scorePromotionEntry(userText, entry);
|
|
1168
|
+
if (match) matches.push(match);
|
|
993
1169
|
}
|
|
1170
|
+
return matches.sort((a, b) => b.score - a.score || a.entry.name.localeCompare(b.entry.name));
|
|
994
1171
|
}
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
const
|
|
998
|
-
|
|
1172
|
+
var OWN_MARKETPLACE_URL_PREFIX = "https://github.com/iowarp/clio-coder/";
|
|
1173
|
+
function isOwnMarketplaceSource(entry) {
|
|
1174
|
+
const source = entry.sourceUrl.trim();
|
|
1175
|
+
if (/^(?:https?:\/\/|git@)/.test(source)) return source.startsWith(OWN_MARKETPLACE_URL_PREFIX);
|
|
1176
|
+
return entry.origin === "catalog" && path.isAbsolute(source);
|
|
999
1177
|
}
|
|
1000
|
-
function
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
issues.push({ path: `${path2}.severity`, message: "expected info, advisory, warn, or hard-block" });
|
|
1006
|
-
}
|
|
1007
|
-
if (message === null) return null;
|
|
1008
|
-
const effect = { kind: "inject_reminder", message };
|
|
1009
|
-
if (severity !== void 0 && isMiddlewareReminderSeverity(severity)) effect.severity = severity;
|
|
1010
|
-
return effect;
|
|
1011
|
-
}
|
|
1012
|
-
function readAnnotateToolResult(record, path2, issues) {
|
|
1013
|
-
rejectUnexpectedFields(record, path2, ["kind", "message", "severity"], issues);
|
|
1014
|
-
const message = readRequiredString(record, `${path2}.message`, issues);
|
|
1015
|
-
const severity = readOptionalString(record, `${path2}.severity`, issues);
|
|
1016
|
-
if (severity !== void 0 && !isMiddlewareAnnotationSeverity(severity)) {
|
|
1017
|
-
issues.push({ path: `${path2}.severity`, message: "expected info or warn" });
|
|
1018
|
-
}
|
|
1019
|
-
if (message === null) return null;
|
|
1020
|
-
const effect = { kind: "annotate_tool_result", message };
|
|
1021
|
-
if (severity !== void 0 && isMiddlewareAnnotationSeverity(severity)) effect.severity = severity;
|
|
1022
|
-
return effect;
|
|
1023
|
-
}
|
|
1024
|
-
function readBlockTool(record, path2, issues) {
|
|
1025
|
-
rejectUnexpectedFields(record, path2, ["kind", "reason", "severity"], issues);
|
|
1026
|
-
const reason = readRequiredString(record, `${path2}.reason`, issues);
|
|
1027
|
-
const severity = readRequiredString(record, `${path2}.severity`, issues);
|
|
1028
|
-
if (severity !== null && severity !== "hard-block") {
|
|
1029
|
-
issues.push({ path: `${path2}.severity`, message: "expected hard-block" });
|
|
1030
|
-
}
|
|
1031
|
-
if (reason === null || severity !== "hard-block") return null;
|
|
1032
|
-
return { kind: "block_tool", reason, severity };
|
|
1033
|
-
}
|
|
1034
|
-
function readProtectPath(record, path2, issues) {
|
|
1035
|
-
rejectUnexpectedFields(record, path2, ["kind", "path", "reason"], issues);
|
|
1036
|
-
const protectedPath = readRequiredString(record, `${path2}.path`, issues);
|
|
1037
|
-
const reason = readRequiredString(record, `${path2}.reason`, issues);
|
|
1038
|
-
if (protectedPath === null || reason === null) return null;
|
|
1039
|
-
return { kind: "protect_path", path: protectedPath, reason };
|
|
1040
|
-
}
|
|
1041
|
-
function readRequestContinuation(record, path2, issues) {
|
|
1042
|
-
rejectUnexpectedFields(record, path2, ["kind", "message"], issues);
|
|
1043
|
-
const message = readRequiredString(record, `${path2}.message`, issues);
|
|
1044
|
-
if (message === null) return null;
|
|
1045
|
-
return { kind: "request_continuation", message };
|
|
1178
|
+
function assertPromotionInstallSource(entry) {
|
|
1179
|
+
if (isOwnMarketplaceSource(entry)) return;
|
|
1180
|
+
throw new Error(
|
|
1181
|
+
`skill promotion: refusing to install "${entry.name}" from ${entry.sourceUrl}; promotion installs only from Clio's own marketplace (local catalog or ${OWN_MARKETPLACE_URL_PREFIX}). Install manually with clio-coder skills install if intended.`
|
|
1182
|
+
);
|
|
1046
1183
|
}
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
issues.push({ path: path2, message: "expected non-empty string" });
|
|
1051
|
-
return null;
|
|
1052
|
-
}
|
|
1053
|
-
return value;
|
|
1184
|
+
var PROMOTION_DECLINES_FILE = "skill-promotion-declines.json";
|
|
1185
|
+
function declineKey(name, version) {
|
|
1186
|
+
return `${name}@${version ?? ""}`;
|
|
1054
1187
|
}
|
|
1055
|
-
function
|
|
1056
|
-
|
|
1057
|
-
if (!Object.hasOwn(record, field)) return void 0;
|
|
1058
|
-
const value = record[field];
|
|
1059
|
-
if (typeof value !== "string" || value.trim().length === 0) {
|
|
1060
|
-
issues.push({ path: path2, message: "expected non-empty string" });
|
|
1061
|
-
return void 0;
|
|
1062
|
-
}
|
|
1063
|
-
return value;
|
|
1188
|
+
function declinesPath(configDir) {
|
|
1189
|
+
return path.join(configDir ?? clioConfigDir(), PROMOTION_DECLINES_FILE);
|
|
1064
1190
|
}
|
|
1065
|
-
function
|
|
1066
|
-
const
|
|
1067
|
-
|
|
1068
|
-
|
|
1191
|
+
function readPromotionDeclines(configDir) {
|
|
1192
|
+
const file = declinesPath(configDir);
|
|
1193
|
+
if (!existsSync(file)) return { never: {} };
|
|
1194
|
+
try {
|
|
1195
|
+
const parsed = JSON.parse(readFileSync2(file, "utf8"));
|
|
1196
|
+
const never = parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed.never : void 0;
|
|
1197
|
+
if (never && typeof never === "object" && !Array.isArray(never)) {
|
|
1198
|
+
const entries = Object.entries(never).filter(
|
|
1199
|
+
(pair) => typeof pair[1] === "string"
|
|
1200
|
+
);
|
|
1201
|
+
return { never: Object.fromEntries(entries) };
|
|
1202
|
+
}
|
|
1203
|
+
} catch {
|
|
1069
1204
|
}
|
|
1205
|
+
return { never: {} };
|
|
1070
1206
|
}
|
|
1071
|
-
function
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1207
|
+
function recordPromotionNeverDecline(name, version, configDir) {
|
|
1208
|
+
const store = readPromotionDeclines(configDir);
|
|
1209
|
+
store.never[declineKey(name, version)] = (/* @__PURE__ */ new Date()).toISOString();
|
|
1210
|
+
safeResourceWrite(declinesPath(configDir), `${JSON.stringify(store, null, 2)}
|
|
1211
|
+
`, { encoding: "utf8" });
|
|
1076
1212
|
}
|
|
1077
1213
|
|
|
1078
|
-
// src/domains/middleware/
|
|
1079
|
-
var
|
|
1080
|
-
var
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
var USER_HOOK_COMMAND_OUTPUT_MAX_CHARS = 4e3;
|
|
1084
|
-
var USER_HOOK_PROMPT_MAX_CHARS = 2e3;
|
|
1085
|
-
function isRecord2(value) {
|
|
1086
|
-
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
1214
|
+
// src/domains/middleware/marketplace-offer.ts
|
|
1215
|
+
var MARKETPLACE_OFFER_REGISTRATION_ID = "observer.marketplace-offer";
|
|
1216
|
+
var NO_EFFECTS2 = [];
|
|
1217
|
+
function offerBindingTag(offerTag) {
|
|
1218
|
+
return `[clio-install:${offerTag}]`;
|
|
1087
1219
|
}
|
|
1088
|
-
function
|
|
1089
|
-
return
|
|
1220
|
+
function marketplaceOfferReminder(entry, offerTag) {
|
|
1221
|
+
return `[Marketplace] This request may need expertise no installed skill covers. The local marketplace has "${entry.name}" (not installed): ${entry.description} First check the installed side with context(scope="skills"); if ${entry.name} is genuinely the right fit and nothing installed serves, ask the operator with ask_user (mode=single_question, header "Install skill") whether to install ${entry.name}, offering exactly these options in this order: "${SKILL_INSTALL_OFFER_OPTION_PROJECT}", "${SKILL_INSTALL_OFFER_OPTION_USER}", "${SKILL_INSTALL_OFFER_OPTION_NOT_NOW}", "${SKILL_INSTALL_OFFER_OPTION_NEVER}". Include the exact tag ${offerBindingTag(offerTag)} verbatim in that question's text so the harness can bind the answer to this offer; the harness acts only on an answer carrying this tag. Then continue the task in the same turn. The harness handles the answer; never install or load a skill yourself. If it is not actually needed, do not mention it.`;
|
|
1090
1222
|
}
|
|
1091
|
-
function
|
|
1092
|
-
|
|
1093
|
-
if (
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
return JSON.stringify(value) ?? "null";
|
|
1223
|
+
function answersFromDetails(details) {
|
|
1224
|
+
const raw = details?.answers;
|
|
1225
|
+
if (!Array.isArray(raw)) return [];
|
|
1226
|
+
return raw.filter(
|
|
1227
|
+
(entry) => typeof entry === "object" && entry !== null && typeof entry.question === "string" && typeof entry.answer === "string"
|
|
1228
|
+
);
|
|
1098
1229
|
}
|
|
1099
|
-
function
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1230
|
+
function chosenOfferOption(answer) {
|
|
1231
|
+
const labels = [
|
|
1232
|
+
SKILL_INSTALL_OFFER_OPTION_PROJECT,
|
|
1233
|
+
SKILL_INSTALL_OFFER_OPTION_USER,
|
|
1234
|
+
SKILL_INSTALL_OFFER_OPTION_NOT_NOW,
|
|
1235
|
+
SKILL_INSTALL_OFFER_OPTION_NEVER
|
|
1236
|
+
];
|
|
1237
|
+
for (const label of labels) {
|
|
1238
|
+
if (answer.options?.includes(label)) return label;
|
|
1105
1239
|
}
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
function normalizeCommandSpec(raw, options, issues) {
|
|
1109
|
-
const argv = readStringArray(raw.argv);
|
|
1110
|
-
if (!argv || argv.length === 0) {
|
|
1111
|
-
issues.push("command.argv must be a non-empty array of non-empty strings");
|
|
1112
|
-
return null;
|
|
1240
|
+
for (const label of labels) {
|
|
1241
|
+
if (answer.answer.includes(label)) return label;
|
|
1113
1242
|
}
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1243
|
+
return null;
|
|
1244
|
+
}
|
|
1245
|
+
function createMarketplaceOfferRegistration(deps) {
|
|
1246
|
+
const declines = deps.declines ?? {
|
|
1247
|
+
readNever: () => readPromotionDeclines().never,
|
|
1248
|
+
recordNever: (name, version) => recordPromotionNeverDecline(name, version)
|
|
1120
1249
|
};
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1250
|
+
const newOfferTag = deps.newOfferTag ?? (() => randomUUID());
|
|
1251
|
+
let lastSeenSessionId;
|
|
1252
|
+
let offeredNames = /* @__PURE__ */ new Set();
|
|
1253
|
+
let sessionDeclines = /* @__PURE__ */ new Set();
|
|
1254
|
+
let installedNamesCache = null;
|
|
1255
|
+
let marketplaceEntriesCache = null;
|
|
1256
|
+
let pendingOffer = null;
|
|
1257
|
+
const trackSession = (input) => {
|
|
1258
|
+
const sessionId = input.sessionId ?? null;
|
|
1259
|
+
if (lastSeenSessionId === void 0 || lastSeenSessionId === null && sessionId !== null) {
|
|
1260
|
+
lastSeenSessionId = sessionId;
|
|
1261
|
+
return;
|
|
1130
1262
|
}
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1263
|
+
if (sessionId !== lastSeenSessionId) {
|
|
1264
|
+
lastSeenSessionId = sessionId;
|
|
1265
|
+
offeredNames = /* @__PURE__ */ new Set();
|
|
1266
|
+
sessionDeclines = /* @__PURE__ */ new Set();
|
|
1267
|
+
pendingOffer = null;
|
|
1268
|
+
installedNamesCache = null;
|
|
1269
|
+
marketplaceEntriesCache = null;
|
|
1137
1270
|
}
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1271
|
+
};
|
|
1272
|
+
const bestMatch = (text) => {
|
|
1273
|
+
const installedWasCached = installedNamesCache !== null;
|
|
1274
|
+
let never;
|
|
1275
|
+
try {
|
|
1276
|
+
installedNamesCache ??= new Set(deps.listInstalledSkillNames());
|
|
1277
|
+
marketplaceEntriesCache ??= deps.listMarketplaceEntries();
|
|
1278
|
+
never = declines.readNever();
|
|
1279
|
+
} catch {
|
|
1146
1280
|
return null;
|
|
1147
1281
|
}
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1282
|
+
const declinedKeys = /* @__PURE__ */ new Set([...Object.keys(never), ...sessionDeclines]);
|
|
1283
|
+
const findMatch = () => matchMarketplaceSkills(
|
|
1284
|
+
text,
|
|
1285
|
+
marketplaceEntriesCache ?? [],
|
|
1286
|
+
/* @__PURE__ */ new Set([...installedNamesCache ?? [], ...offeredNames])
|
|
1287
|
+
).find((candidate) => !declinedKeys.has(declineKey(candidate.entry.name, candidate.entry.version))) ?? null;
|
|
1288
|
+
let match = findMatch();
|
|
1289
|
+
if (match && installedWasCached) {
|
|
1290
|
+
try {
|
|
1291
|
+
installedNamesCache = new Set(deps.listInstalledSkillNames());
|
|
1292
|
+
} catch {
|
|
1293
|
+
return null;
|
|
1294
|
+
}
|
|
1295
|
+
match = findMatch();
|
|
1162
1296
|
}
|
|
1163
|
-
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
}
|
|
1188
|
-
let tools;
|
|
1189
|
-
if (raw.tools !== void 0) {
|
|
1190
|
-
const parsed = readStringArray(raw.tools);
|
|
1191
|
-
if (!parsed) issues.push("tools must be an array of non-empty tool names");
|
|
1192
|
-
else tools = parsed;
|
|
1193
|
-
}
|
|
1194
|
-
let enabled = true;
|
|
1195
|
-
if (raw.enabled !== void 0) {
|
|
1196
|
-
if (typeof raw.enabled !== "boolean") issues.push("enabled must be a boolean");
|
|
1197
|
-
else enabled = raw.enabled;
|
|
1198
|
-
}
|
|
1199
|
-
let spec = null;
|
|
1200
|
-
if (raw.kind === "command") spec = normalizeCommandSpec(raw, options, issues);
|
|
1201
|
-
else if (raw.kind === "prompt") spec = normalizePromptSpec(raw, issues);
|
|
1202
|
-
else if (raw.kind === "effect") spec = normalizeEffectSpec(raw, issues);
|
|
1203
|
-
else issues.push('kind must be "command", "prompt", or "effect"');
|
|
1204
|
-
if (issues.length > 0 || spec === null || raw.on === void 0 || typeof raw.on !== "string") {
|
|
1205
|
-
return { issues };
|
|
1206
|
-
}
|
|
1207
|
-
const on = raw.on;
|
|
1208
|
-
const hash = stableHash({ on, tools: tools ?? null, spec });
|
|
1209
|
-
const id = typeof raw.id === "string" && raw.id.length > 0 ? raw.id : `${source.sourceId ?? source.origin}.${spec.kind}.${hash}`;
|
|
1210
|
-
const hook = { id, source, hash, on, enabled, spec };
|
|
1211
|
-
if (tools !== void 0) hook.tools = tools;
|
|
1212
|
-
return { hook, issues };
|
|
1213
|
-
}
|
|
1214
|
-
function declarationsArray(declarations) {
|
|
1215
|
-
if (Array.isArray(declarations)) return declarations;
|
|
1216
|
-
if (isRecord2(declarations) && Array.isArray(declarations.hooks)) return declarations.hooks;
|
|
1217
|
-
return [];
|
|
1218
|
-
}
|
|
1219
|
-
function loadUserHooks(batches, options) {
|
|
1220
|
-
const ordered = [...batches].sort(
|
|
1221
|
-
(a, b) => USER_HOOK_ORIGIN_ORDER.indexOf(a.source.origin) - USER_HOOK_ORIGIN_ORDER.indexOf(b.source.origin)
|
|
1222
|
-
);
|
|
1223
|
-
const byId = /* @__PURE__ */ new Map();
|
|
1224
|
-
const overridden = [];
|
|
1225
|
-
const issues = [];
|
|
1226
|
-
for (const batch of ordered) {
|
|
1227
|
-
const declarations = declarationsArray(batch.declarations);
|
|
1228
|
-
declarations.forEach((raw, index) => {
|
|
1229
|
-
const result = normalizeUserHook(raw, batch.source, options);
|
|
1230
|
-
if (!result.hook) {
|
|
1231
|
-
issues.push({ source: batch.source, index, issues: result.issues });
|
|
1232
|
-
return;
|
|
1297
|
+
return match;
|
|
1298
|
+
};
|
|
1299
|
+
const installGated = (entry, scope) => {
|
|
1300
|
+
assertPromotionInstallSource(entry);
|
|
1301
|
+
const result = deps.installEntry(entry, scope);
|
|
1302
|
+
installedNamesCache?.add(entry.name);
|
|
1303
|
+
return result;
|
|
1304
|
+
};
|
|
1305
|
+
const evaluateAskUserAnswers = (input) => {
|
|
1306
|
+
const offer = pendingOffer;
|
|
1307
|
+
if (!offer) return NO_EFFECTS2;
|
|
1308
|
+
const marker = offerBindingTag(offer.tag);
|
|
1309
|
+
if (input.toolResultDetails?.cancelled === true) {
|
|
1310
|
+
return NO_EFFECTS2;
|
|
1311
|
+
}
|
|
1312
|
+
for (const answer of answersFromDetails(input.toolResultDetails)) {
|
|
1313
|
+
if (!answer.question.includes(marker)) continue;
|
|
1314
|
+
const option = chosenOfferOption(answer);
|
|
1315
|
+
if (option === null) continue;
|
|
1316
|
+
pendingOffer = null;
|
|
1317
|
+
const offerKey = declineKey(offer.entry.name, offer.entry.version);
|
|
1318
|
+
if (option === SKILL_INSTALL_OFFER_OPTION_NOT_NOW) {
|
|
1319
|
+
sessionDeclines.add(offerKey);
|
|
1320
|
+
return NO_EFFECTS2;
|
|
1233
1321
|
}
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
}
|
|
1244
|
-
function commandEffect(hook, output) {
|
|
1245
|
-
const message = output.slice(0, USER_HOOK_COMMAND_OUTPUT_MAX_CHARS);
|
|
1246
|
-
return hook.as === "reminder" ? { kind: "inject_reminder", message } : { kind: "annotate_tool_result", message };
|
|
1247
|
-
}
|
|
1248
|
-
function userHookToRegistration(hook, deps) {
|
|
1249
|
-
const now = deps.now ?? Date.now;
|
|
1250
|
-
const baseReceipt = (input) => ({
|
|
1251
|
-
at: now(),
|
|
1252
|
-
hookId: hook.id,
|
|
1253
|
-
origin: hook.source.origin,
|
|
1254
|
-
sourcePath: hook.source.sourcePath,
|
|
1255
|
-
hash: hook.hash,
|
|
1256
|
-
hook: hook.on,
|
|
1257
|
-
kind: hook.spec.kind,
|
|
1258
|
-
...input.toolName !== void 0 ? { toolName: input.toolName } : {}
|
|
1259
|
-
});
|
|
1260
|
-
const registration = {
|
|
1261
|
-
id: hook.id,
|
|
1262
|
-
description: `${hook.spec.kind} hook from ${hook.source.origin} (${hook.source.sourcePath})`,
|
|
1263
|
-
hooks: [hook.on],
|
|
1264
|
-
evaluate: (input) => {
|
|
1265
|
-
if (!hook.enabled) return [];
|
|
1322
|
+
if (option === SKILL_INSTALL_OFFER_OPTION_NEVER) {
|
|
1323
|
+
sessionDeclines.add(offerKey);
|
|
1324
|
+
try {
|
|
1325
|
+
declines.recordNever(offer.entry.name, offer.entry.version);
|
|
1326
|
+
} catch {
|
|
1327
|
+
}
|
|
1328
|
+
return NO_EFFECTS2;
|
|
1329
|
+
}
|
|
1330
|
+
const scope = option === SKILL_INSTALL_OFFER_OPTION_USER ? "user" : "project";
|
|
1266
1331
|
try {
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1332
|
+
const result = installGated(offer.entry, scope);
|
|
1333
|
+
return [
|
|
1334
|
+
{
|
|
1335
|
+
kind: "annotate_tool_result",
|
|
1336
|
+
severity: "info",
|
|
1337
|
+
message: `[Marketplace] Installed skill "${offer.entry.name}" (${scope} scope) to ${result.path}. It is installed but not active; the operator activates it with /skill ${offer.entry.name}.`
|
|
1338
|
+
}
|
|
1339
|
+
];
|
|
1340
|
+
} catch (error) {
|
|
1341
|
+
sessionDeclines.add(offerKey);
|
|
1342
|
+
return [
|
|
1343
|
+
{
|
|
1344
|
+
kind: "annotate_tool_result",
|
|
1345
|
+
severity: "warn",
|
|
1346
|
+
message: `[Marketplace] Install of "${offer.entry.name}" failed: ${error instanceof Error ? error.message : String(error)}. Continue the task without it.`
|
|
1347
|
+
}
|
|
1348
|
+
];
|
|
1271
1349
|
}
|
|
1272
1350
|
}
|
|
1351
|
+
return NO_EFFECTS2;
|
|
1352
|
+
};
|
|
1353
|
+
return {
|
|
1354
|
+
id: MARKETPLACE_OFFER_REGISTRATION_ID,
|
|
1355
|
+
description: "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",
|
|
1356
|
+
hooks: ["turn_start", "after_tool"],
|
|
1357
|
+
evaluate(input) {
|
|
1358
|
+
trackSession(input);
|
|
1359
|
+
if (input.hook === "after_tool") {
|
|
1360
|
+
return input.toolName === ToolNames.AskUser ? evaluateAskUserAnswers(input) : NO_EFFECTS2;
|
|
1361
|
+
}
|
|
1362
|
+
if (input.hook !== "turn_start") return NO_EFFECTS2;
|
|
1363
|
+
if (!isSubstantiveUserTurn(input.text)) return NO_EFFECTS2;
|
|
1364
|
+
const pendingSkillRequests = metadataNumber(input, "pendingSkillRequests");
|
|
1365
|
+
if (pendingSkillRequests !== null && pendingSkillRequests > 0) return NO_EFFECTS2;
|
|
1366
|
+
const match = bestMatch(input.text ?? "");
|
|
1367
|
+
if (!match) return NO_EFFECTS2;
|
|
1368
|
+
offeredNames.add(match.entry.name);
|
|
1369
|
+
const tag = newOfferTag();
|
|
1370
|
+
pendingOffer = { entry: match.entry, tag };
|
|
1371
|
+
return [{ kind: "inject_reminder", severity: "info", message: marketplaceOfferReminder(match.entry, tag) }];
|
|
1372
|
+
}
|
|
1273
1373
|
};
|
|
1274
|
-
if (hook.tools !== void 0) registration.toolNames = [...hook.tools];
|
|
1275
|
-
return registration;
|
|
1276
|
-
}
|
|
1277
|
-
function evaluateHook(hook, deps, base) {
|
|
1278
|
-
if (hook.spec.kind === "prompt") {
|
|
1279
|
-
const effect2 = reminderEffect(hook.spec.message, hook.spec.severity);
|
|
1280
|
-
deps.recordReceipt({ ...base, outcome: "emitted", effectKinds: ["inject_reminder"] });
|
|
1281
|
-
return [effect2];
|
|
1282
|
-
}
|
|
1283
|
-
if (hook.spec.kind === "effect") {
|
|
1284
|
-
deps.recordReceipt({ ...base, outcome: "emitted", effectKinds: [hook.spec.effect.kind] });
|
|
1285
|
-
return [hook.spec.effect];
|
|
1286
|
-
}
|
|
1287
|
-
const result = deps.runCommand(hook.spec.argv, {
|
|
1288
|
-
timeoutMs: hook.spec.timeoutMs,
|
|
1289
|
-
...hook.spec.cwd !== void 0 ? { cwd: hook.spec.cwd } : {}
|
|
1290
|
-
});
|
|
1291
|
-
const output = (result.stdout || result.stderr || "").trim();
|
|
1292
|
-
if (result.timedOut) {
|
|
1293
|
-
deps.recordReceipt({ ...base, outcome: "command-timeout", exitCode: result.code ?? -1, outputChars: output.length });
|
|
1294
|
-
return [];
|
|
1295
|
-
}
|
|
1296
|
-
const outcome = result.code === 0 ? "command-ok" : "command-failed";
|
|
1297
|
-
deps.recordReceipt({ ...base, outcome, exitCode: result.code ?? -1, outputChars: output.length });
|
|
1298
|
-
if (output.length === 0) return [];
|
|
1299
|
-
const effect = commandEffect(hook.spec, output);
|
|
1300
|
-
return [effect];
|
|
1301
1374
|
}
|
|
1302
1375
|
|
|
1303
|
-
// src/domains/middleware/
|
|
1304
|
-
var COMMAND_OUTPUT_MAX_BYTES = 1024 * 1024;
|
|
1305
|
-
function readBatch(source, filePath, fileIssues) {
|
|
1306
|
-
let text;
|
|
1307
|
-
try {
|
|
1308
|
-
text = readFileSync(filePath, "utf8");
|
|
1309
|
-
} catch {
|
|
1310
|
-
return null;
|
|
1311
|
-
}
|
|
1312
|
-
try {
|
|
1313
|
-
const parsed = (0, import_yaml.parse)(text);
|
|
1314
|
-
return { source, declarations: parsed ?? [] };
|
|
1315
|
-
} catch (err) {
|
|
1316
|
-
fileIssues.push({
|
|
1317
|
-
source,
|
|
1318
|
-
message: `failed to parse ${filePath}: ${err instanceof Error ? err.message : String(err)}`
|
|
1319
|
-
});
|
|
1320
|
-
return null;
|
|
1321
|
-
}
|
|
1322
|
-
}
|
|
1323
|
-
function readHookSources(options) {
|
|
1324
|
-
const fileIssues = [];
|
|
1325
|
-
const batches = [];
|
|
1326
|
-
for (const extension of options.extensions ?? []) {
|
|
1327
|
-
const source = {
|
|
1328
|
-
origin: "extension",
|
|
1329
|
-
sourcePath: `${extension.id}:hooks.yaml`,
|
|
1330
|
-
sourceId: extension.id
|
|
1331
|
-
};
|
|
1332
|
-
const batch = readBatch(source, join(extension.rootPath, "hooks.yaml"), fileIssues);
|
|
1333
|
-
if (batch) batches.push(batch);
|
|
1334
|
-
}
|
|
1335
|
-
const projectBatch = readBatch(
|
|
1336
|
-
{ origin: "project", sourcePath: ".clio-coder/hooks.yaml" },
|
|
1337
|
-
join(options.cwd, ".clio-coder", "hooks.yaml"),
|
|
1338
|
-
fileIssues
|
|
1339
|
-
);
|
|
1340
|
-
if (projectBatch) batches.push(projectBatch);
|
|
1341
|
-
const localBatch = readBatch(
|
|
1342
|
-
{ origin: "project.local", sourcePath: ".clio-coder/hooks.local.yaml" },
|
|
1343
|
-
join(options.cwd, ".clio-coder", "hooks.local.yaml"),
|
|
1344
|
-
fileIssues
|
|
1345
|
-
);
|
|
1346
|
-
if (localBatch) batches.push(localBatch);
|
|
1347
|
-
return { batches, fileIssues };
|
|
1348
|
-
}
|
|
1349
|
-
function installUserHooks(options) {
|
|
1350
|
-
const workspaceRoot = options.workspaceRoot ?? options.cwd;
|
|
1351
|
-
const readOptions = { cwd: options.cwd };
|
|
1352
|
-
if (options.extensions !== void 0) readOptions.extensions = options.extensions;
|
|
1353
|
-
const { batches, fileIssues } = readHookSources(readOptions);
|
|
1354
|
-
const loaded = loadUserHooks(batches, { workspaceRoot });
|
|
1355
|
-
const runCommand = options.runCommand ?? spawnSyncCommandRunner();
|
|
1356
|
-
for (const hook of loaded.hooks) {
|
|
1357
|
-
options.registerHook(
|
|
1358
|
-
userHookToRegistration(hook, {
|
|
1359
|
-
recordReceipt: options.recordReceipt,
|
|
1360
|
-
runCommand,
|
|
1361
|
-
...options.now !== void 0 ? { now: options.now } : {}
|
|
1362
|
-
})
|
|
1363
|
-
);
|
|
1364
|
-
}
|
|
1365
|
-
return { ...loaded, fileIssues };
|
|
1366
|
-
}
|
|
1367
|
-
function spawnSyncCommandRunner() {
|
|
1368
|
-
return (argv, options) => {
|
|
1369
|
-
const [command, ...args] = argv;
|
|
1370
|
-
const attribution = withManagedGitCommitAttributionEnvironment(process.env, {
|
|
1371
|
-
cwd: options.cwd ?? process.cwd(),
|
|
1372
|
-
evidence: {}
|
|
1373
|
-
});
|
|
1374
|
-
reportCommitAttributionDiagnostic(attribution.diagnostic);
|
|
1375
|
-
const result = spawnSync(command ?? "", args, {
|
|
1376
|
-
...options.cwd !== void 0 ? { cwd: options.cwd } : {},
|
|
1377
|
-
timeout: options.timeoutMs,
|
|
1378
|
-
maxBuffer: COMMAND_OUTPUT_MAX_BYTES,
|
|
1379
|
-
encoding: "utf8",
|
|
1380
|
-
shell: false,
|
|
1381
|
-
env: attribution.env
|
|
1382
|
-
});
|
|
1383
|
-
const timedOut = result.error !== void 0 && result.error.code === "ETIMEDOUT";
|
|
1384
|
-
return {
|
|
1385
|
-
code: result.status,
|
|
1386
|
-
timedOut,
|
|
1387
|
-
stdout: typeof result.stdout === "string" ? result.stdout : "",
|
|
1388
|
-
stderr: typeof result.stderr === "string" ? result.stderr : ""
|
|
1389
|
-
};
|
|
1390
|
-
};
|
|
1391
|
-
}
|
|
1392
|
-
|
|
1393
|
-
// src/domains/middleware/skills-reminder.ts
|
|
1376
|
+
// src/domains/middleware/memory-intervention.ts
|
|
1394
1377
|
init_esm_shims();
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
var
|
|
1401
|
-
var
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
"
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
"
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
"nice",
|
|
1433
|
-
"test",
|
|
1434
|
-
"testing",
|
|
1435
|
-
"ping",
|
|
1436
|
-
"pong"
|
|
1437
|
-
]);
|
|
1438
|
-
function isSubstantiveUserTurn(text) {
|
|
1439
|
-
const normalized = (text ?? "").toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, " ").replace(/\s+/g, " ").trim();
|
|
1440
|
-
if (normalized.length === 0) return false;
|
|
1441
|
-
const tokens = normalized.split(" ");
|
|
1442
|
-
if (tokens.length > 4) return true;
|
|
1443
|
-
return tokens.some((token) => !GREETING_WORDS.has(token));
|
|
1444
|
-
}
|
|
1445
|
-
function createSkillsReminderRegistration(deps) {
|
|
1446
|
-
let lastSeenSessionId;
|
|
1447
|
-
let spentForActiveSession = false;
|
|
1448
|
-
let observedFreshStart = false;
|
|
1449
|
-
let observedAnyTurn = false;
|
|
1378
|
+
import { createHash } from "node:crypto";
|
|
1379
|
+
var MEMORY_INTERVENTION_REGISTRATION_ID = "observer.memory-intervention";
|
|
1380
|
+
var MEMORY_INTERVENTION_DEFAULT_WINDOW_STEPS = 8;
|
|
1381
|
+
var MEMORY_INTERVENTION_DEFAULT_MAX_TOKENS = 2e3;
|
|
1382
|
+
var MEMORY_INTERVENTION_DEFAULT_EVERY_N_TOOLS = 10;
|
|
1383
|
+
var MEMORY_INTERVENTION_TIMEOUT_BACKOFF_THRESHOLD = 2;
|
|
1384
|
+
var MEMORY_INTERVENTION_ACTIVITY_LIMIT = 20;
|
|
1385
|
+
var CALL_DESCRIPTION_MAX_CHARS = 180;
|
|
1386
|
+
var NO_EFFECTS3 = [];
|
|
1387
|
+
function createMemoryInterventionRegistration(deps) {
|
|
1388
|
+
let generation = 0;
|
|
1389
|
+
let generationController = new AbortController();
|
|
1390
|
+
let observedSessionId;
|
|
1391
|
+
let disposed = false;
|
|
1392
|
+
const pending = /* @__PURE__ */ new Map();
|
|
1393
|
+
const trajectory = [];
|
|
1394
|
+
const failures = /* @__PURE__ */ new Map();
|
|
1395
|
+
let toolStep = 0;
|
|
1396
|
+
let lastTurnEndStep = 0;
|
|
1397
|
+
let reactivateAfterCompaction = false;
|
|
1398
|
+
let lastInjectedOperationFingerprint = null;
|
|
1399
|
+
let currentTask = "(current task unavailable)";
|
|
1400
|
+
let toolsSinceMemoryStep = 0;
|
|
1401
|
+
let consecutiveErrors = 0;
|
|
1402
|
+
let lastPromptedBoundary = null;
|
|
1403
|
+
let lastInjectedMessage = null;
|
|
1404
|
+
let lastDecision = null;
|
|
1405
|
+
const pendingTriggers = /* @__PURE__ */ new Set();
|
|
1406
|
+
const activity = [];
|
|
1407
|
+
const annotatedThisTurn = /* @__PURE__ */ new Set();
|
|
1408
|
+
let telemetryBankSnapshot = deps.bank.snapshot();
|
|
1409
|
+
let promptedStepInFlight = false;
|
|
1410
|
+
let promptedStepTier = "rules";
|
|
1411
|
+
let outstandingStep = Promise.resolve();
|
|
1412
|
+
let rulesInjectedSincePromptedStep = false;
|
|
1413
|
+
let annotatedSinceTurnEnd = false;
|
|
1414
|
+
let consecutiveLlmTimeouts = 0;
|
|
1450
1415
|
return {
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1416
|
+
reset,
|
|
1417
|
+
dispose() {
|
|
1418
|
+
disposed = true;
|
|
1419
|
+
reset();
|
|
1420
|
+
},
|
|
1421
|
+
id: MEMORY_INTERVENTION_REGISTRATION_ID,
|
|
1422
|
+
description: "maintain bounded task execution memory and selectively remind after repeated failures or compaction",
|
|
1423
|
+
hooks: ["before_tool", "after_tool", "turn_start", "turn_end", "on_compaction"],
|
|
1454
1424
|
evaluate(input) {
|
|
1455
|
-
|
|
1456
|
-
|
|
1425
|
+
observeSession(input);
|
|
1426
|
+
if (disposed || !settings().enabled) return NO_EFFECTS3;
|
|
1427
|
+
try {
|
|
1428
|
+
switch (input.hook) {
|
|
1429
|
+
case "before_tool":
|
|
1430
|
+
observeBeforeTool(input);
|
|
1431
|
+
return NO_EFFECTS3;
|
|
1432
|
+
case "after_tool":
|
|
1433
|
+
return observeAfterTool(input);
|
|
1434
|
+
case "turn_end": {
|
|
1435
|
+
if (toolStep <= lastTurnEndStep) return NO_EFFECTS3;
|
|
1436
|
+
const alreadyReported = annotatedSinceTurnEnd;
|
|
1437
|
+
annotatedSinceTurnEnd = false;
|
|
1438
|
+
const started = process.hrtime.bigint();
|
|
1439
|
+
const effects = decideRepeatedFailure();
|
|
1440
|
+
if (alreadyReported && effects.length === 0) return effects;
|
|
1441
|
+
emitTelemetry(
|
|
1442
|
+
[effects.length > 0 ? "repeated_failure" : "turn_end"],
|
|
1443
|
+
"rules",
|
|
1444
|
+
effects.length > 0 ? "injected" : "silent",
|
|
1445
|
+
effects.length > 0 ? "intervened" : "no_repeated_failure",
|
|
1446
|
+
citedEntryCount(effects[0]?.kind === "inject_reminder" ? effects[0].message : null),
|
|
1447
|
+
0,
|
|
1448
|
+
0,
|
|
1449
|
+
started
|
|
1450
|
+
);
|
|
1451
|
+
return effects;
|
|
1452
|
+
}
|
|
1453
|
+
case "on_compaction":
|
|
1454
|
+
if (input.metadata?.stage === "working_set_recall") return NO_EFFECTS3;
|
|
1455
|
+
reactivateAfterCompaction = true;
|
|
1456
|
+
return NO_EFFECTS3;
|
|
1457
|
+
case "turn_start": {
|
|
1458
|
+
if (input.text?.trim()) currentTask = shortText(input.text, 2e3);
|
|
1459
|
+
annotatedThisTurn.clear();
|
|
1460
|
+
const shouldReactivate = reactivateAfterCompaction;
|
|
1461
|
+
const started = process.hrtime.bigint();
|
|
1462
|
+
const effects = reactivateKnowledge();
|
|
1463
|
+
if (shouldReactivate) {
|
|
1464
|
+
emitTelemetry(
|
|
1465
|
+
["post_compaction"],
|
|
1466
|
+
"rules",
|
|
1467
|
+
effects.length > 0 ? "injected" : "silent",
|
|
1468
|
+
effects.length > 0 ? "intervened" : "bank_empty",
|
|
1469
|
+
citedEntryCount(effects[0]?.kind === "inject_reminder" ? effects[0].message : null),
|
|
1470
|
+
0,
|
|
1471
|
+
0,
|
|
1472
|
+
started
|
|
1473
|
+
);
|
|
1474
|
+
}
|
|
1475
|
+
return effects;
|
|
1476
|
+
}
|
|
1477
|
+
}
|
|
1478
|
+
} catch {
|
|
1479
|
+
return NO_EFFECTS3;
|
|
1457
1480
|
}
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1481
|
+
},
|
|
1482
|
+
/**
|
|
1483
|
+
* Kicks off the background memory step and returns immediately. The pi
|
|
1484
|
+
* agent does not go idle until every `agent_end` listener settles, so
|
|
1485
|
+
* awaiting a local memory model here would stall the visible turn for its
|
|
1486
|
+
* full latency. Small local models measure in tens of seconds, which is far
|
|
1487
|
+
* past any tolerable end-of-turn pause and past the policy timeout itself.
|
|
1488
|
+
* The reminder is delivered through `onDeferredReminder` instead, landing
|
|
1489
|
+
* in the next submitted turn, which is where an awaited turn_end reminder
|
|
1490
|
+
* would have been buffered anyway.
|
|
1491
|
+
*/
|
|
1492
|
+
async evaluateAsync(input, context) {
|
|
1493
|
+
observeSession(input);
|
|
1494
|
+
if (disposed || !settings().enabled || input.hook !== "turn_end" || pendingTriggers.size === 0) return NO_EFFECTS3;
|
|
1495
|
+
const boundary = input.turnId ?? input.metadata?.userTurnId?.toString() ?? `tool-step:${toolStep}`;
|
|
1496
|
+
if (boundary === lastPromptedBoundary) return NO_EFFECTS3;
|
|
1497
|
+
if (deps.deliversDeferredReminders === false) {
|
|
1498
|
+
emitTelemetry([...pendingTriggers], "rules", "silent", "no_consumer", 0, 0, 0, process.hrtime.bigint());
|
|
1499
|
+
pendingTriggers.clear();
|
|
1500
|
+
return NO_EFFECTS3;
|
|
1468
1501
|
}
|
|
1469
|
-
if (
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1502
|
+
if (promptedStepInFlight) {
|
|
1503
|
+
emitTelemetry(
|
|
1504
|
+
[...pendingTriggers],
|
|
1505
|
+
promptedStepTier,
|
|
1506
|
+
"dropped",
|
|
1507
|
+
"step_in_flight",
|
|
1508
|
+
0,
|
|
1509
|
+
0,
|
|
1510
|
+
0,
|
|
1511
|
+
process.hrtime.bigint()
|
|
1512
|
+
);
|
|
1513
|
+
return NO_EFFECTS3;
|
|
1478
1514
|
}
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1515
|
+
lastPromptedBoundary = boundary;
|
|
1516
|
+
const triggers = [...pendingTriggers];
|
|
1517
|
+
pendingTriggers.clear();
|
|
1518
|
+
toolsSinceMemoryStep = 0;
|
|
1519
|
+
consecutiveErrors = 0;
|
|
1520
|
+
const rulesAlreadySpoke = rulesInjectedSincePromptedStep || (context?.priorEffects.some(
|
|
1521
|
+
(effect) => effect.kind === "inject_reminder" && effect.message.startsWith("Memory:")
|
|
1522
|
+
) ?? false);
|
|
1523
|
+
rulesInjectedSincePromptedStep = false;
|
|
1524
|
+
const stepGeneration = generation;
|
|
1525
|
+
promptedStepInFlight = true;
|
|
1526
|
+
outstandingStep = runPromptedStep({
|
|
1527
|
+
deterministicTrigger: triggers.some((trigger) => trigger !== "interval"),
|
|
1528
|
+
suppressIntervention: rulesAlreadySpoke,
|
|
1529
|
+
triggerReasons: triggers
|
|
1530
|
+
}).then((result) => {
|
|
1531
|
+
if (stepGeneration !== generation || disposed) return;
|
|
1532
|
+
if (rulesAlreadySpoke && result.decision === "silent") lastDecision = "injected";
|
|
1533
|
+
if (result.reminder === null) return;
|
|
1534
|
+
lastInjectedMessage = result.reminder;
|
|
1535
|
+
deps.onDeferredReminder?.(result.reminder);
|
|
1536
|
+
}).catch(() => {
|
|
1537
|
+
}).finally(() => {
|
|
1538
|
+
if (stepGeneration === generation) promptedStepInFlight = false;
|
|
1539
|
+
});
|
|
1540
|
+
return NO_EFFECTS3;
|
|
1541
|
+
},
|
|
1542
|
+
runPromptedStep,
|
|
1543
|
+
signalLoop() {
|
|
1544
|
+
if (!disposed && settings().enabled) pendingTriggers.add("loop_signal");
|
|
1545
|
+
},
|
|
1546
|
+
lastDecision: () => lastDecision,
|
|
1547
|
+
recentActivity: () => [...activity],
|
|
1548
|
+
stepInFlight: () => promptedStepInFlight,
|
|
1549
|
+
whenIdle: () => outstandingStep
|
|
1550
|
+
};
|
|
1551
|
+
function observeSession(input) {
|
|
1552
|
+
if (input.sessionId === void 0) return;
|
|
1553
|
+
if (observedSessionId !== void 0 && observedSessionId !== input.sessionId) reset();
|
|
1554
|
+
observedSessionId = input.sessionId;
|
|
1555
|
+
}
|
|
1556
|
+
function reset() {
|
|
1557
|
+
generation += 1;
|
|
1558
|
+
generationController.abort();
|
|
1559
|
+
generationController = new AbortController();
|
|
1560
|
+
deps.bank.clear();
|
|
1561
|
+
pending.clear();
|
|
1562
|
+
trajectory.length = 0;
|
|
1563
|
+
failures.clear();
|
|
1564
|
+
toolStep = 0;
|
|
1565
|
+
lastTurnEndStep = 0;
|
|
1566
|
+
reactivateAfterCompaction = false;
|
|
1567
|
+
lastInjectedOperationFingerprint = null;
|
|
1568
|
+
currentTask = "(current task unavailable)";
|
|
1569
|
+
toolsSinceMemoryStep = 0;
|
|
1570
|
+
consecutiveErrors = 0;
|
|
1571
|
+
lastPromptedBoundary = null;
|
|
1572
|
+
lastInjectedMessage = null;
|
|
1573
|
+
lastDecision = null;
|
|
1574
|
+
pendingTriggers.clear();
|
|
1575
|
+
activity.length = 0;
|
|
1576
|
+
annotatedThisTurn.clear();
|
|
1577
|
+
telemetryBankSnapshot = deps.bank.snapshot();
|
|
1578
|
+
promptedStepInFlight = false;
|
|
1579
|
+
promptedStepTier = "rules";
|
|
1580
|
+
outstandingStep = Promise.resolve();
|
|
1581
|
+
rulesInjectedSincePromptedStep = false;
|
|
1582
|
+
annotatedSinceTurnEnd = false;
|
|
1583
|
+
consecutiveLlmTimeouts = 0;
|
|
1584
|
+
observedSessionId = void 0;
|
|
1585
|
+
}
|
|
1586
|
+
function settings() {
|
|
1587
|
+
const live = deps.getSettings?.();
|
|
1588
|
+
return {
|
|
1589
|
+
enabled: live?.enabled ?? deps.enabled ?? true,
|
|
1590
|
+
everyNTools: Math.max(
|
|
1591
|
+
2,
|
|
1592
|
+
positiveInteger(live?.everyNTools ?? deps.everyNTools, MEMORY_INTERVENTION_DEFAULT_EVERY_N_TOOLS)
|
|
1593
|
+
),
|
|
1594
|
+
windowSteps: positiveInteger(live?.windowSteps ?? deps.windowSteps, MEMORY_INTERVENTION_DEFAULT_WINDOW_STEPS),
|
|
1595
|
+
maxTokens: positiveInteger(live?.maxTokens ?? deps.maxTokens, MEMORY_INTERVENTION_DEFAULT_MAX_TOKENS),
|
|
1596
|
+
timeoutMs: positiveInteger(live?.timeoutMs ?? deps.timeoutMs, TASK_MEMORY_POLICY_DEFAULT_TIMEOUT_MS)
|
|
1597
|
+
};
|
|
1598
|
+
}
|
|
1599
|
+
async function runPromptedStep(input) {
|
|
1600
|
+
const silent = (reason) => ({
|
|
1601
|
+
decision: "silent",
|
|
1602
|
+
reason,
|
|
1603
|
+
bankOperations: 0,
|
|
1604
|
+
droppedOperations: 0,
|
|
1605
|
+
reminder: null,
|
|
1606
|
+
inputTokens: 0,
|
|
1607
|
+
outputTokens: 0,
|
|
1608
|
+
usage: null,
|
|
1609
|
+
effects: NO_EFFECTS3
|
|
1610
|
+
});
|
|
1611
|
+
const live = settings();
|
|
1612
|
+
const stepGeneration = generation;
|
|
1613
|
+
const isCurrent = () => !disposed && stepGeneration === generation;
|
|
1614
|
+
const usageSink = deps.captureStepUsage?.();
|
|
1615
|
+
if (disposed || !live.enabled) {
|
|
1616
|
+
const result = silent("no_client");
|
|
1617
|
+
lastDecision = result.decision;
|
|
1618
|
+
return result;
|
|
1619
|
+
}
|
|
1620
|
+
const started = process.hrtime.bigint();
|
|
1621
|
+
let attemptStarted = started;
|
|
1622
|
+
const triggers = input.triggerReasons?.length ? input.triggerReasons : ["manual"];
|
|
1623
|
+
let tier = "rules";
|
|
1624
|
+
promptedStepTier = tier;
|
|
1625
|
+
let promptedResult;
|
|
1626
|
+
let telemetryDecision = null;
|
|
1627
|
+
try {
|
|
1628
|
+
if (consecutiveLlmTimeouts >= MEMORY_INTERVENTION_TIMEOUT_BACKOFF_THRESHOLD) {
|
|
1629
|
+
promptedResult = silent("llm_timeout_backoff");
|
|
1630
|
+
} else {
|
|
1631
|
+
const client = deps.getModelClient?.() ?? null;
|
|
1632
|
+
if (client === null) {
|
|
1633
|
+
promptedResult = silent("no_client");
|
|
1634
|
+
} else if (deps.backgroundEndpointBusy?.() === true) {
|
|
1635
|
+
tier = "llm";
|
|
1636
|
+
promptedStepTier = tier;
|
|
1637
|
+
promptedResult = silent("endpoint_busy");
|
|
1638
|
+
telemetryDecision = "dropped";
|
|
1639
|
+
} else {
|
|
1640
|
+
tier = "llm";
|
|
1641
|
+
promptedStepTier = tier;
|
|
1642
|
+
const runClient = (selected, timeoutMs) => runTaskMemoryPolicy(deps.bank, selected, {
|
|
1643
|
+
isCurrent,
|
|
1644
|
+
signal: generationController.signal,
|
|
1645
|
+
onStepUsage: (usage) => {
|
|
1646
|
+
if (usageSink) usageSink(usage, isCurrent());
|
|
1647
|
+
else deps.onStepUsage?.(usage);
|
|
1648
|
+
},
|
|
1649
|
+
task: input.task?.trim() || currentTask,
|
|
1650
|
+
trajectory: [...trajectory],
|
|
1651
|
+
deterministicTrigger: input.deterministicTrigger,
|
|
1652
|
+
maxTokens: live.maxTokens,
|
|
1653
|
+
modelMaxTokens: positiveInteger(deps.getModelMaxTokens?.(live.maxTokens), live.maxTokens),
|
|
1654
|
+
...input.suppressIntervention === void 0 ? {} : { suppressIntervention: input.suppressIntervention },
|
|
1655
|
+
previousReminder: lastInjectedMessage,
|
|
1656
|
+
timeoutMs,
|
|
1657
|
+
...deps.onEnvelope === void 0 ? {} : { onEnvelope: deps.onEnvelope }
|
|
1658
|
+
});
|
|
1659
|
+
const remainingMs = () => Math.floor(live.timeoutMs - Number(process.hrtime.bigint() - started) / 1e6);
|
|
1660
|
+
const initialTimeoutMs = remainingMs();
|
|
1661
|
+
promptedResult = initialTimeoutMs <= 0 ? { ...silent("deadline"), decision: "timeout" } : await runClient(client, initialTimeoutMs);
|
|
1662
|
+
if (promptedResult.reason === "client_error" && isCurrent() && remainingMs() > 0) {
|
|
1663
|
+
let fallback = null;
|
|
1664
|
+
let fallbackReady = false;
|
|
1665
|
+
try {
|
|
1666
|
+
fallback = deps.getFallbackModelClient?.() ?? null;
|
|
1667
|
+
fallbackReady = fallback !== null && deps.backgroundEndpointBusy?.() !== true;
|
|
1668
|
+
} catch {
|
|
1669
|
+
}
|
|
1670
|
+
const fallbackTimeoutMs = remainingMs();
|
|
1671
|
+
if (fallback !== null && fallbackReady && isCurrent() && fallbackTimeoutMs > 0) {
|
|
1672
|
+
emitTelemetry(
|
|
1673
|
+
triggers,
|
|
1674
|
+
tier,
|
|
1675
|
+
promptedResult.decision,
|
|
1676
|
+
promptedResult.reason,
|
|
1677
|
+
0,
|
|
1678
|
+
promptedResult.inputTokens,
|
|
1679
|
+
promptedResult.outputTokens,
|
|
1680
|
+
started,
|
|
1681
|
+
{ bankOperations: promptedResult.bankOperations, droppedOperations: promptedResult.droppedOperations }
|
|
1682
|
+
);
|
|
1683
|
+
attemptStarted = process.hrtime.bigint();
|
|
1684
|
+
promptedResult = await runClient(fallback, fallbackTimeoutMs);
|
|
1685
|
+
}
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1482
1688
|
}
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1689
|
+
} catch (error) {
|
|
1690
|
+
promptedResult = silent("client_error");
|
|
1691
|
+
deps.onEnvelope?.({
|
|
1692
|
+
systemPrompt: "",
|
|
1693
|
+
userPrompt: "",
|
|
1694
|
+
response: "",
|
|
1695
|
+
decision: "silent",
|
|
1696
|
+
reason: "client_error",
|
|
1697
|
+
bankOperations: 0,
|
|
1698
|
+
droppedOperations: 0,
|
|
1699
|
+
reminder: null,
|
|
1700
|
+
error: error instanceof Error ? error.message : "resolving the background model client failed"
|
|
1701
|
+
});
|
|
1702
|
+
}
|
|
1703
|
+
if (!isCurrent()) return { ...promptedResult, reminder: null, effects: NO_EFFECTS3 };
|
|
1704
|
+
if (promptedResult.reason === "endpoint_busy") telemetryDecision = "dropped";
|
|
1705
|
+
if (tier === "llm" && promptedResult.decision === "timeout") consecutiveLlmTimeouts += 1;
|
|
1706
|
+
else if (tier === "llm" && telemetryDecision !== "dropped") consecutiveLlmTimeouts = 0;
|
|
1707
|
+
lastDecision = promptedResult.decision;
|
|
1708
|
+
if (promptedResult.decision === "injected") reportInjectedEntries(promptedResult.reminder);
|
|
1709
|
+
emitTelemetry(
|
|
1710
|
+
triggers,
|
|
1711
|
+
tier,
|
|
1712
|
+
telemetryDecision ?? promptedResult.decision,
|
|
1713
|
+
promptedResult.reason,
|
|
1714
|
+
citedEntryCount(promptedResult.reminder),
|
|
1715
|
+
promptedResult.inputTokens,
|
|
1716
|
+
promptedResult.outputTokens,
|
|
1717
|
+
attemptStarted,
|
|
1718
|
+
{ bankOperations: promptedResult.bankOperations, droppedOperations: promptedResult.droppedOperations }
|
|
1719
|
+
);
|
|
1720
|
+
return {
|
|
1721
|
+
...promptedResult,
|
|
1722
|
+
effects: promptedResult.reminder === null ? NO_EFFECTS3 : [{ kind: "inject_reminder", message: promptedResult.reminder, severity: "advisory" }]
|
|
1723
|
+
};
|
|
1724
|
+
}
|
|
1725
|
+
function emitTelemetry(triggerReasons, tier, decision, reason, citedEntries2, inputTokens, outputTokens, started, operations = { bankOperations: 0, droppedOperations: 0 }) {
|
|
1726
|
+
const next = deps.bank.snapshot();
|
|
1727
|
+
const bankDelta = taskMemoryBankDelta(telemetryBankSnapshot, next);
|
|
1728
|
+
telemetryBankSnapshot = next;
|
|
1729
|
+
const latencyMs = Number(process.hrtime.bigint() - started) / 1e6;
|
|
1730
|
+
try {
|
|
1731
|
+
deps.telemetry?.record({
|
|
1732
|
+
triggerReasons,
|
|
1733
|
+
tier,
|
|
1734
|
+
bankDelta,
|
|
1735
|
+
decision,
|
|
1736
|
+
reason,
|
|
1737
|
+
bankOperations: operations.bankOperations,
|
|
1738
|
+
droppedOperations: operations.droppedOperations,
|
|
1739
|
+
citedEntries: citedEntries2,
|
|
1740
|
+
inputTokens,
|
|
1741
|
+
outputTokens,
|
|
1742
|
+
latencyMs
|
|
1743
|
+
});
|
|
1744
|
+
} catch {
|
|
1745
|
+
}
|
|
1746
|
+
const event = {
|
|
1747
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1748
|
+
triggerReasons: [...new Set(triggerReasons)],
|
|
1749
|
+
tier,
|
|
1750
|
+
decision,
|
|
1751
|
+
reason,
|
|
1752
|
+
citedEntries: citedEntries2,
|
|
1753
|
+
bankWrites: countBankWrites(bankDelta),
|
|
1754
|
+
latencyMs
|
|
1755
|
+
};
|
|
1756
|
+
activity.unshift(event);
|
|
1757
|
+
if (activity.length > MEMORY_INTERVENTION_ACTIVITY_LIMIT) activity.length = MEMORY_INTERVENTION_ACTIVITY_LIMIT;
|
|
1758
|
+
}
|
|
1759
|
+
function citedEntries(message) {
|
|
1760
|
+
if (message === null) return [];
|
|
1761
|
+
const snapshot = deps.bank.snapshot();
|
|
1762
|
+
return [...snapshot.knowledge, ...snapshot.procedural].filter((entry) => message.includes(`[${entry.id}]`));
|
|
1763
|
+
}
|
|
1764
|
+
function citedEntryCount(message) {
|
|
1765
|
+
return citedEntries(message).length;
|
|
1766
|
+
}
|
|
1767
|
+
function reportInjectedEntries(reminder) {
|
|
1768
|
+
if (deps.onInjectedEntries === void 0) return;
|
|
1769
|
+
const entries = citedEntries(reminder);
|
|
1770
|
+
if (entries.length === 0) return;
|
|
1771
|
+
try {
|
|
1772
|
+
deps.onInjectedEntries(entries);
|
|
1773
|
+
} catch {
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
function observeBeforeTool(input) {
|
|
1777
|
+
const prepared = prepareToolStep(input);
|
|
1778
|
+
if (prepared === null) return;
|
|
1779
|
+
setBounded(pending, pendingKey(input, prepared.operationFingerprint), prepared, settings().windowSteps);
|
|
1780
|
+
}
|
|
1781
|
+
function observeAfterTool(input) {
|
|
1782
|
+
const fallback = prepareToolStep(input);
|
|
1783
|
+
if (fallback === null) return NO_EFFECTS3;
|
|
1784
|
+
const key = pendingKey(input, fallback.operationFingerprint);
|
|
1785
|
+
const prepared = pending.get(key) ?? fallback;
|
|
1786
|
+
pending.delete(key);
|
|
1787
|
+
const outcome = input.metadata?.resultKind === "error" ? "error" : "ok";
|
|
1788
|
+
toolStep += 1;
|
|
1789
|
+
toolsSinceMemoryStep += 1;
|
|
1790
|
+
const live = settings();
|
|
1791
|
+
if (toolsSinceMemoryStep >= live.everyNTools) pendingTriggers.add("interval");
|
|
1792
|
+
if (outcome === "error") {
|
|
1793
|
+
consecutiveErrors += 1;
|
|
1794
|
+
if (consecutiveErrors >= 2) pendingTriggers.add("tool_error_streak");
|
|
1795
|
+
} else {
|
|
1796
|
+
consecutiveErrors = 0;
|
|
1797
|
+
}
|
|
1798
|
+
const digest = resultDigest(input, outcome);
|
|
1799
|
+
const step = {
|
|
1800
|
+
...prepared,
|
|
1801
|
+
step: toolStep,
|
|
1802
|
+
outcome,
|
|
1803
|
+
resultDigest: digest.text,
|
|
1804
|
+
resultDigestProvenance: digest.provenance
|
|
1805
|
+
};
|
|
1806
|
+
trajectory.push(step);
|
|
1807
|
+
if (trajectory.length > live.windowSteps) trajectory.splice(0, trajectory.length - live.windowSteps);
|
|
1808
|
+
if (outcome !== "error") return NO_EFFECTS3;
|
|
1809
|
+
rememberFailure(step);
|
|
1810
|
+
return annotateRepeatedFailure(step);
|
|
1811
|
+
}
|
|
1812
|
+
function annotateRepeatedFailure(step) {
|
|
1813
|
+
if (annotatedThisTurn.has(step.operationFingerprint)) return NO_EFFECTS3;
|
|
1814
|
+
const occurrences = trajectory.filter(
|
|
1815
|
+
(candidate) => candidate.outcome === "error" && candidate.operationFingerprint === step.operationFingerprint
|
|
1816
|
+
).length;
|
|
1817
|
+
const failure = failures.get(step.operationFingerprint);
|
|
1818
|
+
if (occurrences < 2 || failure === void 0) return NO_EFFECTS3;
|
|
1819
|
+
const message = boundedReminder(
|
|
1820
|
+
`Memory: [${failure.entryId}] you already tried ${failure.callDescription} at step ${failure.firstStep} and it failed with ${failure.errorDigest}. Change the approach rather than repeating it.`,
|
|
1821
|
+
settings().maxTokens
|
|
1822
|
+
);
|
|
1823
|
+
if (message.length === 0) return NO_EFFECTS3;
|
|
1824
|
+
annotatedThisTurn.add(step.operationFingerprint);
|
|
1825
|
+
lastInjectedOperationFingerprint = step.operationFingerprint;
|
|
1826
|
+
lastDecision = "injected";
|
|
1827
|
+
rulesInjectedSincePromptedStep = true;
|
|
1828
|
+
annotatedSinceTurnEnd = true;
|
|
1829
|
+
deps.bank.recordInjection([failure.entryId]);
|
|
1830
|
+
emitTelemetry(["repeated_failure"], "rules", "injected", "intervened", 1, 0, 0, process.hrtime.bigint());
|
|
1831
|
+
return [{ kind: "annotate_tool_result", message, severity: "warn" }];
|
|
1832
|
+
}
|
|
1833
|
+
function rememberFailure(step) {
|
|
1834
|
+
const previous = failures.get(step.operationFingerprint);
|
|
1835
|
+
const attempts = (previous?.attempts ?? 0) + 1;
|
|
1836
|
+
const firstStep = previous?.firstStep ?? step.step;
|
|
1837
|
+
const content = proceduralContent(step.callDescription, attempts, firstStep, step.resultDigest);
|
|
1838
|
+
let entryId;
|
|
1839
|
+
if (previous === void 0) {
|
|
1840
|
+
entryId = deps.bank.saveProcedural(content).id;
|
|
1841
|
+
} else {
|
|
1489
1842
|
try {
|
|
1490
|
-
|
|
1491
|
-
installable = deps.countInstallableSkills?.() ?? 0;
|
|
1843
|
+
entryId = deps.bank.saveProcedural(content, { id: previous.entryId }).id;
|
|
1492
1844
|
} catch {
|
|
1493
|
-
|
|
1845
|
+
entryId = deps.bank.saveProcedural(content).id;
|
|
1494
1846
|
}
|
|
1495
|
-
if (installed <= 0 && installable <= 0) return NO_EFFECTS;
|
|
1496
|
-
return [{ kind: "inject_reminder", severity: "info", message: skillsReminderMessage(installed, installable) }];
|
|
1497
1847
|
}
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
"want",
|
|
1540
|
-
"help",
|
|
1541
|
-
"make",
|
|
1542
|
-
"file",
|
|
1543
|
-
"files",
|
|
1544
|
-
"code"
|
|
1545
|
-
]);
|
|
1546
|
-
function normalize(text) {
|
|
1547
|
-
return text.toLowerCase().replace(/[^\p{L}\p{N}\s]/gu, " ").replace(/\s+/g, " ").trim();
|
|
1548
|
-
}
|
|
1549
|
-
function distinctiveTokens(text) {
|
|
1550
|
-
return new Set(
|
|
1551
|
-
normalize(text).split(" ").filter((token) => token.length >= 4 && !TOKEN_STOPWORDS.has(token))
|
|
1552
|
-
);
|
|
1553
|
-
}
|
|
1554
|
-
var TRIGGER_SCORE = 4;
|
|
1555
|
-
var TOKEN_FIRE_THRESHOLD = 3;
|
|
1556
|
-
function scorePromotionEntry(userText, entry) {
|
|
1557
|
-
const haystack = ` ${normalize(userText)} `;
|
|
1558
|
-
if (haystack.trim().length === 0) return null;
|
|
1559
|
-
for (const trigger of entry.triggers ?? []) {
|
|
1560
|
-
const phrase = normalize(trigger);
|
|
1561
|
-
if (phrase.length >= 4 && haystack.includes(` ${phrase} `)) {
|
|
1562
|
-
return { entry, score: TRIGGER_SCORE, matchedTrigger: trigger };
|
|
1848
|
+
setBounded(
|
|
1849
|
+
failures,
|
|
1850
|
+
step.operationFingerprint,
|
|
1851
|
+
{
|
|
1852
|
+
entryId,
|
|
1853
|
+
attempts,
|
|
1854
|
+
firstStep,
|
|
1855
|
+
callDescription: step.callDescription,
|
|
1856
|
+
errorDigest: step.resultDigest
|
|
1857
|
+
},
|
|
1858
|
+
TASK_MEMORY_DEFAULT_PROCEDURAL_CAP
|
|
1859
|
+
);
|
|
1860
|
+
}
|
|
1861
|
+
function decideRepeatedFailure() {
|
|
1862
|
+
try {
|
|
1863
|
+
for (let index = trajectory.length - 1; index >= 0; index -= 1) {
|
|
1864
|
+
const step = trajectory[index];
|
|
1865
|
+
if (step === void 0 || step.outcome !== "error" || step.step <= lastTurnEndStep || step.operationFingerprint === lastInjectedOperationFingerprint) {
|
|
1866
|
+
continue;
|
|
1867
|
+
}
|
|
1868
|
+
const occurrences = trajectory.filter(
|
|
1869
|
+
(candidate) => candidate.outcome === "error" && candidate.operationFingerprint === step.operationFingerprint
|
|
1870
|
+
).length;
|
|
1871
|
+
const failure = failures.get(step.operationFingerprint);
|
|
1872
|
+
if (occurrences < 2 || failure === void 0) continue;
|
|
1873
|
+
const message = boundedReminder(
|
|
1874
|
+
`Memory: [${failure.entryId}] you already tried ${failure.callDescription} at step ${failure.firstStep} and it failed with ${failure.errorDigest}.`,
|
|
1875
|
+
settings().maxTokens
|
|
1876
|
+
);
|
|
1877
|
+
if (message.length === 0) return NO_EFFECTS3;
|
|
1878
|
+
lastInjectedOperationFingerprint = step.operationFingerprint;
|
|
1879
|
+
lastInjectedMessage = message;
|
|
1880
|
+
lastDecision = "injected";
|
|
1881
|
+
rulesInjectedSincePromptedStep = true;
|
|
1882
|
+
deps.bank.recordInjection([failure.entryId]);
|
|
1883
|
+
return [{ kind: "inject_reminder", message, severity: "advisory" }];
|
|
1884
|
+
}
|
|
1885
|
+
lastDecision = "silent";
|
|
1886
|
+
return NO_EFFECTS3;
|
|
1887
|
+
} finally {
|
|
1888
|
+
lastTurnEndStep = toolStep;
|
|
1563
1889
|
}
|
|
1564
1890
|
}
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
const
|
|
1577
|
-
if (
|
|
1891
|
+
function reactivateKnowledge() {
|
|
1892
|
+
if (!reactivateAfterCompaction) return NO_EFFECTS3;
|
|
1893
|
+
reactivateAfterCompaction = false;
|
|
1894
|
+
const prefix = "Memory: execution state restored after compaction:\n";
|
|
1895
|
+
const maxTokens = settings().maxTokens;
|
|
1896
|
+
const prefixTokens = ceilChars(prefix.length);
|
|
1897
|
+
const rendered = deps.bank.renderRestoredState(Math.max(0, maxTokens - prefixTokens));
|
|
1898
|
+
if (rendered.length === 0) {
|
|
1899
|
+
lastDecision = "silent";
|
|
1900
|
+
return NO_EFFECTS3;
|
|
1901
|
+
}
|
|
1902
|
+
const message = boundedReminder(`${prefix}${rendered}`, maxTokens);
|
|
1903
|
+
if (message.length === 0) {
|
|
1904
|
+
lastDecision = "silent";
|
|
1905
|
+
return NO_EFFECTS3;
|
|
1906
|
+
}
|
|
1907
|
+
const citedIds = [...message.matchAll(/\[([^\]]+)\]/gu)].map((match) => match[1]).filter((id) => id !== void 0);
|
|
1908
|
+
deps.bank.recordInjection(citedIds);
|
|
1909
|
+
lastInjectedMessage = message;
|
|
1910
|
+
lastDecision = "injected";
|
|
1911
|
+
return [{ kind: "inject_reminder", message, severity: "advisory" }];
|
|
1578
1912
|
}
|
|
1579
|
-
return matches.sort((a, b) => b.score - a.score || a.entry.name.localeCompare(b.entry.name));
|
|
1580
|
-
}
|
|
1581
|
-
var OWN_MARKETPLACE_URL_PREFIX = "https://github.com/iowarp/clio-coder/";
|
|
1582
|
-
function isOwnMarketplaceSource(entry) {
|
|
1583
|
-
const source = entry.sourceUrl.trim();
|
|
1584
|
-
if (/^(?:https?:\/\/|git@)/.test(source)) return source.startsWith(OWN_MARKETPLACE_URL_PREFIX);
|
|
1585
|
-
return entry.origin === "catalog" && path.isAbsolute(source);
|
|
1586
1913
|
}
|
|
1587
|
-
function
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1914
|
+
function prepareToolStep(input) {
|
|
1915
|
+
const toolName = input.toolName?.trim();
|
|
1916
|
+
if (!toolName) return null;
|
|
1917
|
+
const canonical = hashToolCall(toolName, input.toolArgs ?? {});
|
|
1918
|
+
return {
|
|
1919
|
+
toolName,
|
|
1920
|
+
operationFingerprint: createHash("sha256").update(canonical).digest("hex").slice(0, 16),
|
|
1921
|
+
callDescription: shortText(canonical, CALL_DESCRIPTION_MAX_CHARS)
|
|
1922
|
+
};
|
|
1596
1923
|
}
|
|
1597
|
-
function
|
|
1598
|
-
return
|
|
1924
|
+
function pendingKey(input, operationFingerprint) {
|
|
1925
|
+
return input.toolCallId ?? `anonymous:${operationFingerprint}`;
|
|
1599
1926
|
}
|
|
1600
|
-
function
|
|
1601
|
-
|
|
1602
|
-
if (
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
if (never && typeof never === "object" && !Array.isArray(never)) {
|
|
1607
|
-
const entries = Object.entries(never).filter(
|
|
1608
|
-
(pair) => typeof pair[1] === "string"
|
|
1609
|
-
);
|
|
1610
|
-
return { never: Object.fromEntries(entries) };
|
|
1927
|
+
function resultDigest(input, outcome) {
|
|
1928
|
+
if (input.toolResultDigest !== void 0) return sanitizeToolResultDigest(input.toolResultDigest);
|
|
1929
|
+
if (outcome === "error") {
|
|
1930
|
+
const metadataMessage = input.metadata?.errorMessage;
|
|
1931
|
+
if (typeof metadataMessage === "string" && metadataMessage.trim().length > 0) {
|
|
1932
|
+
return legacyToolResultDigest(diagnosticLine(metadataMessage), { outcome });
|
|
1611
1933
|
}
|
|
1612
|
-
|
|
1934
|
+
for (const key of ["error", "message"]) {
|
|
1935
|
+
const value = input.toolResultDetails?.[key];
|
|
1936
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
1937
|
+
return legacyToolResultDigest(diagnosticLine(value), { outcome });
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
return legacyToolResultDigest("", { outcome });
|
|
1613
1941
|
}
|
|
1614
|
-
|
|
1942
|
+
const resultFingerprint = input.metadata?.resultFingerprint;
|
|
1943
|
+
return legacyToolResultDigest(
|
|
1944
|
+
typeof resultFingerprint === "string" ? `ok result ${resultFingerprint.slice(0, 16)}` : "ok",
|
|
1945
|
+
{ outcome }
|
|
1946
|
+
);
|
|
1615
1947
|
}
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1948
|
+
var DIAGNOSTIC_HINT = /\b(error|failed|failure|cannot|unable|expected|refused|denied|missing|not found|timed out|timeout|fatal)\b/iu;
|
|
1949
|
+
function diagnosticLine(value) {
|
|
1950
|
+
const lines = value.split(/\r?\n/u).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
1951
|
+
return lines.find((line) => DIAGNOSTIC_HINT.test(line)) ?? lines[0] ?? "";
|
|
1952
|
+
}
|
|
1953
|
+
function proceduralContent(call, attempts, firstStep, error) {
|
|
1954
|
+
return `${call} failed ${attempts} time${attempts === 1 ? "" : "s"}; first observed at step ${firstStep}: ${error}.`;
|
|
1621
1955
|
}
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
return `[clio-install:${offerTag}]`;
|
|
1956
|
+
function boundedReminder(message, maxTokens) {
|
|
1957
|
+
const maxChars = maxTokens * 4;
|
|
1958
|
+
if (maxChars <= 0) return "";
|
|
1959
|
+
const normalized = message.replace(/[^\S\n]+/gu, " ").trim();
|
|
1960
|
+
return normalized.length <= maxChars ? normalized : normalized.slice(0, maxChars).trimEnd();
|
|
1628
1961
|
}
|
|
1629
|
-
function
|
|
1630
|
-
|
|
1962
|
+
function shortText(value, maxChars) {
|
|
1963
|
+
const normalized = value.replace(/\s+/gu, " ").trim();
|
|
1964
|
+
return normalized.length <= maxChars ? normalized : `${normalized.slice(0, Math.max(0, maxChars - 1)).trimEnd()}\u2026`;
|
|
1631
1965
|
}
|
|
1632
|
-
function
|
|
1633
|
-
return
|
|
1966
|
+
function positiveInteger(value, fallback) {
|
|
1967
|
+
return value !== void 0 && Number.isInteger(value) && value > 0 ? value : fallback;
|
|
1634
1968
|
}
|
|
1635
|
-
function
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
(entry) => typeof entry === "object" && entry !== null && typeof entry.question === "string" && typeof entry.answer === "string"
|
|
1969
|
+
function countBankWrites(delta) {
|
|
1970
|
+
return [delta.status, delta.knowledge, delta.procedural].reduce(
|
|
1971
|
+
(total, entry) => total + entry.added + entry.updated + entry.deleted,
|
|
1972
|
+
0
|
|
1640
1973
|
);
|
|
1641
1974
|
}
|
|
1642
|
-
function
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
SKILL_INSTALL_OFFER_OPTION_NOT_NOW,
|
|
1647
|
-
SKILL_INSTALL_OFFER_OPTION_NEVER
|
|
1648
|
-
];
|
|
1649
|
-
for (const label of labels) {
|
|
1650
|
-
if (answer.options?.includes(label)) return label;
|
|
1651
|
-
}
|
|
1652
|
-
for (const label of labels) {
|
|
1653
|
-
if (answer.answer.includes(label)) return label;
|
|
1975
|
+
function setBounded(map, key, value, capacity) {
|
|
1976
|
+
if (!map.has(key) && map.size >= capacity) {
|
|
1977
|
+
const oldest = map.keys().next().value;
|
|
1978
|
+
if (oldest !== void 0) map.delete(oldest);
|
|
1654
1979
|
}
|
|
1655
|
-
|
|
1980
|
+
map.set(key, value);
|
|
1656
1981
|
}
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
};
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
let sessionDeclines = /* @__PURE__ */ new Set();
|
|
1666
|
-
let installedNamesCache = null;
|
|
1667
|
-
let marketplaceEntriesCache = null;
|
|
1668
|
-
let pendingOffer = null;
|
|
1669
|
-
const trackSession = (input) => {
|
|
1670
|
-
const sessionId = input.sessionId ?? null;
|
|
1671
|
-
if (lastSeenSessionId === void 0 || lastSeenSessionId === null && sessionId !== null) {
|
|
1672
|
-
lastSeenSessionId = sessionId;
|
|
1673
|
-
return;
|
|
1674
|
-
}
|
|
1675
|
-
if (sessionId !== lastSeenSessionId) {
|
|
1676
|
-
lastSeenSessionId = sessionId;
|
|
1677
|
-
offeredNames = /* @__PURE__ */ new Set();
|
|
1678
|
-
sessionDeclines = /* @__PURE__ */ new Set();
|
|
1679
|
-
pendingOffer = null;
|
|
1680
|
-
installedNamesCache = null;
|
|
1681
|
-
marketplaceEntriesCache = null;
|
|
1682
|
-
}
|
|
1683
|
-
};
|
|
1684
|
-
const bestMatch = (text) => {
|
|
1685
|
-
const installedWasCached = installedNamesCache !== null;
|
|
1686
|
-
let never;
|
|
1982
|
+
|
|
1983
|
+
// src/domains/middleware/memory-step-endpoint.ts
|
|
1984
|
+
init_esm_shims();
|
|
1985
|
+
function announceMemoryStepEndpoint(deps, complete) {
|
|
1986
|
+
const { bus, endpointKey, targetId } = deps;
|
|
1987
|
+
if (endpointKey === null) return complete;
|
|
1988
|
+
return async (request) => {
|
|
1989
|
+
const releaseEndpointSlot = registerForegroundStream(endpointKey);
|
|
1687
1990
|
try {
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
return null;
|
|
1693
|
-
}
|
|
1694
|
-
const declinedKeys = /* @__PURE__ */ new Set([...Object.keys(never), ...sessionDeclines]);
|
|
1695
|
-
const findMatch = () => matchMarketplaceSkills(
|
|
1696
|
-
text,
|
|
1697
|
-
marketplaceEntriesCache ?? [],
|
|
1698
|
-
/* @__PURE__ */ new Set([...installedNamesCache ?? [], ...offeredNames])
|
|
1699
|
-
).find((candidate) => !declinedKeys.has(declineKey(candidate.entry.name, candidate.entry.version))) ?? null;
|
|
1700
|
-
let match = findMatch();
|
|
1701
|
-
if (match && installedWasCached) {
|
|
1702
|
-
try {
|
|
1703
|
-
installedNamesCache = new Set(deps.listInstalledSkillNames());
|
|
1704
|
-
} catch {
|
|
1705
|
-
return null;
|
|
1706
|
-
}
|
|
1707
|
-
match = findMatch();
|
|
1708
|
-
}
|
|
1709
|
-
return match;
|
|
1710
|
-
};
|
|
1711
|
-
const installGated = (entry, scope) => {
|
|
1712
|
-
assertPromotionInstallSource(entry);
|
|
1713
|
-
const result = deps.installEntry(entry, scope);
|
|
1714
|
-
installedNamesCache?.add(entry.name);
|
|
1715
|
-
return result;
|
|
1716
|
-
};
|
|
1717
|
-
const evaluateAskUserAnswers = (input) => {
|
|
1718
|
-
const offer = pendingOffer;
|
|
1719
|
-
if (!offer) return NO_EFFECTS2;
|
|
1720
|
-
const marker = offerBindingTag(offer.tag);
|
|
1721
|
-
if (input.toolResultDetails?.cancelled === true) {
|
|
1722
|
-
return NO_EFFECTS2;
|
|
1723
|
-
}
|
|
1724
|
-
for (const answer of answersFromDetails(input.toolResultDetails)) {
|
|
1725
|
-
if (!answer.question.includes(marker)) continue;
|
|
1726
|
-
const option = chosenOfferOption(answer);
|
|
1727
|
-
if (option === null) continue;
|
|
1728
|
-
pendingOffer = null;
|
|
1729
|
-
const offerKey = declineKey(offer.entry.name, offer.entry.version);
|
|
1730
|
-
if (option === SKILL_INSTALL_OFFER_OPTION_NOT_NOW) {
|
|
1731
|
-
sessionDeclines.add(offerKey);
|
|
1732
|
-
return NO_EFFECTS2;
|
|
1733
|
-
}
|
|
1734
|
-
if (option === SKILL_INSTALL_OFFER_OPTION_NEVER) {
|
|
1735
|
-
sessionDeclines.add(offerKey);
|
|
1991
|
+
return await complete(request);
|
|
1992
|
+
} finally {
|
|
1993
|
+
releaseEndpointSlot();
|
|
1994
|
+
if (bus !== null) {
|
|
1736
1995
|
try {
|
|
1737
|
-
|
|
1996
|
+
bus.emit(BusChannels.MemoryStepCompleted, { endpointKey, targetId });
|
|
1738
1997
|
} catch {
|
|
1739
1998
|
}
|
|
1740
|
-
return NO_EFFECTS2;
|
|
1741
|
-
}
|
|
1742
|
-
const scope = option === SKILL_INSTALL_OFFER_OPTION_USER ? "user" : "project";
|
|
1743
|
-
try {
|
|
1744
|
-
const result = installGated(offer.entry, scope);
|
|
1745
|
-
return [
|
|
1746
|
-
{
|
|
1747
|
-
kind: "annotate_tool_result",
|
|
1748
|
-
severity: "info",
|
|
1749
|
-
message: `[Marketplace] Installed skill "${offer.entry.name}" (${scope} scope) to ${result.path}. It is installed but not active; the operator activates it with /skill ${offer.entry.name}.`
|
|
1750
|
-
}
|
|
1751
|
-
];
|
|
1752
|
-
} catch (error) {
|
|
1753
|
-
sessionDeclines.add(offerKey);
|
|
1754
|
-
return [
|
|
1755
|
-
{
|
|
1756
|
-
kind: "annotate_tool_result",
|
|
1757
|
-
severity: "warn",
|
|
1758
|
-
message: `[Marketplace] Install of "${offer.entry.name}" failed: ${error instanceof Error ? error.message : String(error)}. Continue the task without it.`
|
|
1759
|
-
}
|
|
1760
|
-
];
|
|
1761
1999
|
}
|
|
1762
2000
|
}
|
|
1763
|
-
return NO_EFFECTS2;
|
|
1764
2001
|
};
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
// src/domains/middleware/task-board-reminder.ts
|
|
2005
|
+
init_esm_shims();
|
|
2006
|
+
var TASK_BOARD_REMINDER_REGISTRATION_ID = "observer.task-board-reminder";
|
|
2007
|
+
function taskBoardReminderMessage(steps) {
|
|
2008
|
+
return `[Tasks] This request enumerates ${steps} steps. Before the first edit, declare the task board: tasks action="plan" with a title and one task per step, then work them one at a time ("start", then "done" with an evidence note).`;
|
|
2009
|
+
}
|
|
2010
|
+
function countEnumeratedSteps(text) {
|
|
2011
|
+
const value = text ?? "";
|
|
2012
|
+
if (value.trim().length === 0) return 0;
|
|
2013
|
+
const markers = [];
|
|
2014
|
+
const markerPattern = /(?:^|[\s(])(?:step\s+)?([1-9])\s*[).:]\s/gi;
|
|
2015
|
+
let match = markerPattern.exec(value);
|
|
2016
|
+
while (match !== null) {
|
|
2017
|
+
const digit = Number(match[1]);
|
|
2018
|
+
if (Number.isInteger(digit)) markers.push(digit);
|
|
2019
|
+
match = markerPattern.exec(value);
|
|
2020
|
+
}
|
|
2021
|
+
let run = 0;
|
|
2022
|
+
for (const digit of markers) {
|
|
2023
|
+
if (digit === run + 1) run += 1;
|
|
2024
|
+
else if (digit === 1) run = 1;
|
|
2025
|
+
}
|
|
2026
|
+
if (run >= 3) return run;
|
|
2027
|
+
const bulletLines = value.split(/\r?\n/).filter((line) => /^\s*[-*]\s+\S/.test(line)).length;
|
|
2028
|
+
return bulletLines >= 3 ? bulletLines : 0;
|
|
2029
|
+
}
|
|
2030
|
+
var NO_EFFECTS4 = [];
|
|
2031
|
+
function createTaskBoardReminderRegistration() {
|
|
2032
|
+
let lastSeenSessionId;
|
|
2033
|
+
let spentForActiveSession = false;
|
|
1765
2034
|
return {
|
|
1766
|
-
id:
|
|
1767
|
-
description: "
|
|
1768
|
-
hooks: ["turn_start"
|
|
2035
|
+
id: TASK_BOARD_REMINDER_REGISTRATION_ID,
|
|
2036
|
+
description: "once per session, on the first explicitly enumerated multi-step turn, teaches the board-first protocol",
|
|
2037
|
+
hooks: ["turn_start"],
|
|
1769
2038
|
evaluate(input) {
|
|
1770
|
-
|
|
1771
|
-
if (
|
|
1772
|
-
|
|
2039
|
+
const sessionId = input.sessionId ?? null;
|
|
2040
|
+
if (lastSeenSessionId === void 0 || lastSeenSessionId === null && sessionId !== null) {
|
|
2041
|
+
lastSeenSessionId = sessionId;
|
|
2042
|
+
} else if (sessionId !== lastSeenSessionId) {
|
|
2043
|
+
lastSeenSessionId = sessionId;
|
|
2044
|
+
spentForActiveSession = false;
|
|
1773
2045
|
}
|
|
1774
|
-
if (
|
|
1775
|
-
|
|
2046
|
+
if (spentForActiveSession) return NO_EFFECTS4;
|
|
2047
|
+
const steps = countEnumeratedSteps(input.text);
|
|
2048
|
+
if (steps < 3) return NO_EFFECTS4;
|
|
2049
|
+
spentForActiveSession = true;
|
|
1776
2050
|
const pendingSkillRequests = metadataNumber(input, "pendingSkillRequests");
|
|
1777
|
-
if (pendingSkillRequests !== null && pendingSkillRequests > 0) return
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
2051
|
+
if (pendingSkillRequests !== null && pendingSkillRequests > 0) return NO_EFFECTS4;
|
|
2052
|
+
return [{ kind: "inject_reminder", severity: "info", message: taskBoardReminderMessage(steps) }];
|
|
2053
|
+
}
|
|
2054
|
+
};
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
// src/domains/middleware/task-nudge.ts
|
|
2058
|
+
init_esm_shims();
|
|
2059
|
+
var TASK_NUDGE_REGISTRATION_ID = "nudge.open-tasks";
|
|
2060
|
+
function openTasks(board) {
|
|
2061
|
+
return board.tasks.filter((task) => task.status === "pending" || task.status === "active");
|
|
2062
|
+
}
|
|
2063
|
+
function buildOpenTasksMessage(board) {
|
|
2064
|
+
const open = openTasks(board);
|
|
2065
|
+
const counts = taskBoardCounts(board);
|
|
2066
|
+
const rows = open.map((task) => ` ${task.status === "active" ? "[>]" : "[ ]"} ${task.id} ${task.title}`);
|
|
2067
|
+
return `[Clio Coder] task board "${board.title}" still has ${open.length} of ${counts.total} task(s) open:
|
|
2068
|
+
${rows.join("\n")}
|
|
2069
|
+
Continue working them, or record the honest state on the board: tasks action="done" with an evidence note, action="block" with a reason, or action="drop". Do not end the turn with a stale board.`;
|
|
2070
|
+
}
|
|
2071
|
+
function createTaskNudgeRegistration(options) {
|
|
2072
|
+
return {
|
|
2073
|
+
id: TASK_NUDGE_REGISTRATION_ID,
|
|
2074
|
+
description: "carry the turn onward when a work turn ends with open task-board tasks",
|
|
2075
|
+
hooks: ["turn_end"],
|
|
2076
|
+
evaluate(input) {
|
|
2077
|
+
if (input.hook !== "turn_end") return [];
|
|
2078
|
+
const stopReason = input.metadata?.stopReason;
|
|
2079
|
+
if (stopReason !== void 0 && stopReason !== "stop") return [];
|
|
2080
|
+
const turnToolCalls = input.metadata?.turnToolCalls;
|
|
2081
|
+
if (typeof turnToolCalls !== "number" || turnToolCalls <= 0) return [];
|
|
2082
|
+
const activeToolNames = input.metadata?.activeToolNames;
|
|
2083
|
+
if (typeof activeToolNames === "string" && !activeToolNames.split(",").includes(ToolNames.Tasks)) return [];
|
|
2084
|
+
let board;
|
|
2085
|
+
try {
|
|
2086
|
+
board = options.getBoard();
|
|
2087
|
+
} catch {
|
|
2088
|
+
return [];
|
|
1793
2089
|
}
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
return [
|
|
2090
|
+
if (board === null || openTasks(board).length === 0) return [];
|
|
2091
|
+
const message = buildOpenTasksMessage(board);
|
|
2092
|
+
return [
|
|
2093
|
+
{ kind: "request_continuation", message },
|
|
2094
|
+
{ kind: "inject_reminder", message, severity: "warn" }
|
|
2095
|
+
];
|
|
1797
2096
|
}
|
|
1798
2097
|
};
|
|
1799
2098
|
}
|
|
1800
2099
|
|
|
1801
|
-
// src/domains/middleware/
|
|
2100
|
+
// src/domains/middleware/tool-choice-control.ts
|
|
1802
2101
|
init_esm_shims();
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
var MEMORY_INTERVENTION_DEFAULT_WINDOW_STEPS = 8;
|
|
1806
|
-
var MEMORY_INTERVENTION_DEFAULT_MAX_TOKENS = 2e3;
|
|
1807
|
-
var MEMORY_INTERVENTION_DEFAULT_EVERY_N_TOOLS = 10;
|
|
1808
|
-
var MEMORY_INTERVENTION_TIMEOUT_BACKOFF_THRESHOLD = 2;
|
|
1809
|
-
var MEMORY_INTERVENTION_ACTIVITY_LIMIT = 20;
|
|
1810
|
-
var CALL_DESCRIPTION_MAX_CHARS = 180;
|
|
1811
|
-
var NO_EFFECTS3 = [];
|
|
1812
|
-
function createMemoryInterventionRegistration(deps) {
|
|
1813
|
-
const pending = /* @__PURE__ */ new Map();
|
|
1814
|
-
const trajectory = [];
|
|
1815
|
-
const failures = /* @__PURE__ */ new Map();
|
|
1816
|
-
let toolStep = 0;
|
|
1817
|
-
let lastTurnEndStep = 0;
|
|
1818
|
-
let reactivateAfterCompaction = false;
|
|
1819
|
-
let lastInjectedOperationFingerprint = null;
|
|
1820
|
-
let currentTask = "(current task unavailable)";
|
|
1821
|
-
let toolsSinceMemoryStep = 0;
|
|
1822
|
-
let consecutiveErrors = 0;
|
|
1823
|
-
let lastPromptedBoundary = null;
|
|
1824
|
-
let lastInjectedMessage = null;
|
|
1825
|
-
let lastDecision = null;
|
|
1826
|
-
const pendingTriggers = /* @__PURE__ */ new Set();
|
|
1827
|
-
const activity = [];
|
|
1828
|
-
const annotatedThisTurn = /* @__PURE__ */ new Set();
|
|
1829
|
-
let telemetryBankSnapshot = deps.bank.snapshot();
|
|
1830
|
-
let promptedStepInFlight = false;
|
|
1831
|
-
let promptedStepTier = "rules";
|
|
1832
|
-
let outstandingStep = Promise.resolve();
|
|
1833
|
-
let rulesInjectedSincePromptedStep = false;
|
|
1834
|
-
let annotatedSinceTurnEnd = false;
|
|
1835
|
-
let consecutiveLlmTimeouts = 0;
|
|
2102
|
+
function createMiddlewareToolChoiceControl() {
|
|
2103
|
+
let choice = { kind: "auto" };
|
|
1836
2104
|
return {
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
try {
|
|
1843
|
-
switch (input.hook) {
|
|
1844
|
-
case "before_tool":
|
|
1845
|
-
observeBeforeTool(input);
|
|
1846
|
-
return NO_EFFECTS3;
|
|
1847
|
-
case "after_tool":
|
|
1848
|
-
return observeAfterTool(input);
|
|
1849
|
-
case "turn_end": {
|
|
1850
|
-
if (toolStep <= lastTurnEndStep) return NO_EFFECTS3;
|
|
1851
|
-
const alreadyReported = annotatedSinceTurnEnd;
|
|
1852
|
-
annotatedSinceTurnEnd = false;
|
|
1853
|
-
const started = process.hrtime.bigint();
|
|
1854
|
-
const effects = decideRepeatedFailure();
|
|
1855
|
-
if (alreadyReported && effects.length === 0) return effects;
|
|
1856
|
-
emitTelemetry(
|
|
1857
|
-
[effects.length > 0 ? "repeated_failure" : "turn_end"],
|
|
1858
|
-
"rules",
|
|
1859
|
-
effects.length > 0 ? "injected" : "silent",
|
|
1860
|
-
effects.length > 0 ? "intervened" : "no_repeated_failure",
|
|
1861
|
-
citedEntryCount(effects[0]?.kind === "inject_reminder" ? effects[0].message : null),
|
|
1862
|
-
0,
|
|
1863
|
-
0,
|
|
1864
|
-
started
|
|
1865
|
-
);
|
|
1866
|
-
return effects;
|
|
1867
|
-
}
|
|
1868
|
-
case "on_compaction":
|
|
1869
|
-
if (input.metadata?.stage === "working_set_recall") return NO_EFFECTS3;
|
|
1870
|
-
reactivateAfterCompaction = true;
|
|
1871
|
-
return NO_EFFECTS3;
|
|
1872
|
-
case "turn_start": {
|
|
1873
|
-
if (input.text?.trim()) currentTask = shortText(input.text, 2e3);
|
|
1874
|
-
annotatedThisTurn.clear();
|
|
1875
|
-
const shouldReactivate = reactivateAfterCompaction;
|
|
1876
|
-
const started = process.hrtime.bigint();
|
|
1877
|
-
const effects = reactivateKnowledge();
|
|
1878
|
-
if (shouldReactivate) {
|
|
1879
|
-
emitTelemetry(
|
|
1880
|
-
["post_compaction"],
|
|
1881
|
-
"rules",
|
|
1882
|
-
effects.length > 0 ? "injected" : "silent",
|
|
1883
|
-
effects.length > 0 ? "intervened" : "bank_empty",
|
|
1884
|
-
citedEntryCount(effects[0]?.kind === "inject_reminder" ? effects[0].message : null),
|
|
1885
|
-
0,
|
|
1886
|
-
0,
|
|
1887
|
-
started
|
|
1888
|
-
);
|
|
1889
|
-
}
|
|
1890
|
-
return effects;
|
|
1891
|
-
}
|
|
2105
|
+
apply(effects) {
|
|
2106
|
+
for (const effect of effects) {
|
|
2107
|
+
if (effect.kind === "lock_tools") {
|
|
2108
|
+
choice = { kind: "none" };
|
|
2109
|
+
continue;
|
|
1892
2110
|
}
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
}
|
|
1896
|
-
},
|
|
1897
|
-
/**
|
|
1898
|
-
* Kicks off the background memory step and returns immediately. The pi
|
|
1899
|
-
* agent does not go idle until every `agent_end` listener settles, so
|
|
1900
|
-
* awaiting a local memory model here would stall the visible turn for its
|
|
1901
|
-
* full latency. Small local models measure in tens of seconds, which is far
|
|
1902
|
-
* past any tolerable end-of-turn pause and past the policy timeout itself.
|
|
1903
|
-
* The reminder is delivered through `onDeferredReminder` instead, landing
|
|
1904
|
-
* in the next submitted turn, which is where an awaited turn_end reminder
|
|
1905
|
-
* would have been buffered anyway.
|
|
1906
|
-
*/
|
|
1907
|
-
async evaluateAsync(input, context) {
|
|
1908
|
-
if (!settings().enabled || input.hook !== "turn_end" || pendingTriggers.size === 0) return NO_EFFECTS3;
|
|
1909
|
-
const boundary = input.turnId ?? input.metadata?.userTurnId?.toString() ?? `tool-step:${toolStep}`;
|
|
1910
|
-
if (boundary === lastPromptedBoundary) return NO_EFFECTS3;
|
|
1911
|
-
if (deps.deliversDeferredReminders === false) {
|
|
1912
|
-
emitTelemetry([...pendingTriggers], "rules", "silent", "no_consumer", 0, 0, 0, process.hrtime.bigint());
|
|
1913
|
-
pendingTriggers.clear();
|
|
1914
|
-
return NO_EFFECTS3;
|
|
1915
|
-
}
|
|
1916
|
-
if (promptedStepInFlight) {
|
|
1917
|
-
emitTelemetry(
|
|
1918
|
-
[...pendingTriggers],
|
|
1919
|
-
promptedStepTier,
|
|
1920
|
-
"dropped",
|
|
1921
|
-
"step_in_flight",
|
|
1922
|
-
0,
|
|
1923
|
-
0,
|
|
1924
|
-
0,
|
|
1925
|
-
process.hrtime.bigint()
|
|
1926
|
-
);
|
|
1927
|
-
return NO_EFFECTS3;
|
|
1928
|
-
}
|
|
1929
|
-
lastPromptedBoundary = boundary;
|
|
1930
|
-
const triggers = [...pendingTriggers];
|
|
1931
|
-
pendingTriggers.clear();
|
|
1932
|
-
toolsSinceMemoryStep = 0;
|
|
1933
|
-
consecutiveErrors = 0;
|
|
1934
|
-
const rulesAlreadySpoke = rulesInjectedSincePromptedStep || (context?.priorEffects.some(
|
|
1935
|
-
(effect) => effect.kind === "inject_reminder" && effect.message.startsWith("Memory:")
|
|
1936
|
-
) ?? false);
|
|
1937
|
-
rulesInjectedSincePromptedStep = false;
|
|
1938
|
-
promptedStepInFlight = true;
|
|
1939
|
-
outstandingStep = runPromptedStep({
|
|
1940
|
-
deterministicTrigger: triggers.some((trigger) => trigger !== "interval"),
|
|
1941
|
-
suppressIntervention: rulesAlreadySpoke,
|
|
1942
|
-
triggerReasons: triggers
|
|
1943
|
-
}).then((result) => {
|
|
1944
|
-
if (rulesAlreadySpoke && result.decision === "silent") lastDecision = "injected";
|
|
1945
|
-
if (result.reminder === null) return;
|
|
1946
|
-
lastInjectedMessage = result.reminder;
|
|
1947
|
-
deps.onDeferredReminder?.(result.reminder);
|
|
1948
|
-
}).catch(() => {
|
|
1949
|
-
}).finally(() => {
|
|
1950
|
-
promptedStepInFlight = false;
|
|
1951
|
-
});
|
|
1952
|
-
return NO_EFFECTS3;
|
|
1953
|
-
},
|
|
1954
|
-
runPromptedStep,
|
|
1955
|
-
signalLoop() {
|
|
1956
|
-
if (settings().enabled) pendingTriggers.add("loop_signal");
|
|
1957
|
-
},
|
|
1958
|
-
lastDecision: () => lastDecision,
|
|
1959
|
-
recentActivity: () => [...activity],
|
|
1960
|
-
stepInFlight: () => promptedStepInFlight,
|
|
1961
|
-
whenIdle: () => outstandingStep
|
|
1962
|
-
};
|
|
1963
|
-
function settings() {
|
|
1964
|
-
const live = deps.getSettings?.();
|
|
1965
|
-
return {
|
|
1966
|
-
enabled: live?.enabled ?? deps.enabled ?? true,
|
|
1967
|
-
everyNTools: Math.max(
|
|
1968
|
-
2,
|
|
1969
|
-
positiveInteger(live?.everyNTools ?? deps.everyNTools, MEMORY_INTERVENTION_DEFAULT_EVERY_N_TOOLS)
|
|
1970
|
-
),
|
|
1971
|
-
windowSteps: positiveInteger(live?.windowSteps ?? deps.windowSteps, MEMORY_INTERVENTION_DEFAULT_WINDOW_STEPS),
|
|
1972
|
-
maxTokens: positiveInteger(live?.maxTokens ?? deps.maxTokens, MEMORY_INTERVENTION_DEFAULT_MAX_TOKENS),
|
|
1973
|
-
timeoutMs: positiveInteger(live?.timeoutMs ?? deps.timeoutMs, TASK_MEMORY_POLICY_DEFAULT_TIMEOUT_MS)
|
|
1974
|
-
};
|
|
1975
|
-
}
|
|
1976
|
-
async function runPromptedStep(input) {
|
|
1977
|
-
const silent = (reason) => ({
|
|
1978
|
-
decision: "silent",
|
|
1979
|
-
reason,
|
|
1980
|
-
bankOperations: 0,
|
|
1981
|
-
droppedOperations: 0,
|
|
1982
|
-
reminder: null,
|
|
1983
|
-
inputTokens: 0,
|
|
1984
|
-
outputTokens: 0,
|
|
1985
|
-
usage: null,
|
|
1986
|
-
effects: NO_EFFECTS3
|
|
1987
|
-
});
|
|
1988
|
-
const live = settings();
|
|
1989
|
-
if (!live.enabled) {
|
|
1990
|
-
const result = silent("no_client");
|
|
1991
|
-
lastDecision = result.decision;
|
|
1992
|
-
return result;
|
|
1993
|
-
}
|
|
1994
|
-
const started = process.hrtime.bigint();
|
|
1995
|
-
const triggers = input.triggerReasons?.length ? input.triggerReasons : ["manual"];
|
|
1996
|
-
let tier = "rules";
|
|
1997
|
-
promptedStepTier = tier;
|
|
1998
|
-
let promptedResult;
|
|
1999
|
-
let telemetryDecision = null;
|
|
2000
|
-
try {
|
|
2001
|
-
if (consecutiveLlmTimeouts >= MEMORY_INTERVENTION_TIMEOUT_BACKOFF_THRESHOLD) {
|
|
2002
|
-
promptedResult = silent("llm_timeout_backoff");
|
|
2003
|
-
} else {
|
|
2004
|
-
const client = deps.getModelClient?.() ?? null;
|
|
2005
|
-
if (client === null) {
|
|
2006
|
-
promptedResult = silent("no_client");
|
|
2007
|
-
} else if (deps.backgroundEndpointBusy?.() === true) {
|
|
2008
|
-
tier = "llm";
|
|
2009
|
-
promptedStepTier = tier;
|
|
2010
|
-
promptedResult = silent("endpoint_busy");
|
|
2011
|
-
telemetryDecision = "dropped";
|
|
2012
|
-
} else {
|
|
2013
|
-
tier = "llm";
|
|
2014
|
-
promptedStepTier = tier;
|
|
2015
|
-
promptedResult = await runTaskMemoryPolicy(deps.bank, client, {
|
|
2016
|
-
task: input.task?.trim() || currentTask,
|
|
2017
|
-
trajectory: [...trajectory],
|
|
2018
|
-
deterministicTrigger: input.deterministicTrigger,
|
|
2019
|
-
maxTokens: live.maxTokens,
|
|
2020
|
-
modelMaxTokens: positiveInteger(deps.getModelMaxTokens?.(live.maxTokens), live.maxTokens),
|
|
2021
|
-
...input.suppressIntervention === void 0 ? {} : { suppressIntervention: input.suppressIntervention },
|
|
2022
|
-
previousReminder: lastInjectedMessage,
|
|
2023
|
-
timeoutMs: live.timeoutMs,
|
|
2024
|
-
...deps.onEnvelope === void 0 ? {} : { onEnvelope: deps.onEnvelope }
|
|
2025
|
-
});
|
|
2111
|
+
if (effect.kind === "require_tool" && choice.kind !== "none") {
|
|
2112
|
+
choice = { kind: "required", toolName: effect.toolName };
|
|
2026
2113
|
}
|
|
2027
2114
|
}
|
|
2028
|
-
}
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
reason: "client_error",
|
|
2036
|
-
bankOperations: 0,
|
|
2037
|
-
droppedOperations: 0,
|
|
2038
|
-
reminder: null,
|
|
2039
|
-
error: error instanceof Error ? error.message : "resolving the background model client failed"
|
|
2040
|
-
});
|
|
2041
|
-
}
|
|
2042
|
-
if (tier === "llm" && promptedResult.decision === "timeout") consecutiveLlmTimeouts += 1;
|
|
2043
|
-
else if (tier === "llm" && telemetryDecision !== "dropped") consecutiveLlmTimeouts = 0;
|
|
2044
|
-
lastDecision = promptedResult.decision;
|
|
2045
|
-
if (promptedResult.usage !== null) {
|
|
2046
|
-
try {
|
|
2047
|
-
deps.onStepUsage?.(promptedResult.usage);
|
|
2048
|
-
} catch {
|
|
2049
|
-
}
|
|
2115
|
+
},
|
|
2116
|
+
current: () => choice,
|
|
2117
|
+
toolStarted(toolName) {
|
|
2118
|
+
if (choice.kind === "required" && choice.toolName === toolName) choice = { kind: "auto" };
|
|
2119
|
+
},
|
|
2120
|
+
reset() {
|
|
2121
|
+
choice = { kind: "auto" };
|
|
2050
2122
|
}
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2123
|
+
};
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
// src/domains/middleware/watchdog.ts
|
|
2127
|
+
init_esm_shims();
|
|
2128
|
+
var WATCHDOG_REGISTRATION_ID = "observer.watchdog";
|
|
2129
|
+
var WATCHDOG_DIFF_MAX_BYTES = 12 * 1024;
|
|
2130
|
+
var WATCHDOG_PATHS_MAX = 40;
|
|
2131
|
+
var WATCHDOG_TRUNCATION_MARKER = "\n[diff truncated]";
|
|
2132
|
+
var MUTATING_TOOL_NAMES = /* @__PURE__ */ new Set([ToolNames.Write, ToolNames.Edit, ToolNames.Artifact]);
|
|
2133
|
+
var NO_EFFECTS5 = [];
|
|
2134
|
+
function stringValue2(value) {
|
|
2135
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
2136
|
+
}
|
|
2137
|
+
function firstPath(details) {
|
|
2138
|
+
const paths = details?.paths;
|
|
2139
|
+
if (!Array.isArray(paths)) return null;
|
|
2140
|
+
for (const entry of paths) {
|
|
2141
|
+
const path2 = stringValue2(entry);
|
|
2142
|
+
if (path2 !== null) return path2;
|
|
2067
2143
|
}
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2144
|
+
return null;
|
|
2145
|
+
}
|
|
2146
|
+
function coalesceTurnDiff(byPath, maxBytes = WATCHDOG_DIFF_MAX_BYTES) {
|
|
2147
|
+
const blocks = [];
|
|
2148
|
+
let index = 0;
|
|
2149
|
+
for (const [path2, diff] of byPath) {
|
|
2150
|
+
if (index >= WATCHDOG_PATHS_MAX) {
|
|
2151
|
+
blocks.push(`[${byPath.size - WATCHDOG_PATHS_MAX} more changed path(s) omitted]`);
|
|
2152
|
+
break;
|
|
2153
|
+
}
|
|
2154
|
+
blocks.push(`--- ${path2}
|
|
2155
|
+
${diff}`);
|
|
2156
|
+
index += 1;
|
|
2157
|
+
}
|
|
2158
|
+
const whole = blocks.join("\n\n");
|
|
2159
|
+
if (Buffer.byteLength(whole, "utf8") <= maxBytes) return whole;
|
|
2160
|
+
const room = Math.max(0, maxBytes - Buffer.byteLength(WATCHDOG_TRUNCATION_MARKER, "utf8"));
|
|
2161
|
+
const buffer = Buffer.from(whole, "utf8");
|
|
2162
|
+
let cut = Math.min(room, buffer.byteLength);
|
|
2163
|
+
while (cut > 0) {
|
|
2164
|
+
const next = buffer[cut];
|
|
2165
|
+
if (next === void 0 || (next & 192) !== 128) break;
|
|
2166
|
+
cut -= 1;
|
|
2167
|
+
}
|
|
2168
|
+
return `${buffer.subarray(0, cut).toString("utf8")}${WATCHDOG_TRUNCATION_MARKER}`;
|
|
2169
|
+
}
|
|
2170
|
+
var WATCHDOG_TASK = "Review the coalesced diff in the briefing against the scope it names. Report one check per concern: a change outside the stated scope, a change the scope required that the diff does not make, or a defect visible in the diff itself. Run no commands and change nothing.";
|
|
2171
|
+
function watchdogBriefing(trigger) {
|
|
2172
|
+
const scope = trigger.scope === null ? "(no task board; scope is unstated)" : trigger.scope;
|
|
2173
|
+
const paths = trigger.paths.length === 0 ? "(none)" : trigger.paths.join("\n");
|
|
2174
|
+
return [
|
|
2175
|
+
"## Current scope",
|
|
2176
|
+
scope,
|
|
2177
|
+
"",
|
|
2178
|
+
"## Changed paths",
|
|
2179
|
+
paths,
|
|
2180
|
+
"",
|
|
2181
|
+
"## Coalesced diff for this turn",
|
|
2182
|
+
trigger.diff
|
|
2183
|
+
].join("\n");
|
|
2184
|
+
}
|
|
2185
|
+
function createWatchdogRegistration(deps) {
|
|
2186
|
+
const diffByPath = /* @__PURE__ */ new Map();
|
|
2187
|
+
let toolCalls = 0;
|
|
2188
|
+
let dropped = 0;
|
|
2189
|
+
let inFlight = false;
|
|
2190
|
+
let outstanding = Promise.resolve();
|
|
2191
|
+
const active = () => {
|
|
2192
|
+
if (deps.firesOnThisSurface === false) return null;
|
|
2193
|
+
let settings;
|
|
2073
2194
|
try {
|
|
2074
|
-
deps.
|
|
2075
|
-
triggerReasons,
|
|
2076
|
-
tier,
|
|
2077
|
-
bankDelta,
|
|
2078
|
-
decision,
|
|
2079
|
-
reason,
|
|
2080
|
-
bankOperations: operations.bankOperations,
|
|
2081
|
-
droppedOperations: operations.droppedOperations,
|
|
2082
|
-
citedEntries: citedEntries2,
|
|
2083
|
-
inputTokens,
|
|
2084
|
-
outputTokens,
|
|
2085
|
-
latencyMs
|
|
2086
|
-
});
|
|
2195
|
+
settings = deps.getSettings();
|
|
2087
2196
|
} catch {
|
|
2197
|
+
return null;
|
|
2088
2198
|
}
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2199
|
+
return settings.enabled ? settings : null;
|
|
2200
|
+
};
|
|
2201
|
+
const resetTurn = () => {
|
|
2202
|
+
diffByPath.clear();
|
|
2203
|
+
toolCalls = 0;
|
|
2204
|
+
};
|
|
2205
|
+
const fire = (reason) => {
|
|
2206
|
+
if (diffByPath.size === 0) return;
|
|
2207
|
+
if (inFlight) {
|
|
2208
|
+
dropped += 1;
|
|
2209
|
+
return;
|
|
2210
|
+
}
|
|
2211
|
+
const trigger = {
|
|
2094
2212
|
reason,
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2213
|
+
diff: coalesceTurnDiff(diffByPath),
|
|
2214
|
+
paths: [...diffByPath.keys()].slice(0, WATCHDOG_PATHS_MAX),
|
|
2215
|
+
scope: scopeNow(),
|
|
2216
|
+
toolCalls
|
|
2098
2217
|
};
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
}
|
|
2102
|
-
function citedEntries(message) {
|
|
2103
|
-
if (message === null) return [];
|
|
2104
|
-
const snapshot = deps.bank.snapshot();
|
|
2105
|
-
return [...snapshot.knowledge, ...snapshot.procedural].filter((entry) => message.includes(`[${entry.id}]`));
|
|
2106
|
-
}
|
|
2107
|
-
function citedEntryCount(message) {
|
|
2108
|
-
return citedEntries(message).length;
|
|
2109
|
-
}
|
|
2110
|
-
function reportInjectedEntries(reminder) {
|
|
2111
|
-
if (deps.onInjectedEntries === void 0) return;
|
|
2112
|
-
const entries = citedEntries(reminder);
|
|
2113
|
-
if (entries.length === 0) return;
|
|
2218
|
+
inFlight = true;
|
|
2219
|
+
let started;
|
|
2114
2220
|
try {
|
|
2115
|
-
deps.
|
|
2221
|
+
started = deps.run(trigger);
|
|
2116
2222
|
} catch {
|
|
2223
|
+
inFlight = false;
|
|
2224
|
+
return;
|
|
2117
2225
|
}
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
const prepared = pending.get(key) ?? fallback;
|
|
2129
|
-
pending.delete(key);
|
|
2130
|
-
const outcome = input.metadata?.resultKind === "error" ? "error" : "ok";
|
|
2131
|
-
toolStep += 1;
|
|
2132
|
-
toolsSinceMemoryStep += 1;
|
|
2133
|
-
const live = settings();
|
|
2134
|
-
if (toolsSinceMemoryStep >= live.everyNTools) pendingTriggers.add("interval");
|
|
2135
|
-
if (outcome === "error") {
|
|
2136
|
-
consecutiveErrors += 1;
|
|
2137
|
-
if (consecutiveErrors >= 2) pendingTriggers.add("tool_error_streak");
|
|
2138
|
-
} else {
|
|
2139
|
-
consecutiveErrors = 0;
|
|
2226
|
+
outstanding = started.catch(() => {
|
|
2227
|
+
}).finally(() => {
|
|
2228
|
+
inFlight = false;
|
|
2229
|
+
});
|
|
2230
|
+
};
|
|
2231
|
+
const scopeNow = () => {
|
|
2232
|
+
try {
|
|
2233
|
+
return deps.getScope?.() ?? null;
|
|
2234
|
+
} catch {
|
|
2235
|
+
return null;
|
|
2140
2236
|
}
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
)
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
return [{ kind: "annotate_tool_result", message, severity: "warn" }];
|
|
2237
|
+
};
|
|
2238
|
+
return {
|
|
2239
|
+
id: WATCHDOG_REGISTRATION_ID,
|
|
2240
|
+
description: "review a mutating turn's coalesced diff against the board scope with a read-only verifier run",
|
|
2241
|
+
hooks: ["after_tool", "turn_end"],
|
|
2242
|
+
evaluate(input) {
|
|
2243
|
+
const settings = active();
|
|
2244
|
+
if (settings === null) {
|
|
2245
|
+
if (input.hook === "turn_end") resetTurn();
|
|
2246
|
+
return NO_EFFECTS5;
|
|
2247
|
+
}
|
|
2248
|
+
if (input.hook === "after_tool") {
|
|
2249
|
+
toolCalls += 1;
|
|
2250
|
+
recordMutation(input);
|
|
2251
|
+
const cadence = settings.cadenceToolCalls;
|
|
2252
|
+
if (cadence !== void 0 && cadence >= 1 && toolCalls % cadence === 0) fire("cadence");
|
|
2253
|
+
return NO_EFFECTS5;
|
|
2254
|
+
}
|
|
2255
|
+
if (input.hook !== "turn_end") return NO_EFFECTS5;
|
|
2256
|
+
fire("turn_end");
|
|
2257
|
+
resetTurn();
|
|
2258
|
+
return NO_EFFECTS5;
|
|
2259
|
+
},
|
|
2260
|
+
droppedTriggers: () => dropped,
|
|
2261
|
+
runInFlight: () => inFlight,
|
|
2262
|
+
whenIdle: () => outstanding
|
|
2263
|
+
};
|
|
2264
|
+
function recordMutation(input) {
|
|
2265
|
+
if (input.metadata?.resultKind !== "ok") return;
|
|
2266
|
+
if (input.toolName === void 0 || !MUTATING_TOOL_NAMES.has(input.toolName)) return;
|
|
2267
|
+
const diff = stringValue2(input.toolResultDetails?.diff);
|
|
2268
|
+
if (diff === null) return;
|
|
2269
|
+
diffByPath.set(firstPath(input.toolResultDetails) ?? input.toolName, diff);
|
|
2175
2270
|
}
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
// src/domains/middleware/index.ts
|
|
2274
|
+
init_esm_shims();
|
|
2275
|
+
|
|
2276
|
+
// src/domains/middleware/extension.ts
|
|
2277
|
+
init_esm_shims();
|
|
2278
|
+
|
|
2279
|
+
// src/domains/middleware/registrations.ts
|
|
2280
|
+
init_esm_shims();
|
|
2281
|
+
var MIDDLEWARE_REGISTRATION_OWNERS = ["user-hooks"];
|
|
2282
|
+
function isRegistrationOwner(value) {
|
|
2283
|
+
return MIDDLEWARE_REGISTRATION_OWNERS.includes(value);
|
|
2284
|
+
}
|
|
2285
|
+
function createMiddlewareRegistrationTable(options) {
|
|
2286
|
+
const fixed = Object.freeze([...options.fixed]);
|
|
2287
|
+
const fixedIds = new Set(fixed.map((registration) => registration.id));
|
|
2288
|
+
const buildList = (host, slots) => {
|
|
2289
|
+
const list = [...fixed];
|
|
2290
|
+
const slotAt = (index) => {
|
|
2291
|
+
for (const owner of MIDDLEWARE_REGISTRATION_OWNERS) {
|
|
2292
|
+
const slot = slots.get(owner);
|
|
2293
|
+
if (slot !== void 0 && slot.anchor === index) list.push(...slot.registrations);
|
|
2189
2294
|
}
|
|
2295
|
+
};
|
|
2296
|
+
for (let index = 0; index < host.length; index += 1) {
|
|
2297
|
+
slotAt(index);
|
|
2298
|
+
list.push(host[index]);
|
|
2190
2299
|
}
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
);
|
|
2203
|
-
}
|
|
2204
|
-
function decideRepeatedFailure() {
|
|
2300
|
+
slotAt(host.length);
|
|
2301
|
+
return Object.freeze(list);
|
|
2302
|
+
};
|
|
2303
|
+
const makeState = (host, slots) => Object.freeze({
|
|
2304
|
+
host: Object.freeze([...host]),
|
|
2305
|
+
hostIds: new Set(host.map((registration) => registration.id)),
|
|
2306
|
+
slots: new Map(slots),
|
|
2307
|
+
list: buildList(host, slots)
|
|
2308
|
+
});
|
|
2309
|
+
let state = makeState([], /* @__PURE__ */ new Map());
|
|
2310
|
+
const emit = (diagnostic) => {
|
|
2205
2311
|
try {
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2312
|
+
options.diagnosticSink()(diagnostic);
|
|
2313
|
+
} catch {
|
|
2314
|
+
}
|
|
2315
|
+
};
|
|
2316
|
+
const slotFor = (base, owner) => base.slots.get(owner) ?? { generation: 0, registrations: [], anchor: base.host.length };
|
|
2317
|
+
const disposeGeneration = (owner, generation) => {
|
|
2318
|
+
const base = state;
|
|
2319
|
+
const held = base.slots.get(owner);
|
|
2320
|
+
if (held === void 0 || held.generation !== generation || held.registrations.length === 0) return;
|
|
2321
|
+
const slots = new Map(base.slots);
|
|
2322
|
+
slots.set(owner, { ...held, registrations: Object.freeze([]) });
|
|
2323
|
+
state = makeState(base.host, slots);
|
|
2324
|
+
};
|
|
2325
|
+
const table = {
|
|
2326
|
+
list: () => state.list,
|
|
2327
|
+
registerHook(registration) {
|
|
2328
|
+
const base = state;
|
|
2329
|
+
if (fixedIds.has(registration.id) || base.hostIds.has(registration.id)) return;
|
|
2330
|
+
const evictions = [];
|
|
2331
|
+
const slots = new Map(base.slots);
|
|
2332
|
+
for (const [owner, slot] of base.slots) {
|
|
2333
|
+
if (!slot.registrations.some((entry) => entry.id === registration.id)) continue;
|
|
2334
|
+
slots.set(owner, {
|
|
2335
|
+
...slot,
|
|
2336
|
+
registrations: Object.freeze(slot.registrations.filter((entry) => entry.id !== registration.id))
|
|
2337
|
+
});
|
|
2338
|
+
evictions.push({
|
|
2339
|
+
kind: "registration_conflict",
|
|
2340
|
+
registrationId: registration.id,
|
|
2341
|
+
owner,
|
|
2342
|
+
generation: slot.generation,
|
|
2343
|
+
conflictsWith: "host",
|
|
2344
|
+
action: "evicted"
|
|
2345
|
+
});
|
|
2346
|
+
}
|
|
2347
|
+
state = makeState([...base.host, registration], slots);
|
|
2348
|
+
for (const eviction of evictions) emit(eviction);
|
|
2349
|
+
},
|
|
2350
|
+
prepareReplacement(owner, generation, registrations) {
|
|
2351
|
+
if (!isRegistrationOwner(owner)) {
|
|
2352
|
+
return { status: "rejected", owner, reason: "unknown-owner", activeGeneration: 0 };
|
|
2353
|
+
}
|
|
2354
|
+
const base = state;
|
|
2355
|
+
const slot = slotFor(base, owner);
|
|
2356
|
+
if (!Number.isInteger(generation) || generation <= slot.generation) {
|
|
2357
|
+
return { status: "rejected", owner, reason: "stale", activeGeneration: slot.generation };
|
|
2358
|
+
}
|
|
2359
|
+
const dropped = [];
|
|
2360
|
+
const conflicts = [];
|
|
2361
|
+
const accepted = [];
|
|
2362
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2363
|
+
for (const registration of registrations) {
|
|
2364
|
+
const conflictsWith = fixedIds.has(registration.id) ? "builtin" : base.hostIds.has(registration.id) ? "host" : seen.has(registration.id) ? "owned" : null;
|
|
2365
|
+
if (conflictsWith !== null) {
|
|
2366
|
+
dropped.push({ id: registration.id, conflictsWith });
|
|
2367
|
+
conflicts.push({
|
|
2368
|
+
kind: "registration_conflict",
|
|
2369
|
+
registrationId: registration.id,
|
|
2370
|
+
owner,
|
|
2371
|
+
generation,
|
|
2372
|
+
conflictsWith,
|
|
2373
|
+
action: "dropped"
|
|
2374
|
+
});
|
|
2209
2375
|
continue;
|
|
2210
2376
|
}
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
).length;
|
|
2214
|
-
const failure = failures.get(step.operationFingerprint);
|
|
2215
|
-
if (occurrences < 2 || failure === void 0) continue;
|
|
2216
|
-
const message = boundedReminder(
|
|
2217
|
-
`Memory: [${failure.entryId}] you already tried ${failure.callDescription} at step ${failure.firstStep} and it failed with ${failure.errorDigest}.`,
|
|
2218
|
-
settings().maxTokens
|
|
2219
|
-
);
|
|
2220
|
-
if (message.length === 0) return NO_EFFECTS3;
|
|
2221
|
-
lastInjectedOperationFingerprint = step.operationFingerprint;
|
|
2222
|
-
lastInjectedMessage = message;
|
|
2223
|
-
lastDecision = "injected";
|
|
2224
|
-
rulesInjectedSincePromptedStep = true;
|
|
2225
|
-
deps.bank.recordInjection([failure.entryId]);
|
|
2226
|
-
return [{ kind: "inject_reminder", message, severity: "advisory" }];
|
|
2377
|
+
seen.add(registration.id);
|
|
2378
|
+
accepted.push(registration);
|
|
2227
2379
|
}
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2380
|
+
const slots = new Map(base.slots);
|
|
2381
|
+
slots.set(owner, { generation, registrations: Object.freeze(accepted), anchor: slot.anchor });
|
|
2382
|
+
const prepared = makeState(base.host, slots);
|
|
2383
|
+
let settled = false;
|
|
2384
|
+
const frozenDropped = Object.freeze(dropped);
|
|
2385
|
+
const frozenConflicts = Object.freeze(conflicts);
|
|
2386
|
+
const replacement = {
|
|
2387
|
+
owner,
|
|
2388
|
+
generation,
|
|
2389
|
+
dropped: frozenDropped,
|
|
2390
|
+
conflicts: frozenConflicts,
|
|
2391
|
+
size: accepted.length,
|
|
2392
|
+
// Reference identity on the base state covers every kind of
|
|
2393
|
+
// intervening change: host appends, other replacements, disposals.
|
|
2394
|
+
current: () => !settled && state === base,
|
|
2395
|
+
publish() {
|
|
2396
|
+
settled = true;
|
|
2397
|
+
state = prepared;
|
|
2398
|
+
},
|
|
2399
|
+
emitConflicts() {
|
|
2400
|
+
for (const conflict of frozenConflicts) emit(conflict);
|
|
2401
|
+
},
|
|
2402
|
+
dispose() {
|
|
2403
|
+
disposeGeneration(owner, generation);
|
|
2404
|
+
},
|
|
2405
|
+
discard() {
|
|
2406
|
+
settled = true;
|
|
2407
|
+
}
|
|
2408
|
+
};
|
|
2409
|
+
return { status: "prepared", replacement };
|
|
2410
|
+
},
|
|
2411
|
+
replaceRegistrations(owner, generation, registrations) {
|
|
2412
|
+
const prepared = table.prepareReplacement(owner, generation, registrations);
|
|
2413
|
+
if (prepared.status === "rejected") {
|
|
2414
|
+
return {
|
|
2415
|
+
applied: false,
|
|
2416
|
+
owner: prepared.owner,
|
|
2417
|
+
activeGeneration: prepared.activeGeneration,
|
|
2418
|
+
reason: prepared.reason,
|
|
2419
|
+
dropped: [],
|
|
2420
|
+
dispose: () => void 0
|
|
2421
|
+
};
|
|
2422
|
+
}
|
|
2423
|
+
const replacement = prepared.replacement;
|
|
2424
|
+
if (!replacement.current()) {
|
|
2425
|
+
replacement.discard();
|
|
2426
|
+
return {
|
|
2427
|
+
applied: false,
|
|
2428
|
+
owner,
|
|
2429
|
+
activeGeneration: table.ownedGeneration(owner),
|
|
2430
|
+
reason: "stale",
|
|
2431
|
+
dropped: replacement.dropped,
|
|
2432
|
+
dispose: () => void 0
|
|
2433
|
+
};
|
|
2434
|
+
}
|
|
2435
|
+
replacement.publish();
|
|
2436
|
+
replacement.emitConflicts();
|
|
2437
|
+
return {
|
|
2438
|
+
applied: true,
|
|
2439
|
+
owner,
|
|
2440
|
+
activeGeneration: generation,
|
|
2441
|
+
dropped: replacement.dropped,
|
|
2442
|
+
dispose: () => replacement.dispose()
|
|
2443
|
+
};
|
|
2444
|
+
},
|
|
2445
|
+
ownedGeneration(owner) {
|
|
2446
|
+
return state.slots.get(owner)?.generation ?? 0;
|
|
2249
2447
|
}
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
lastInjectedMessage = message;
|
|
2253
|
-
lastDecision = "injected";
|
|
2254
|
-
return [{ kind: "inject_reminder", message, severity: "advisory" }];
|
|
2255
|
-
}
|
|
2448
|
+
};
|
|
2449
|
+
return table;
|
|
2256
2450
|
}
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2451
|
+
|
|
2452
|
+
// src/domains/middleware/snapshot.ts
|
|
2453
|
+
init_esm_shims();
|
|
2454
|
+
function createMiddlewareSnapshot(rules = listMiddlewareRules()) {
|
|
2261
2455
|
return {
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
callDescription: shortText(canonical, CALL_DESCRIPTION_MAX_CHARS)
|
|
2456
|
+
version: 1,
|
|
2457
|
+
rules: rules.map(cloneMiddlewareRule)
|
|
2265
2458
|
};
|
|
2266
2459
|
}
|
|
2267
|
-
function
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2460
|
+
function createMiddlewareContractFromSnapshot(snapshot) {
|
|
2461
|
+
const builtinById = new Map(listMiddlewareRuleDefinitions().map((definition) => [definition.rule.id, definition]));
|
|
2462
|
+
const definitions = snapshot.rules.map((rule) => {
|
|
2463
|
+
const builtin = builtinById.get(rule.id);
|
|
2464
|
+
const definition = {
|
|
2465
|
+
rule: cloneMiddlewareRule(rule),
|
|
2466
|
+
effects: builtin === void 0 ? [] : builtin.effects.map(cloneMiddlewareEffect)
|
|
2467
|
+
};
|
|
2468
|
+
if (builtin?.toolNames !== void 0) definition.toolNames = [...builtin.toolNames];
|
|
2469
|
+
if (builtin?.predicate !== void 0) definition.predicate = builtin.predicate;
|
|
2470
|
+
return definition;
|
|
2471
|
+
});
|
|
2472
|
+
let diagnosticSink;
|
|
2473
|
+
const table = createMiddlewareRegistrationTable({
|
|
2474
|
+
fixed: definitions.map(registrationFromRuleDefinition),
|
|
2475
|
+
diagnosticSink: () => diagnosticSink ?? writeMiddlewareDiagnosticToStderr
|
|
2476
|
+
});
|
|
2477
|
+
const budgetTracker = createHookBudgetTracker({
|
|
2478
|
+
budgets: resolveHookBudgetsFromEnv(),
|
|
2479
|
+
...resolveHookBudgetTunablesFromEnv()
|
|
2480
|
+
});
|
|
2481
|
+
return {
|
|
2482
|
+
runHook(input) {
|
|
2483
|
+
const registrations = table.list();
|
|
2484
|
+
return runMiddlewareRegistrations(input, registrations, {
|
|
2485
|
+
budgetTracker,
|
|
2486
|
+
...diagnosticSink !== void 0 ? { onDiagnostic: diagnosticSink } : {}
|
|
2487
|
+
});
|
|
2488
|
+
},
|
|
2489
|
+
listRules() {
|
|
2490
|
+
return definitions.map((definition) => cloneMiddlewareRule(definition.rule));
|
|
2491
|
+
},
|
|
2492
|
+
snapshot() {
|
|
2493
|
+
return createMiddlewareSnapshot(definitions.map((definition) => definition.rule));
|
|
2494
|
+
},
|
|
2495
|
+
registerHook(registration) {
|
|
2496
|
+
table.registerHook(registration);
|
|
2497
|
+
},
|
|
2498
|
+
setDiagnosticSink(sink) {
|
|
2499
|
+
diagnosticSink = sink;
|
|
2500
|
+
},
|
|
2501
|
+
prepareRegistrationReplacement(owner, generation, registrations) {
|
|
2502
|
+
return table.prepareReplacement(owner, generation, registrations);
|
|
2503
|
+
},
|
|
2504
|
+
replaceRegistrations(owner, generation, registrations) {
|
|
2505
|
+
return table.replaceRegistrations(owner, generation, registrations);
|
|
2506
|
+
},
|
|
2507
|
+
ownedGeneration(owner) {
|
|
2508
|
+
return table.ownedGeneration(owner);
|
|
2282
2509
|
}
|
|
2283
|
-
|
|
2284
|
-
}
|
|
2285
|
-
const resultFingerprint = input.metadata?.resultFingerprint;
|
|
2286
|
-
return legacyToolResultDigest(
|
|
2287
|
-
typeof resultFingerprint === "string" ? `ok result ${resultFingerprint.slice(0, 16)}` : "ok",
|
|
2288
|
-
{ outcome }
|
|
2289
|
-
);
|
|
2290
|
-
}
|
|
2291
|
-
var DIAGNOSTIC_HINT = /\b(error|failed|failure|cannot|unable|expected|refused|denied|missing|not found|timed out|timeout|fatal)\b/iu;
|
|
2292
|
-
function diagnosticLine(value) {
|
|
2293
|
-
const lines = value.split(/\r?\n/u).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
2294
|
-
return lines.find((line) => DIAGNOSTIC_HINT.test(line)) ?? lines[0] ?? "";
|
|
2295
|
-
}
|
|
2296
|
-
function proceduralContent(call, attempts, firstStep, error) {
|
|
2297
|
-
return `${call} failed ${attempts} time${attempts === 1 ? "" : "s"}; first observed at step ${firstStep}: ${error}.`;
|
|
2298
|
-
}
|
|
2299
|
-
function boundedReminder(message, maxTokens) {
|
|
2300
|
-
const maxChars = maxTokens * 4;
|
|
2301
|
-
if (maxChars <= 0) return "";
|
|
2302
|
-
const normalized = message.replace(/[^\S\n]+/gu, " ").trim();
|
|
2303
|
-
return normalized.length <= maxChars ? normalized : normalized.slice(0, maxChars).trimEnd();
|
|
2304
|
-
}
|
|
2305
|
-
function shortText(value, maxChars) {
|
|
2306
|
-
const normalized = value.replace(/\s+/gu, " ").trim();
|
|
2307
|
-
return normalized.length <= maxChars ? normalized : `${normalized.slice(0, Math.max(0, maxChars - 1)).trimEnd()}\u2026`;
|
|
2510
|
+
};
|
|
2308
2511
|
}
|
|
2309
|
-
|
|
2310
|
-
|
|
2512
|
+
|
|
2513
|
+
// src/domains/middleware/extension.ts
|
|
2514
|
+
function createEnvHookBudgetTracker(env = process.env) {
|
|
2515
|
+
return createHookBudgetTracker({ budgets: resolveHookBudgetsFromEnv(env), ...resolveHookBudgetTunablesFromEnv(env) });
|
|
2311
2516
|
}
|
|
2312
|
-
function
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2517
|
+
function createMiddlewareBundle(options = {}) {
|
|
2518
|
+
const ruleDefinitions = combineRuleDefinitions(listMiddlewareRuleDefinitions(), options.ruleDefinitions ?? []);
|
|
2519
|
+
let diagnosticSink = options.onDiagnostic;
|
|
2520
|
+
const table = createMiddlewareRegistrationTable({
|
|
2521
|
+
fixed: ruleDefinitions.map(registrationFromRuleDefinition),
|
|
2522
|
+
diagnosticSink: () => diagnosticSink ?? writeMiddlewareDiagnosticToStderr
|
|
2523
|
+
});
|
|
2524
|
+
for (const registration of options.registrations ?? []) table.registerHook(registration);
|
|
2525
|
+
const budgetTracker = options.budgetTracker ?? createEnvHookBudgetTracker();
|
|
2526
|
+
const contract = {
|
|
2527
|
+
runHook(input) {
|
|
2528
|
+
const registrations = table.list();
|
|
2529
|
+
return runMiddlewareRegistrations(input, registrations, {
|
|
2530
|
+
budgetTracker,
|
|
2531
|
+
...diagnosticSink !== void 0 ? { onDiagnostic: diagnosticSink } : {}
|
|
2532
|
+
});
|
|
2533
|
+
},
|
|
2534
|
+
runAsyncHook(input, priorEffects = []) {
|
|
2535
|
+
const registrations = table.list();
|
|
2536
|
+
return runMiddlewareAsyncRegistrations(input, registrations, priorEffects, {
|
|
2537
|
+
...diagnosticSink !== void 0 ? { onDiagnostic: diagnosticSink } : {}
|
|
2538
|
+
});
|
|
2539
|
+
},
|
|
2540
|
+
listRules() {
|
|
2541
|
+
return ruleDefinitions.map((definition) => cloneMiddlewareRule(definition.rule));
|
|
2542
|
+
},
|
|
2543
|
+
snapshot() {
|
|
2544
|
+
return createMiddlewareSnapshot(ruleDefinitions.map((definition) => definition.rule));
|
|
2545
|
+
},
|
|
2546
|
+
registerHook(registration) {
|
|
2547
|
+
table.registerHook(registration);
|
|
2548
|
+
},
|
|
2549
|
+
setDiagnosticSink(sink) {
|
|
2550
|
+
diagnosticSink = sink;
|
|
2551
|
+
},
|
|
2552
|
+
prepareRegistrationReplacement(owner, generation, registrations) {
|
|
2553
|
+
return table.prepareReplacement(owner, generation, registrations);
|
|
2554
|
+
},
|
|
2555
|
+
replaceRegistrations(owner, generation, registrations) {
|
|
2556
|
+
return table.replaceRegistrations(owner, generation, registrations);
|
|
2557
|
+
},
|
|
2558
|
+
ownedGeneration(owner) {
|
|
2559
|
+
return table.ownedGeneration(owner);
|
|
2560
|
+
}
|
|
2561
|
+
};
|
|
2562
|
+
return {
|
|
2563
|
+
extension: {
|
|
2564
|
+
start() {
|
|
2565
|
+
return void 0;
|
|
2566
|
+
}
|
|
2567
|
+
},
|
|
2568
|
+
contract
|
|
2569
|
+
};
|
|
2317
2570
|
}
|
|
2318
|
-
function
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2571
|
+
function combineRuleDefinitions(builtin, registered) {
|
|
2572
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2573
|
+
const combined = [];
|
|
2574
|
+
for (const definition of [...builtin, ...registered]) {
|
|
2575
|
+
if (seen.has(definition.rule.id)) continue;
|
|
2576
|
+
seen.add(definition.rule.id);
|
|
2577
|
+
combined.push(definition);
|
|
2322
2578
|
}
|
|
2323
|
-
|
|
2579
|
+
return combined;
|
|
2324
2580
|
}
|
|
2325
2581
|
|
|
2326
|
-
// src/domains/middleware/
|
|
2582
|
+
// src/domains/middleware/manifest.ts
|
|
2327
2583
|
init_esm_shims();
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
const releaseEndpointSlot = registerForegroundStream(endpointKey);
|
|
2333
|
-
try {
|
|
2334
|
-
return await complete(request);
|
|
2335
|
-
} finally {
|
|
2336
|
-
releaseEndpointSlot();
|
|
2337
|
-
if (bus !== null) {
|
|
2338
|
-
try {
|
|
2339
|
-
bus.emit(BusChannels.MemoryStepCompleted, { endpointKey, targetId });
|
|
2340
|
-
} catch {
|
|
2341
|
-
}
|
|
2342
|
-
}
|
|
2343
|
-
}
|
|
2344
|
-
};
|
|
2345
|
-
}
|
|
2584
|
+
var MiddlewareManifest = {
|
|
2585
|
+
name: "middleware",
|
|
2586
|
+
dependsOn: []
|
|
2587
|
+
};
|
|
2346
2588
|
|
|
2347
|
-
// src/domains/middleware/
|
|
2589
|
+
// src/domains/middleware/hooks.ts
|
|
2348
2590
|
init_esm_shims();
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2591
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
2592
|
+
import { isAbsolute, resolve } from "node:path";
|
|
2593
|
+
|
|
2594
|
+
// src/domains/middleware/validate.ts
|
|
2595
|
+
init_esm_shims();
|
|
2596
|
+
function validateMiddlewareEffect(value, source = "$") {
|
|
2597
|
+
const issues = [];
|
|
2598
|
+
const effect = readMiddlewareEffect(value, source, issues);
|
|
2599
|
+
if (issues.length > 0 || effect === null) return { valid: false, issues };
|
|
2600
|
+
return { valid: true, effect, issues: [] };
|
|
2352
2601
|
}
|
|
2353
|
-
function
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
const markerPattern = /(?:^|[\s(])(?:step\s+)?([1-9])\s*[).:]\s/gi;
|
|
2358
|
-
let match = markerPattern.exec(value);
|
|
2359
|
-
while (match !== null) {
|
|
2360
|
-
const digit = Number(match[1]);
|
|
2361
|
-
if (Number.isInteger(digit)) markers.push(digit);
|
|
2362
|
-
match = markerPattern.exec(value);
|
|
2602
|
+
function readMiddlewareEffect(value, path2, issues) {
|
|
2603
|
+
if (!isRecord(value)) {
|
|
2604
|
+
issues.push({ path: path2, message: "expected middleware effect object" });
|
|
2605
|
+
return null;
|
|
2363
2606
|
}
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2607
|
+
const kindValue = value.kind;
|
|
2608
|
+
if (typeof kindValue !== "string" || !isMiddlewareEffectKind(kindValue)) {
|
|
2609
|
+
issues.push({ path: `${path2}.kind`, message: "expected known middleware effect kind" });
|
|
2610
|
+
return null;
|
|
2611
|
+
}
|
|
2612
|
+
switch (kindValue) {
|
|
2613
|
+
case "inject_reminder":
|
|
2614
|
+
return readInjectReminder(value, path2, issues);
|
|
2615
|
+
case "annotate_tool_result":
|
|
2616
|
+
return readAnnotateToolResult(value, path2, issues);
|
|
2617
|
+
case "block_tool":
|
|
2618
|
+
return readBlockTool(value, path2, issues);
|
|
2619
|
+
case "protect_path":
|
|
2620
|
+
return readProtectPath(value, path2, issues);
|
|
2621
|
+
case "request_continuation":
|
|
2622
|
+
return readRequestContinuation(value, path2, issues);
|
|
2623
|
+
case "require_tool":
|
|
2624
|
+
return readRequireTool(value, path2, issues);
|
|
2625
|
+
case "lock_tools":
|
|
2626
|
+
rejectUnexpectedFields(value, path2, ["kind"], issues);
|
|
2627
|
+
return { kind: "lock_tools" };
|
|
2368
2628
|
}
|
|
2369
|
-
if (run >= 3) return run;
|
|
2370
|
-
const bulletLines = value.split(/\r?\n/).filter((line) => /^\s*[-*]\s+\S/.test(line)).length;
|
|
2371
|
-
return bulletLines >= 3 ? bulletLines : 0;
|
|
2372
2629
|
}
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
return {
|
|
2378
|
-
id: TASK_BOARD_REMINDER_REGISTRATION_ID,
|
|
2379
|
-
description: "once per session, on the first explicitly enumerated multi-step turn, teaches the board-first protocol",
|
|
2380
|
-
hooks: ["turn_start"],
|
|
2381
|
-
evaluate(input) {
|
|
2382
|
-
const sessionId = input.sessionId ?? null;
|
|
2383
|
-
if (lastSeenSessionId === void 0 || lastSeenSessionId === null && sessionId !== null) {
|
|
2384
|
-
lastSeenSessionId = sessionId;
|
|
2385
|
-
} else if (sessionId !== lastSeenSessionId) {
|
|
2386
|
-
lastSeenSessionId = sessionId;
|
|
2387
|
-
spentForActiveSession = false;
|
|
2388
|
-
}
|
|
2389
|
-
if (spentForActiveSession) return NO_EFFECTS4;
|
|
2390
|
-
const steps = countEnumeratedSteps(input.text);
|
|
2391
|
-
if (steps < 3) return NO_EFFECTS4;
|
|
2392
|
-
spentForActiveSession = true;
|
|
2393
|
-
const pendingSkillRequests = metadataNumber(input, "pendingSkillRequests");
|
|
2394
|
-
if (pendingSkillRequests !== null && pendingSkillRequests > 0) return NO_EFFECTS4;
|
|
2395
|
-
return [{ kind: "inject_reminder", severity: "info", message: taskBoardReminderMessage(steps) }];
|
|
2396
|
-
}
|
|
2397
|
-
};
|
|
2630
|
+
function readRequireTool(record, path2, issues) {
|
|
2631
|
+
rejectUnexpectedFields(record, path2, ["kind", "toolName"], issues);
|
|
2632
|
+
const toolName = readRequiredString(record, `${path2}.toolName`, issues);
|
|
2633
|
+
return toolName === null ? null : { kind: "require_tool", toolName };
|
|
2398
2634
|
}
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2635
|
+
function readInjectReminder(record, path2, issues) {
|
|
2636
|
+
rejectUnexpectedFields(record, path2, ["kind", "message", "severity"], issues);
|
|
2637
|
+
const message = readRequiredString(record, `${path2}.message`, issues);
|
|
2638
|
+
const severity = readOptionalString(record, `${path2}.severity`, issues);
|
|
2639
|
+
if (severity !== void 0 && !isMiddlewareReminderSeverity(severity)) {
|
|
2640
|
+
issues.push({ path: `${path2}.severity`, message: "expected info, advisory, warn, or hard-block" });
|
|
2641
|
+
}
|
|
2642
|
+
if (message === null) return null;
|
|
2643
|
+
const effect = { kind: "inject_reminder", message };
|
|
2644
|
+
if (severity !== void 0 && isMiddlewareReminderSeverity(severity)) effect.severity = severity;
|
|
2645
|
+
return effect;
|
|
2405
2646
|
}
|
|
2406
|
-
function
|
|
2407
|
-
|
|
2408
|
-
const
|
|
2409
|
-
const
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2647
|
+
function readAnnotateToolResult(record, path2, issues) {
|
|
2648
|
+
rejectUnexpectedFields(record, path2, ["kind", "message", "severity"], issues);
|
|
2649
|
+
const message = readRequiredString(record, `${path2}.message`, issues);
|
|
2650
|
+
const severity = readOptionalString(record, `${path2}.severity`, issues);
|
|
2651
|
+
if (severity !== void 0 && !isMiddlewareAnnotationSeverity(severity)) {
|
|
2652
|
+
issues.push({ path: `${path2}.severity`, message: "expected info or warn" });
|
|
2653
|
+
}
|
|
2654
|
+
if (message === null) return null;
|
|
2655
|
+
const effect = { kind: "annotate_tool_result", message };
|
|
2656
|
+
if (severity !== void 0 && isMiddlewareAnnotationSeverity(severity)) effect.severity = severity;
|
|
2657
|
+
return effect;
|
|
2413
2658
|
}
|
|
2414
|
-
function
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
const turnToolCalls = input.metadata?.turnToolCalls;
|
|
2424
|
-
if (typeof turnToolCalls !== "number" || turnToolCalls <= 0) return [];
|
|
2425
|
-
const activeToolNames = input.metadata?.activeToolNames;
|
|
2426
|
-
if (typeof activeToolNames === "string" && !activeToolNames.split(",").includes(ToolNames.Tasks)) return [];
|
|
2427
|
-
let board;
|
|
2428
|
-
try {
|
|
2429
|
-
board = options.getBoard();
|
|
2430
|
-
} catch {
|
|
2431
|
-
return [];
|
|
2432
|
-
}
|
|
2433
|
-
if (board === null || openTasks(board).length === 0) return [];
|
|
2434
|
-
const message = buildOpenTasksMessage(board);
|
|
2435
|
-
return [
|
|
2436
|
-
{ kind: "request_continuation", message },
|
|
2437
|
-
{ kind: "inject_reminder", message, severity: "warn" }
|
|
2438
|
-
];
|
|
2439
|
-
}
|
|
2440
|
-
};
|
|
2659
|
+
function readBlockTool(record, path2, issues) {
|
|
2660
|
+
rejectUnexpectedFields(record, path2, ["kind", "reason", "severity"], issues);
|
|
2661
|
+
const reason = readRequiredString(record, `${path2}.reason`, issues);
|
|
2662
|
+
const severity = readRequiredString(record, `${path2}.severity`, issues);
|
|
2663
|
+
if (severity !== null && severity !== "hard-block") {
|
|
2664
|
+
issues.push({ path: `${path2}.severity`, message: "expected hard-block" });
|
|
2665
|
+
}
|
|
2666
|
+
if (reason === null || severity !== "hard-block") return null;
|
|
2667
|
+
return { kind: "block_tool", reason, severity };
|
|
2441
2668
|
}
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
return {
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
}
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2669
|
+
function readProtectPath(record, path2, issues) {
|
|
2670
|
+
rejectUnexpectedFields(record, path2, ["kind", "path", "reason"], issues);
|
|
2671
|
+
const protectedPath = readRequiredString(record, `${path2}.path`, issues);
|
|
2672
|
+
const reason = readRequiredString(record, `${path2}.reason`, issues);
|
|
2673
|
+
if (protectedPath === null || reason === null) return null;
|
|
2674
|
+
return { kind: "protect_path", path: protectedPath, reason };
|
|
2675
|
+
}
|
|
2676
|
+
function readRequestContinuation(record, path2, issues) {
|
|
2677
|
+
rejectUnexpectedFields(record, path2, ["kind", "message"], issues);
|
|
2678
|
+
const message = readRequiredString(record, `${path2}.message`, issues);
|
|
2679
|
+
if (message === null) return null;
|
|
2680
|
+
return { kind: "request_continuation", message };
|
|
2681
|
+
}
|
|
2682
|
+
function readRequiredString(record, path2, issues) {
|
|
2683
|
+
const value = record[pathField(path2)];
|
|
2684
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
2685
|
+
issues.push({ path: path2, message: "expected non-empty string" });
|
|
2686
|
+
return null;
|
|
2687
|
+
}
|
|
2688
|
+
return value;
|
|
2689
|
+
}
|
|
2690
|
+
function readOptionalString(record, path2, issues) {
|
|
2691
|
+
const field = pathField(path2);
|
|
2692
|
+
if (!Object.hasOwn(record, field)) return void 0;
|
|
2693
|
+
const value = record[field];
|
|
2694
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
2695
|
+
issues.push({ path: path2, message: "expected non-empty string" });
|
|
2696
|
+
return void 0;
|
|
2697
|
+
}
|
|
2698
|
+
return value;
|
|
2699
|
+
}
|
|
2700
|
+
function rejectUnexpectedFields(record, path2, allowedFields, issues) {
|
|
2701
|
+
const allowed = new Set(allowedFields);
|
|
2702
|
+
for (const field of Object.keys(record).sort((a, b) => a.localeCompare(b))) {
|
|
2703
|
+
if (!allowed.has(field)) issues.push({ path: `${path2}.${field}`, message: "unexpected field" });
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
function pathField(path2) {
|
|
2707
|
+
return path2.slice(path2.lastIndexOf(".") + 1);
|
|
2708
|
+
}
|
|
2709
|
+
function isRecord(value) {
|
|
2710
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2467
2711
|
}
|
|
2468
2712
|
|
|
2469
|
-
// src/domains/middleware/
|
|
2470
|
-
|
|
2471
|
-
var
|
|
2472
|
-
var
|
|
2473
|
-
var
|
|
2474
|
-
var
|
|
2475
|
-
var
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
return typeof value === "string" && value.length > 0 ? value : null;
|
|
2713
|
+
// src/domains/middleware/hooks.ts
|
|
2714
|
+
var USER_HOOK_ORIGIN_ORDER = ["extension", "user", "project", "project.local"];
|
|
2715
|
+
var USER_HOOK_COMMAND_TIMEOUT_DEFAULT_MS = 2e3;
|
|
2716
|
+
var USER_HOOK_COMMAND_TIMEOUT_MIN_MS = 100;
|
|
2717
|
+
var USER_HOOK_COMMAND_TIMEOUT_MAX_MS = 5e3;
|
|
2718
|
+
var USER_HOOK_COMMAND_OUTPUT_MAX_CHARS = 4e3;
|
|
2719
|
+
var USER_HOOK_PROMPT_MAX_CHARS = 2e3;
|
|
2720
|
+
function isRecord2(value) {
|
|
2721
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2479
2722
|
}
|
|
2480
|
-
function
|
|
2481
|
-
|
|
2482
|
-
if (!Array.isArray(paths)) return null;
|
|
2483
|
-
for (const entry of paths) {
|
|
2484
|
-
const path2 = stringValue2(entry);
|
|
2485
|
-
if (path2 !== null) return path2;
|
|
2486
|
-
}
|
|
2487
|
-
return null;
|
|
2723
|
+
function stableHash(value) {
|
|
2724
|
+
return createHash2("sha256").update(canonicalJson(value)).digest("hex").slice(0, 16);
|
|
2488
2725
|
}
|
|
2489
|
-
function
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
blocks.push(`[${byPath.size - WATCHDOG_PATHS_MAX} more changed path(s) omitted]`);
|
|
2495
|
-
break;
|
|
2496
|
-
}
|
|
2497
|
-
blocks.push(`--- ${path2}
|
|
2498
|
-
${diff}`);
|
|
2499
|
-
index += 1;
|
|
2500
|
-
}
|
|
2501
|
-
const whole = blocks.join("\n\n");
|
|
2502
|
-
if (Buffer.byteLength(whole, "utf8") <= maxBytes) return whole;
|
|
2503
|
-
const room = Math.max(0, maxBytes - Buffer.byteLength(WATCHDOG_TRUNCATION_MARKER, "utf8"));
|
|
2504
|
-
const buffer = Buffer.from(whole, "utf8");
|
|
2505
|
-
let cut = Math.min(room, buffer.byteLength);
|
|
2506
|
-
while (cut > 0) {
|
|
2507
|
-
const next = buffer[cut];
|
|
2508
|
-
if (next === void 0 || (next & 192) !== 128) break;
|
|
2509
|
-
cut -= 1;
|
|
2726
|
+
function canonicalJson(value) {
|
|
2727
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
2728
|
+
if (isRecord2(value)) {
|
|
2729
|
+
const keys = Object.keys(value).sort();
|
|
2730
|
+
return `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`).join(",")}}`;
|
|
2510
2731
|
}
|
|
2511
|
-
return
|
|
2732
|
+
return JSON.stringify(value) ?? "null";
|
|
2512
2733
|
}
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
const
|
|
2516
|
-
const
|
|
2517
|
-
|
|
2518
|
-
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
"## Changed paths",
|
|
2522
|
-
paths,
|
|
2523
|
-
"",
|
|
2524
|
-
"## Coalesced diff for this turn",
|
|
2525
|
-
trigger.diff
|
|
2526
|
-
].join("\n");
|
|
2734
|
+
function readStringArray(value) {
|
|
2735
|
+
if (!Array.isArray(value)) return null;
|
|
2736
|
+
const out = [];
|
|
2737
|
+
for (const item of value) {
|
|
2738
|
+
if (typeof item !== "string" || item.length === 0) return null;
|
|
2739
|
+
out.push(item);
|
|
2740
|
+
}
|
|
2741
|
+
return out;
|
|
2527
2742
|
}
|
|
2528
|
-
function
|
|
2529
|
-
const
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
const
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2743
|
+
function normalizeCommandSpec(raw, options, issues) {
|
|
2744
|
+
const argv = readStringArray(raw.argv);
|
|
2745
|
+
if (!argv || argv.length === 0) {
|
|
2746
|
+
issues.push("command.argv must be a non-empty array of non-empty strings");
|
|
2747
|
+
return null;
|
|
2748
|
+
}
|
|
2749
|
+
const spec = {
|
|
2750
|
+
kind: "command",
|
|
2751
|
+
argv,
|
|
2752
|
+
cwd: resolve(options.workspaceRoot),
|
|
2753
|
+
timeoutMs: USER_HOOK_COMMAND_TIMEOUT_DEFAULT_MS,
|
|
2754
|
+
as: "annotate"
|
|
2755
|
+
};
|
|
2756
|
+
if (raw.cwd !== void 0) {
|
|
2757
|
+
if (typeof raw.cwd !== "string" || raw.cwd.length === 0) {
|
|
2758
|
+
issues.push("command.cwd must be a non-empty string");
|
|
2540
2759
|
return null;
|
|
2541
2760
|
}
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
toolCalls = 0;
|
|
2547
|
-
};
|
|
2548
|
-
const fire = (reason) => {
|
|
2549
|
-
if (diffByPath.size === 0) return;
|
|
2550
|
-
if (inFlight) {
|
|
2551
|
-
dropped += 1;
|
|
2552
|
-
return;
|
|
2761
|
+
const resolved = isAbsolute(raw.cwd) ? resolve(raw.cwd) : resolve(options.workspaceRoot, raw.cwd);
|
|
2762
|
+
if (!isWithin(resolved, options.workspaceRoot)) {
|
|
2763
|
+
issues.push("command.cwd must resolve under the workspace root");
|
|
2764
|
+
return null;
|
|
2553
2765
|
}
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
};
|
|
2561
|
-
inFlight = true;
|
|
2562
|
-
let started;
|
|
2563
|
-
try {
|
|
2564
|
-
started = deps.run(trigger);
|
|
2565
|
-
} catch {
|
|
2566
|
-
inFlight = false;
|
|
2567
|
-
return;
|
|
2766
|
+
spec.cwd = resolved;
|
|
2767
|
+
}
|
|
2768
|
+
if (raw.timeoutMs !== void 0) {
|
|
2769
|
+
if (typeof raw.timeoutMs !== "number" || !Number.isFinite(raw.timeoutMs)) {
|
|
2770
|
+
issues.push("command.timeoutMs must be a number");
|
|
2771
|
+
return null;
|
|
2568
2772
|
}
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
}
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
} catch {
|
|
2773
|
+
spec.timeoutMs = Math.min(
|
|
2774
|
+
USER_HOOK_COMMAND_TIMEOUT_MAX_MS,
|
|
2775
|
+
Math.max(USER_HOOK_COMMAND_TIMEOUT_MIN_MS, Math.floor(raw.timeoutMs))
|
|
2776
|
+
);
|
|
2777
|
+
}
|
|
2778
|
+
if (raw.as !== void 0) {
|
|
2779
|
+
if (raw.as !== "annotate" && raw.as !== "reminder") {
|
|
2780
|
+
issues.push('command.as must be "annotate" or "reminder"');
|
|
2578
2781
|
return null;
|
|
2579
2782
|
}
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2783
|
+
spec.as = raw.as;
|
|
2784
|
+
}
|
|
2785
|
+
return spec;
|
|
2786
|
+
}
|
|
2787
|
+
function normalizePromptSpec(raw, issues) {
|
|
2788
|
+
if (typeof raw.message !== "string" || raw.message.trim().length === 0) {
|
|
2789
|
+
issues.push("prompt.message must be a non-empty string");
|
|
2790
|
+
return null;
|
|
2791
|
+
}
|
|
2792
|
+
let severity = "info";
|
|
2793
|
+
if (raw.severity !== void 0) {
|
|
2794
|
+
if (typeof raw.severity !== "string" || !isMiddlewareReminderSeverity(raw.severity)) {
|
|
2795
|
+
issues.push("prompt.severity must be info, advisory, warn, or hard-block");
|
|
2796
|
+
return null;
|
|
2797
|
+
}
|
|
2798
|
+
severity = raw.severity;
|
|
2799
|
+
}
|
|
2800
|
+
return { kind: "prompt", message: raw.message.slice(0, USER_HOOK_PROMPT_MAX_CHARS), severity };
|
|
2801
|
+
}
|
|
2802
|
+
function normalizeEffectSpec(raw, issues) {
|
|
2803
|
+
const result = validateMiddlewareEffect(raw.effect ?? raw, "effect");
|
|
2804
|
+
if (!result.valid) {
|
|
2805
|
+
for (const issue of result.issues) issues.push(`effect: ${issue.message}`);
|
|
2806
|
+
return null;
|
|
2807
|
+
}
|
|
2808
|
+
return { kind: "effect", effect: result.effect };
|
|
2809
|
+
}
|
|
2810
|
+
function isWithin(child, parent) {
|
|
2811
|
+
const rel = resolve(child);
|
|
2812
|
+
const base = resolve(parent);
|
|
2813
|
+
return rel === base || rel.startsWith(`${base}/`);
|
|
2814
|
+
}
|
|
2815
|
+
function normalizeUserHook(raw, source, options) {
|
|
2816
|
+
const issues = [];
|
|
2817
|
+
if (!isRecord2(raw)) {
|
|
2818
|
+
return { issues: ["hook declaration must be an object"] };
|
|
2819
|
+
}
|
|
2820
|
+
if (raw.on === void 0 || typeof raw.on !== "string" || !isMiddlewareHook(raw.on)) {
|
|
2821
|
+
issues.push("on must be one of before_tool, after_tool, turn_start, turn_end, on_compaction");
|
|
2822
|
+
}
|
|
2823
|
+
let tools;
|
|
2824
|
+
if (raw.tools !== void 0) {
|
|
2825
|
+
const parsed = readStringArray(raw.tools);
|
|
2826
|
+
if (!parsed) issues.push("tools must be an array of non-empty tool names");
|
|
2827
|
+
else tools = parsed;
|
|
2828
|
+
}
|
|
2829
|
+
let enabled = true;
|
|
2830
|
+
if (raw.enabled !== void 0) {
|
|
2831
|
+
if (typeof raw.enabled !== "boolean") issues.push("enabled must be a boolean");
|
|
2832
|
+
else enabled = raw.enabled;
|
|
2833
|
+
}
|
|
2834
|
+
let spec = null;
|
|
2835
|
+
if (raw.kind === "command") spec = normalizeCommandSpec(raw, options, issues);
|
|
2836
|
+
else if (raw.kind === "prompt") spec = normalizePromptSpec(raw, issues);
|
|
2837
|
+
else if (raw.kind === "effect") spec = normalizeEffectSpec(raw, issues);
|
|
2838
|
+
else issues.push('kind must be "command", "prompt", or "effect"');
|
|
2839
|
+
if (issues.length > 0 || spec === null || raw.on === void 0 || typeof raw.on !== "string") {
|
|
2840
|
+
return { issues };
|
|
2841
|
+
}
|
|
2842
|
+
const on = raw.on;
|
|
2843
|
+
const hash = stableHash({ on, tools: tools ?? null, spec });
|
|
2844
|
+
const id = typeof raw.id === "string" && raw.id.length > 0 ? raw.id : `${source.sourceId ?? source.origin}.${spec.kind}.${hash}`;
|
|
2845
|
+
const hook = { id, source, hash, on, enabled, spec };
|
|
2846
|
+
if (tools !== void 0) hook.tools = tools;
|
|
2847
|
+
return { hook, issues };
|
|
2848
|
+
}
|
|
2849
|
+
function declarationsArray(declarations) {
|
|
2850
|
+
if (Array.isArray(declarations)) return declarations;
|
|
2851
|
+
if (isRecord2(declarations) && Array.isArray(declarations.hooks)) return declarations.hooks;
|
|
2852
|
+
return [];
|
|
2853
|
+
}
|
|
2854
|
+
function loadUserHooks(batches, options) {
|
|
2855
|
+
const ordered = [...batches].sort(
|
|
2856
|
+
(a, b) => USER_HOOK_ORIGIN_ORDER.indexOf(a.source.origin) - USER_HOOK_ORIGIN_ORDER.indexOf(b.source.origin)
|
|
2857
|
+
);
|
|
2858
|
+
const byId = /* @__PURE__ */ new Map();
|
|
2859
|
+
const overridden = [];
|
|
2860
|
+
const issues = [];
|
|
2861
|
+
for (const batch of ordered) {
|
|
2862
|
+
const declarations = declarationsArray(batch.declarations);
|
|
2863
|
+
declarations.forEach((raw, index) => {
|
|
2864
|
+
const result = normalizeUserHook(raw, batch.source, options);
|
|
2865
|
+
if (!result.hook) {
|
|
2866
|
+
issues.push({ source: batch.source, index, issues: result.issues });
|
|
2867
|
+
return;
|
|
2597
2868
|
}
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
},
|
|
2603
|
-
droppedTriggers: () => dropped,
|
|
2604
|
-
runInFlight: () => inFlight,
|
|
2605
|
-
whenIdle: () => outstanding
|
|
2606
|
-
};
|
|
2607
|
-
function recordMutation(input) {
|
|
2608
|
-
if (input.metadata?.resultKind !== "ok") return;
|
|
2609
|
-
if (input.toolName === void 0 || !MUTATING_TOOL_NAMES.has(input.toolName)) return;
|
|
2610
|
-
const diff = stringValue2(input.toolResultDetails?.diff);
|
|
2611
|
-
if (diff === null) return;
|
|
2612
|
-
diffByPath.set(firstPath(input.toolResultDetails) ?? input.toolName, diff);
|
|
2869
|
+
const existing = byId.get(result.hook.id);
|
|
2870
|
+
if (existing) overridden.push({ winner: batch.source, loser: existing });
|
|
2871
|
+
byId.set(result.hook.id, result.hook);
|
|
2872
|
+
});
|
|
2613
2873
|
}
|
|
2874
|
+
return { hooks: [...byId.values()], overridden, issues };
|
|
2614
2875
|
}
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
init_esm_shims();
|
|
2618
|
-
|
|
2619
|
-
// src/domains/middleware/extension.ts
|
|
2620
|
-
init_esm_shims();
|
|
2621
|
-
|
|
2622
|
-
// src/domains/middleware/snapshot.ts
|
|
2623
|
-
init_esm_shims();
|
|
2624
|
-
function createMiddlewareSnapshot(rules = listMiddlewareRules()) {
|
|
2625
|
-
return {
|
|
2626
|
-
version: 1,
|
|
2627
|
-
rules: rules.map(cloneMiddlewareRule)
|
|
2628
|
-
};
|
|
2876
|
+
function reminderEffect(message, severity) {
|
|
2877
|
+
return severity === "info" ? { kind: "inject_reminder", message } : { kind: "inject_reminder", message, severity };
|
|
2629
2878
|
}
|
|
2630
|
-
function
|
|
2631
|
-
const
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
...
|
|
2879
|
+
function commandEffect(hook, output) {
|
|
2880
|
+
const message = output.slice(0, USER_HOOK_COMMAND_OUTPUT_MAX_CHARS);
|
|
2881
|
+
return hook.as === "reminder" ? { kind: "inject_reminder", message } : { kind: "annotate_tool_result", message };
|
|
2882
|
+
}
|
|
2883
|
+
function userHookToRegistration(hook, deps) {
|
|
2884
|
+
const now = deps.now ?? Date.now;
|
|
2885
|
+
const extension = hook.source.extension === void 0 ? void 0 : receiptExtension(hook.source.extension);
|
|
2886
|
+
const baseReceipt = (input) => ({
|
|
2887
|
+
at: now(),
|
|
2888
|
+
hookId: hook.id,
|
|
2889
|
+
origin: hook.source.origin,
|
|
2890
|
+
sourcePath: hook.source.sourcePath,
|
|
2891
|
+
hash: hook.hash,
|
|
2892
|
+
hook: hook.on,
|
|
2893
|
+
kind: hook.spec.kind,
|
|
2894
|
+
...extension !== void 0 ? { extension: { ...extension } } : {},
|
|
2895
|
+
...input.toolName !== void 0 ? { toolName: input.toolName } : {}
|
|
2647
2896
|
});
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
return createMiddlewareSnapshot(definitions.map((definition) => definition.rule));
|
|
2661
|
-
},
|
|
2662
|
-
registerHook(registration) {
|
|
2663
|
-
if (registeredIds.has(registration.id)) return;
|
|
2664
|
-
registeredIds.add(registration.id);
|
|
2665
|
-
registrations.push(registration);
|
|
2666
|
-
},
|
|
2667
|
-
setDiagnosticSink(sink) {
|
|
2668
|
-
diagnosticSink = sink;
|
|
2897
|
+
const registration = {
|
|
2898
|
+
id: hook.id,
|
|
2899
|
+
description: `${hook.spec.kind} hook from ${hook.source.origin} (${hook.source.sourcePath})`,
|
|
2900
|
+
hooks: [hook.on],
|
|
2901
|
+
evaluate: (input) => {
|
|
2902
|
+
if (!hook.enabled) return [];
|
|
2903
|
+
try {
|
|
2904
|
+
return evaluateHook(hook, deps, baseReceipt(input));
|
|
2905
|
+
} catch {
|
|
2906
|
+
deps.recordReceipt({ ...baseReceipt(input), outcome: "skipped" });
|
|
2907
|
+
return [];
|
|
2908
|
+
}
|
|
2669
2909
|
}
|
|
2670
2910
|
};
|
|
2911
|
+
if (hook.tools !== void 0) registration.toolNames = [...hook.tools];
|
|
2912
|
+
return registration;
|
|
2671
2913
|
}
|
|
2672
|
-
|
|
2673
|
-
// src/domains/middleware/extension.ts
|
|
2674
|
-
function createEnvHookBudgetTracker(env = process.env) {
|
|
2675
|
-
return createHookBudgetTracker({ budgets: resolveHookBudgetsFromEnv(env), ...resolveHookBudgetTunablesFromEnv(env) });
|
|
2676
|
-
}
|
|
2677
|
-
function createMiddlewareBundle(options = {}) {
|
|
2678
|
-
const ruleDefinitions = combineRuleDefinitions(listMiddlewareRuleDefinitions(), options.ruleDefinitions ?? []);
|
|
2679
|
-
const registrations = combineRegistrations(ruleDefinitions, options.registrations ?? []);
|
|
2680
|
-
const registeredIds = new Set(registrations.map((registration) => registration.id));
|
|
2681
|
-
let diagnosticSink = options.onDiagnostic;
|
|
2682
|
-
const budgetTracker = options.budgetTracker ?? createEnvHookBudgetTracker();
|
|
2683
|
-
const contract = {
|
|
2684
|
-
runHook(input) {
|
|
2685
|
-
return runMiddlewareRegistrations(input, registrations, {
|
|
2686
|
-
budgetTracker,
|
|
2687
|
-
...diagnosticSink !== void 0 ? { onDiagnostic: diagnosticSink } : {}
|
|
2688
|
-
});
|
|
2689
|
-
},
|
|
2690
|
-
runAsyncHook(input, priorEffects = []) {
|
|
2691
|
-
return runMiddlewareAsyncRegistrations(input, registrations, priorEffects, {
|
|
2692
|
-
...diagnosticSink !== void 0 ? { onDiagnostic: diagnosticSink } : {}
|
|
2693
|
-
});
|
|
2694
|
-
},
|
|
2695
|
-
listRules() {
|
|
2696
|
-
return ruleDefinitions.map((definition) => cloneMiddlewareRule(definition.rule));
|
|
2697
|
-
},
|
|
2698
|
-
snapshot() {
|
|
2699
|
-
return createMiddlewareSnapshot(ruleDefinitions.map((definition) => definition.rule));
|
|
2700
|
-
},
|
|
2701
|
-
registerHook(registration) {
|
|
2702
|
-
if (registeredIds.has(registration.id)) return;
|
|
2703
|
-
registeredIds.add(registration.id);
|
|
2704
|
-
registrations.push(registration);
|
|
2705
|
-
},
|
|
2706
|
-
setDiagnosticSink(sink) {
|
|
2707
|
-
diagnosticSink = sink;
|
|
2708
|
-
}
|
|
2709
|
-
};
|
|
2914
|
+
function receiptExtension(source) {
|
|
2710
2915
|
return {
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2916
|
+
id: source.provenance.id,
|
|
2917
|
+
scope: source.provenance.scope,
|
|
2918
|
+
canonicalRoot: source.provenance.canonicalRoot,
|
|
2919
|
+
manifestDigest: source.provenance.manifestDigest,
|
|
2920
|
+
contentDigest: source.provenance.contentDigest,
|
|
2921
|
+
declarationsDigest: source.declarationsDigest,
|
|
2922
|
+
generation: source.generation
|
|
2717
2923
|
};
|
|
2718
2924
|
}
|
|
2719
|
-
function
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
seen.add(definition.rule.id);
|
|
2725
|
-
combined.push(definition);
|
|
2925
|
+
function evaluateHook(hook, deps, base) {
|
|
2926
|
+
if (hook.spec.kind === "prompt") {
|
|
2927
|
+
const effect2 = reminderEffect(hook.spec.message, hook.spec.severity);
|
|
2928
|
+
deps.recordReceipt({ ...base, outcome: "emitted", effectKinds: ["inject_reminder"] });
|
|
2929
|
+
return [effect2];
|
|
2726
2930
|
}
|
|
2727
|
-
|
|
2728
|
-
}
|
|
2729
|
-
|
|
2730
|
-
const seen = new Set(ruleDefinitions.map((definition) => definition.rule.id));
|
|
2731
|
-
const combined = ruleDefinitions.map(registrationFromRuleDefinition);
|
|
2732
|
-
for (const registration of coded) {
|
|
2733
|
-
if (seen.has(registration.id)) continue;
|
|
2734
|
-
seen.add(registration.id);
|
|
2735
|
-
combined.push(registration);
|
|
2931
|
+
if (hook.spec.kind === "effect") {
|
|
2932
|
+
deps.recordReceipt({ ...base, outcome: "emitted", effectKinds: [hook.spec.effect.kind] });
|
|
2933
|
+
return [hook.spec.effect];
|
|
2736
2934
|
}
|
|
2737
|
-
|
|
2935
|
+
const result = deps.runCommand(hook.spec.argv, {
|
|
2936
|
+
timeoutMs: hook.spec.timeoutMs,
|
|
2937
|
+
...hook.spec.cwd !== void 0 ? { cwd: hook.spec.cwd } : {}
|
|
2938
|
+
});
|
|
2939
|
+
const output = (result.stdout || result.stderr || "").trim();
|
|
2940
|
+
if (result.timedOut) {
|
|
2941
|
+
deps.recordReceipt({ ...base, outcome: "command-timeout", exitCode: result.code ?? -1, outputChars: output.length });
|
|
2942
|
+
return [];
|
|
2943
|
+
}
|
|
2944
|
+
const outcome = result.code === 0 ? "command-ok" : "command-failed";
|
|
2945
|
+
deps.recordReceipt({ ...base, outcome, exitCode: result.code ?? -1, outputChars: output.length });
|
|
2946
|
+
if (output.length === 0) return [];
|
|
2947
|
+
const effect = commandEffect(hook.spec, output);
|
|
2948
|
+
return [effect];
|
|
2738
2949
|
}
|
|
2739
2950
|
|
|
2740
|
-
// src/domains/middleware/
|
|
2951
|
+
// src/domains/middleware/hooks-io.ts
|
|
2741
2952
|
init_esm_shims();
|
|
2742
|
-
var
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
};
|
|
2953
|
+
var import_yaml = __toESM(require_dist(), 1);
|
|
2954
|
+
import { spawnSync } from "node:child_process";
|
|
2955
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
2956
|
+
import { join } from "node:path";
|
|
2957
|
+
var COMMAND_OUTPUT_MAX_BYTES = 1024 * 1024;
|
|
2958
|
+
function readBatch(source, filePath, fileIssues) {
|
|
2959
|
+
let text;
|
|
2960
|
+
try {
|
|
2961
|
+
text = readFileSync3(filePath, "utf8");
|
|
2962
|
+
} catch {
|
|
2963
|
+
return null;
|
|
2964
|
+
}
|
|
2965
|
+
try {
|
|
2966
|
+
const parsed = (0, import_yaml.parse)(text);
|
|
2967
|
+
return { source, declarations: parsed ?? [] };
|
|
2968
|
+
} catch (err) {
|
|
2969
|
+
fileIssues.push({
|
|
2970
|
+
source,
|
|
2971
|
+
message: `failed to parse ${filePath}: ${err instanceof Error ? err.message : String(err)}`
|
|
2972
|
+
});
|
|
2973
|
+
return null;
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
function readHookSources(options) {
|
|
2977
|
+
const fileIssues = [];
|
|
2978
|
+
const batches = [];
|
|
2979
|
+
const captured = options.capturedSources;
|
|
2980
|
+
for (const entry of captured?.sources ?? []) {
|
|
2981
|
+
const source = {
|
|
2982
|
+
origin: "extension",
|
|
2983
|
+
sourcePath: `${entry.provenance.id}:hooks.yaml`,
|
|
2984
|
+
sourceId: entry.provenance.id,
|
|
2985
|
+
extension: {
|
|
2986
|
+
provenance: entry.provenance,
|
|
2987
|
+
generation: captured?.generation ?? 0,
|
|
2988
|
+
declarationsDigest: entry.declarationsDigest
|
|
2989
|
+
}
|
|
2990
|
+
};
|
|
2991
|
+
if (entry.parseError !== void 0) {
|
|
2992
|
+
fileIssues.push({ source, message: `failed to parse ${source.sourcePath}: ${entry.parseError}` });
|
|
2993
|
+
continue;
|
|
2994
|
+
}
|
|
2995
|
+
batches.push({ source, declarations: entry.declarations });
|
|
2996
|
+
}
|
|
2997
|
+
const projectBatch = readBatch(
|
|
2998
|
+
{ origin: "project", sourcePath: ".clio-coder/hooks.yaml" },
|
|
2999
|
+
join(options.cwd, ".clio-coder", "hooks.yaml"),
|
|
3000
|
+
fileIssues
|
|
3001
|
+
);
|
|
3002
|
+
if (projectBatch) batches.push(projectBatch);
|
|
3003
|
+
const localBatch = readBatch(
|
|
3004
|
+
{ origin: "project.local", sourcePath: ".clio-coder/hooks.local.yaml" },
|
|
3005
|
+
join(options.cwd, ".clio-coder", "hooks.local.yaml"),
|
|
3006
|
+
fileIssues
|
|
3007
|
+
);
|
|
3008
|
+
if (localBatch) batches.push(localBatch);
|
|
3009
|
+
return { batches, fileIssues };
|
|
3010
|
+
}
|
|
3011
|
+
function buildUserHookRegistrations(options) {
|
|
3012
|
+
const workspaceRoot = options.workspaceRoot ?? options.cwd;
|
|
3013
|
+
const readOptions = { cwd: options.cwd };
|
|
3014
|
+
if (options.capturedSources !== void 0) readOptions.capturedSources = options.capturedSources;
|
|
3015
|
+
const { batches, fileIssues } = readHookSources(readOptions);
|
|
3016
|
+
const loaded = loadUserHooks(batches, { workspaceRoot });
|
|
3017
|
+
const runCommand = options.runCommand ?? spawnSyncCommandRunner();
|
|
3018
|
+
const registrations = loaded.hooks.map(
|
|
3019
|
+
(hook) => userHookToRegistration(hook, {
|
|
3020
|
+
recordReceipt: options.recordReceipt,
|
|
3021
|
+
runCommand,
|
|
3022
|
+
...options.now !== void 0 ? { now: options.now } : {}
|
|
3023
|
+
})
|
|
3024
|
+
);
|
|
3025
|
+
return { ...loaded, fileIssues, registrations };
|
|
3026
|
+
}
|
|
3027
|
+
function spawnSyncCommandRunner() {
|
|
3028
|
+
return (argv, options) => {
|
|
3029
|
+
const [command, ...args] = argv;
|
|
3030
|
+
const attribution = withManagedGitCommitAttributionEnvironment(process.env, {
|
|
3031
|
+
cwd: options.cwd ?? process.cwd(),
|
|
3032
|
+
evidence: {}
|
|
3033
|
+
});
|
|
3034
|
+
reportCommitAttributionDiagnostic(attribution.diagnostic);
|
|
3035
|
+
const result = spawnSync(command ?? "", args, {
|
|
3036
|
+
...options.cwd !== void 0 ? { cwd: options.cwd } : {},
|
|
3037
|
+
timeout: options.timeoutMs,
|
|
3038
|
+
maxBuffer: COMMAND_OUTPUT_MAX_BYTES,
|
|
3039
|
+
encoding: "utf8",
|
|
3040
|
+
shell: false,
|
|
3041
|
+
env: attribution.env
|
|
3042
|
+
});
|
|
3043
|
+
const timedOut = result.error !== void 0 && result.error.code === "ETIMEDOUT";
|
|
3044
|
+
return {
|
|
3045
|
+
code: result.status,
|
|
3046
|
+
timedOut,
|
|
3047
|
+
stdout: typeof result.stdout === "string" ? result.stdout : "",
|
|
3048
|
+
stderr: typeof result.stderr === "string" ? result.stderr : ""
|
|
3049
|
+
};
|
|
3050
|
+
};
|
|
3051
|
+
}
|
|
2746
3052
|
|
|
2747
3053
|
// src/domains/middleware/index.ts
|
|
2748
3054
|
var MiddlewareDomainModule = {
|
|
@@ -2754,15 +3060,18 @@ export {
|
|
|
2754
3060
|
MIDDLEWARE_HOOK_TEXT_MAX_CHARS,
|
|
2755
3061
|
shouldRequestStalledTurnContinuation,
|
|
2756
3062
|
writeMiddlewareDiagnosticToStderr,
|
|
3063
|
+
formatRegistrationConflict,
|
|
2757
3064
|
createMiddlewareContractFromSnapshot,
|
|
2758
3065
|
createReadOnlyExplorationNudgeRegistration,
|
|
2759
3066
|
createUnbackedWorkerClaimRegistration,
|
|
2760
3067
|
openDetachedBatchViews,
|
|
2761
3068
|
createDetachedDispatchNudgeRegistration,
|
|
3069
|
+
HOOK_RECEIPT_LOG_CAPACITY,
|
|
2762
3070
|
createHookReceiptLog,
|
|
3071
|
+
readPersistedHookReceipts,
|
|
2763
3072
|
loadUserHooks,
|
|
2764
3073
|
readHookSources,
|
|
2765
|
-
|
|
3074
|
+
buildUserHookRegistrations,
|
|
2766
3075
|
createSkillsReminderRegistration,
|
|
2767
3076
|
createMarketplaceOfferRegistration,
|
|
2768
3077
|
createMemoryInterventionRegistration,
|
|
@@ -2775,4 +3084,4 @@ export {
|
|
|
2775
3084
|
createWatchdogRegistration,
|
|
2776
3085
|
MiddlewareDomainModule
|
|
2777
3086
|
};
|
|
2778
|
-
//# sourceMappingURL=chunk-
|
|
3087
|
+
//# sourceMappingURL=chunk-2UG5F4C5.js.map
|