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