@iowarp/clio-coder 0.5.0 → 0.5.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/.claude-plugin/marketplace.json +4 -4
- package/CHANGELOG.md +35 -0
- package/CONTRIBUTING.md +9 -0
- package/README.md +12 -9
- package/ROADMAP.md +30 -5
- package/dist/{acp-KS7ARGV6.js → acp-F6PGOFZ4.js} +2 -2
- package/dist/{agents-YOGJ2YEJ.js → agents-ZHP4R6EX.js} +22 -21
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-CEDCO6O6.js → auth-N75U2H3Z.js} +4 -4
- package/dist/{chunk-D3KWFOUT.js → chunk-23UIXH6Z.js} +3 -3
- package/dist/{chunk-ZR5YA6ZF.js → chunk-36XVK5P5.js} +5 -5
- package/dist/{chunk-AVSR2HKI.js → chunk-3DZ4LPLS.js} +6 -6
- package/dist/{chunk-LYJJ6546.js → chunk-3GSPCWFX.js} +12 -12
- package/dist/{chunk-CRHPCPZP.js → chunk-4KC4CHKJ.js} +2 -2
- package/dist/{chunk-4VDHLCF5.js → chunk-4WJBWKMI.js} +2 -2
- package/dist/{chunk-KBUTSJSR.js → chunk-4XBYHXGK.js} +2 -2
- package/dist/{chunk-6W75D37F.js → chunk-53P775GE.js} +6 -3
- package/dist/{chunk-XZWSP67B.js → chunk-5APYAXCB.js} +2 -2
- package/dist/{chunk-D65E25JG.js → chunk-67HPYNTJ.js} +2 -2
- package/dist/{chunk-5JQR5B4D.js → chunk-6KV4KCQZ.js} +4 -4
- package/dist/{chunk-V4XJWDHR.js → chunk-6UKFRTXI.js} +2 -1
- package/dist/{chunk-NDPF2N2L.js → chunk-7DW7CJW2.js} +2 -2
- package/dist/{chunk-5KQEUIWM.js → chunk-7LUEQODV.js} +4 -4
- package/dist/{chunk-LNOXCGN2.js → chunk-7UFDIOBT.js} +3 -3
- package/dist/{chunk-DXSETN72.js → chunk-7WUGNN43.js} +2 -2
- package/dist/{chunk-V35KZDUZ.js → chunk-AA3GRZWS.js} +2 -2
- package/dist/{chunk-X4CEFOSG.js → chunk-ADO3OQIC.js} +4 -4
- package/dist/{chunk-UX2L5CUD.js → chunk-AI36V3ON.js} +3 -3
- package/dist/{chunk-B7GY6QER.js → chunk-B26OUMJW.js} +2 -2
- package/dist/{chunk-LAR7DX5E.js → chunk-B5TASTCK.js} +153 -62
- package/dist/{chunk-TGWZXJ7L.js → chunk-BEMQK5Q7.js} +92 -84
- package/dist/{chunk-RNDCROSG.js → chunk-CNXU7TR2.js} +3 -3
- package/dist/{chunk-3JLTRPN7.js → chunk-CVMUXT64.js} +35 -66
- package/dist/{chunk-733ZBT5P.js → chunk-E74MJF6G.js} +4 -4
- package/dist/{chunk-T2MDZES3.js → chunk-EBMTBE76.js} +3 -3
- package/dist/{chunk-OZ76TLGU.js → chunk-EEITU7S6.js} +2 -2
- package/dist/{chunk-GRHOXDKB.js → chunk-ELDJWJKM.js} +97 -14
- package/dist/{chunk-GCQFN7YI.js → chunk-EMYEQLWO.js} +2 -2
- package/dist/{chunk-LTWH2ACR.js → chunk-EQPFEDFV.js} +222 -127
- package/dist/{chunk-TDGYTWM6.js → chunk-F4I3H56H.js} +2 -2
- package/dist/chunk-F6KCJO3U.js +523 -0
- package/dist/{chunk-FGSQHUCK.js → chunk-F6VKWTTY.js} +5 -5
- package/dist/{chunk-UJD5MPP6.js → chunk-FLNXQQ5B.js} +2 -2
- package/dist/{chunk-EX57VVWW.js → chunk-FOSRJ2VZ.js} +4 -4
- package/dist/{chunk-ZARZ4POR.js → chunk-G5ZDQQE3.js} +3 -3
- package/dist/{chunk-HQVN7G4F.js → chunk-G62JBPXN.js} +7 -7
- package/dist/{chunk-ZJPORZTC.js → chunk-GIAI7A2K.js} +2 -2
- package/dist/chunk-GPTR6OTU.js +2551 -0
- package/dist/{chunk-EZD2BXGP.js → chunk-GUFQPTQK.js} +2 -2
- package/dist/{chunk-OPD7GL6F.js → chunk-H6FQVNZC.js} +8 -7
- package/dist/chunk-H7REJ7L5.js +80 -0
- package/dist/{chunk-EYTLV3W3.js → chunk-HTWTORF7.js} +2 -2
- package/dist/{chunk-DF6PI6GN.js → chunk-I6UKQPGS.js} +3 -3
- package/dist/{chunk-ISHUS7HC.js → chunk-JRW236JT.js} +3 -3
- package/dist/{chunk-DE2LU267.js → chunk-KKAJGT3B.js} +3 -2
- package/dist/{chunk-OV62D6KO.js → chunk-KQMYL5GR.js} +3 -3
- package/dist/{chunk-QDUUOZ3S.js → chunk-KWMFC7FN.js} +3 -3
- package/dist/{chunk-R7FGRZNF.js → chunk-LXYHCCIZ.js} +9 -9
- package/dist/{chunk-NFSK2VNU.js → chunk-LZMTVVN4.js} +2 -2
- package/dist/{chunk-62UK7AGW.js → chunk-M5PMRZSR.js} +397 -44
- package/dist/chunk-MMZMM6SW.js +131 -0
- package/dist/{chunk-FFYTC2KQ.js → chunk-MNABNEBX.js} +2 -2
- package/dist/{chunk-XMDBTYCH.js → chunk-N3CWOGVF.js} +3 -3
- package/dist/{chunk-VYRQHORQ.js → chunk-NMQSO6Z6.js} +4 -3
- package/dist/{chunk-R2UUNSNQ.js → chunk-O3ZAF7UY.js} +18 -5
- package/dist/{chunk-4SYHGVDE.js → chunk-OQNCLZI4.js} +4 -4
- package/dist/{chunk-EG4ARXPC.js → chunk-OX6QTA4N.js} +3 -3
- package/dist/{chunk-P4GNZPXO.js → chunk-OYUS3UZP.js} +2 -2
- package/dist/{chunk-OQ2EOAHA.js → chunk-PF36KOGI.js} +291 -112
- package/dist/{chunk-55RMDDFM.js → chunk-PGQG7ZMW.js} +2 -2
- package/dist/{chunk-CBAQPTDU.js → chunk-PIU6BXXW.js} +2 -2
- package/dist/{chunk-EKV4UBCM.js → chunk-PZBQUJ2F.js} +2 -2
- package/dist/{chunk-7RET77VB.js → chunk-Q2HKY32Y.js} +2 -2
- package/dist/{chunk-U6YTUVOX.js → chunk-Q4RMNWMZ.js} +4 -4
- package/dist/chunk-Q6FRR3AQ.js +48 -0
- package/dist/{chunk-6RJMYYIE.js → chunk-QJSIAJBY.js} +3 -3
- package/dist/{chunk-DOUISWEE.js → chunk-QOJ4ERMU.js} +5 -5
- package/dist/{chunk-IFPDAD7R.js → chunk-RWSRKXLT.js} +3 -3
- package/dist/{chunk-T33IYTZM.js → chunk-SAASVMIY.js} +3 -3
- package/dist/{chunk-2DTIWSAW.js → chunk-SEQDH6JC.js} +3 -3
- package/dist/{chunk-BRCOSE7O.js → chunk-SZUO5BSE.js} +2 -2
- package/dist/{chunk-HOEDA42N.js → chunk-T7HZ7KJF.js} +2 -2
- package/dist/{chunk-OJXOA6YU.js → chunk-TL6LNERH.js} +2 -2
- package/dist/{chunk-UCINIRHW.js → chunk-TPCZXWLT.js} +3 -3
- package/dist/{chunk-ZUKUCZYZ.js → chunk-TQ2KTH4A.js} +2 -2
- package/dist/{chunk-4WOOTKFD.js → chunk-U3FETNQB.js} +341 -20
- package/dist/{chunk-5B3RTYAK.js → chunk-UGDSB4AN.js} +2 -2
- package/dist/{chunk-VKHLUZNO.js → chunk-UZ7YBL43.js} +3 -3
- package/dist/{chunk-QNUQ7K7D.js → chunk-VKMSEO7Y.js} +4 -4
- package/dist/{chunk-IN7DGBVS.js → chunk-VLX5VZ35.js} +5 -5
- package/dist/{chunk-SJGS3GDI.js → chunk-WAGBMMNX.js} +5 -5
- package/dist/{chunk-BFOSV5EZ.js → chunk-WX2YCH7F.js} +2 -2
- package/dist/{chunk-3LT34CAM.js → chunk-XBIGUILU.js} +32 -17
- package/dist/{chunk-XDHUDE5K.js → chunk-XKYBFRWR.js} +5 -5
- package/dist/{chunk-U5QU5ZOD.js → chunk-XYPWFSU5.js} +3 -3
- package/dist/{chunk-YBUECLAF.js → chunk-YEJQDODA.js} +2 -2
- package/dist/{chunk-YQ6XEFVK.js → chunk-Z74OGONW.js} +39 -7
- package/dist/{chunk-I4ELN5BX.js → chunk-ZTNOEJRI.js} +2 -2
- package/dist/cli/index.js +26 -26
- package/dist/{clio-K2PBVCEM.js → clio-4IKFE25F.js} +2 -2
- package/dist/{clio-context-tools-GR4OHFSO.js → clio-context-tools-OHJXYX4R.js} +25 -23
- package/dist/{code-nav-4X3OI6DK.js → code-nav-XPM3MY5I.js} +7 -7
- package/dist/{config-Q5J7UBAB.js → config-343YS25T.js} +43 -41
- package/dist/{config-graph-P3555574.js → config-graph-WI263KDX.js} +43 -41
- package/dist/{configure-KIJ32ZO6.js → configure-GZDG4HER.js} +21 -21
- package/dist/{context-LFBQOSXT.js → context-4OFO7N7Y.js} +12 -12
- package/dist/{context-WBH3KSVR.js → context-JLK2RLOG.js} +42 -40
- package/dist/{context-NNLBES2Y.js → context-O6DIWCF6.js} +23 -21
- package/dist/{context-clear-YOHMQ6SQ.js → context-clear-MR7KOGQI.js} +42 -40
- package/dist/{context-working-set-MRAEVDNV.js → context-working-set-YZAD3KWO.js} +14 -13
- package/dist/{data-tool-JJPBGMQJ.js → data-tool-EKD7DFHG.js} +7 -7
- package/dist/{detail-RHJOTGN4.js → detail-YZPHFEVJ.js} +43 -41
- package/dist/{dispatch-runner-NC7PYNIO.js → dispatch-runner-VKVXVTWJ.js} +44 -42
- package/dist/{doctor-S5KZ2GTN.js → doctor-53AVQXAO.js} +17 -16
- package/dist/{doctor-deep-2EJQSHIZ.js → doctor-deep-BLRB2NCG.js} +5 -5
- package/dist/{eval-3D6M36N7.js → eval-P3BQSLQE.js} +21 -20
- package/dist/{evidence-XNVPPLVK.js → evidence-6LLI6SZH.js} +44 -42
- package/dist/{evidence-6IYN52ET.js → evidence-HTN3JLUO.js} +42 -40
- package/dist/{evidence-JVECPI3F.js → evidence-NJRE3R5H.js} +42 -40
- package/dist/{evolve-TLPUDJDM.js → evolve-WWZHJVJP.js} +42 -40
- package/dist/{fleet-WKVBF5SZ.js → fleet-GGZ4BVJE.js} +64 -62
- package/dist/{fleet-6RJWIOEO.js → fleet-QG3IIWWM.js} +44 -42
- package/dist/{fleet-commands-L6WFT7KZ.js → fleet-commands-J2ZIKISQ.js} +7 -7
- package/dist/{fleet-decisions-BTQMWU5A.js → fleet-decisions-Y2Q7375Q.js} +8 -8
- package/dist/{fleet-graph-553DZ67Q.js → fleet-graph-TCBCCCBP.js} +11 -11
- package/dist/{fleet-inspect-236FZPRB.js → fleet-inspect-KOLKBC56.js} +44 -42
- package/dist/{fleet-preflight-NZQHABWO.js → fleet-preflight-44UNCC4Y.js} +27 -25
- package/dist/{fleet-validate-7NLAE26R.js → fleet-validate-K2FXRZVA.js} +13 -13
- package/dist/{fleet-verify-E3RO6A5W.js → fleet-verify-AS444WDW.js} +42 -40
- package/dist/{fleet-view-KMZQY7YJ.js → fleet-view-X4JU4J6O.js} +43 -41
- package/dist/gui/ops-worker.js +9 -9
- package/dist/gui/reads-worker.js +9 -8
- package/dist/{init-DV5B6HQG.js → init-VSOWFHVL.js} +54 -52
- package/dist/{interactive-OXGYZYAS.js → interactive-JRCBENLZ.js} +1733 -577
- package/dist/{interop-J7UOBOSF.js → interop-JR6BCY7R.js} +10 -10
- package/dist/{inventory-OEGIL6PY.js → inventory-3JUGLVJI.js} +43 -41
- package/dist/{library-ROZAMUIL.js → library-7BOH3Y7N.js} +15 -15
- package/dist/{library-B3UPGY4V.js → library-7PFONXUI.js} +9 -9
- package/dist/{library-MXQA5DLT.js → library-TUYZMFIA.js} +7 -7
- package/dist/{library-import-2LTWOVBD.js → library-import-TJVXKLDK.js} +10 -10
- package/dist/{library-inventory-LJ3XHLWO.js → library-inventory-MD3AYTP4.js} +9 -9
- package/dist/{library-validation-DARHHUNW.js → library-validation-ZDLITEA3.js} +7 -7
- package/dist/{mcp-2GSPYQ54.js → mcp-EFCWUPUR.js} +3 -3
- package/dist/{memory-J3STUUIK.js → memory-Q2YVOKK5.js} +42 -40
- package/dist/{models-Y3GMFJ2V.js → models-M7BOCW7F.js} +17 -16
- package/dist/{monitor-LHNH577J.js → monitor-2HD3MC67.js} +47 -45
- package/dist/{orchestrator-DTAPXULA.js → orchestrator-OUW3ZN6Y.js} +1894 -226
- package/dist/{panes-A743ONZ5.js → panes-TTPPSYCL.js} +3 -3
- package/dist/{preload-OJBMAIXF.js → preload-A47C2NUF.js} +42 -40
- package/dist/{providers-CMETPZNS.js → providers-T43W7EL4.js} +4 -4
- package/dist/{resources-FY6XYFN2.js → resources-TP6X2V6W.js} +20 -11
- package/dist/{run-H64MEVTA.js → run-LDSBPMCS.js} +61 -59
- package/dist/{share-CAFMMQLM.js → share-URHKKMHR.js} +9 -9
- package/dist/{skills-ERKQUC7L.js → skills-E477MOOO.js} +13 -11
- package/dist/{skills-eval-CQ23MYQK.js → skills-eval-AIBR2L7H.js} +583 -93
- package/dist/{skills-inventory-TVUYJDVN.js → skills-inventory-HLMKY3R5.js} +13 -11
- package/dist/{slash-commands-ROG3KIFJ.js → slash-commands-LIIZKA5Y.js} +28 -26
- package/dist/{startup-background-KDWCQMWT.js → startup-background-JR2KDK6R.js} +43 -41
- package/dist/{steer-KKYXJSH4.js → steer-D7RTAPCM.js} +3 -3
- package/dist/{system-ZDW44ZGX.js → system-X2P5N4IY.js} +14 -13
- package/dist/{targets-AJWA6E7Z.js → targets-VFFAKUFM.js} +25 -24
- package/dist/{tasks-6L2JJRC5.js → tasks-ERAWZA22.js} +6 -6
- package/dist/{terminal-lease-YFUSDPAD.js → terminal-lease-V2N2BIKQ.js} +2 -2
- package/dist/{trace-LA5MVR4Y.js → trace-MA5BT7XY.js} +3 -3
- package/dist/{usage-XSILDHKF.js → usage-WYFY2L6M.js} +50 -48
- package/dist/{verifiers-ECA5GIHL.js → verifiers-TYIW2XKF.js} +7 -7
- package/dist/{verify-R6QQCCEB.js → verify-242UFYTP.js} +6 -6
- package/dist/{web-fetch-PVZNMBOB.js → web-fetch-LRAGYU6Y.js} +3 -3
- package/dist/{wiki-generate-RP22WP5K.js → wiki-generate-OFKOX2T6.js} +53 -51
- package/dist/worker/entry.js +31 -29
- package/docs/architecture/architecture.md +1 -0
- package/docs/architecture/context-engine.md +4 -2
- package/docs/architecture/observability.md +6 -4
- package/docs/architecture/prompt-envelope-and-tools.md +1 -1
- package/docs/architecture/session-lifecycle.md +1 -1
- package/docs/architecture/tui-design.md +12 -9
- package/docs/gui/parity/02-slash-and-surfaces.md +1 -1
- package/docs/guide/commands-and-modes.md +91 -5
- package/docs/guide/configuration-and-targets.md +2 -2
- package/docs/guide/context-continuity.md +49 -0
- package/docs/guide/proactive-memory.md +6 -3
- package/library/registry.yaml +292 -32
- package/library/skills/README.md +27 -2
- package/library/skills/context/context-handoff/SKILL.md +10 -4
- package/library/skills/meta/clio-coder-dev/SKILL.md +92 -88
- package/library/skills/meta/clio-coder-dev/evals.md +51 -46
- package/library/skills/meta/clio-coder-dev/plugin.json +2 -2
- package/library/skills/meta/clio-coder-dev/references/change-map.md +49 -0
- package/library/skills/meta/clio-coder-test/SKILL.md +89 -142
- package/library/skills/meta/clio-coder-test/evals.md +58 -62
- package/library/skills/meta/clio-coder-test/plugin.json +2 -2
- package/library/skills/meta/clio-coder-test/references/harness.md +3 -3
- package/library/skills/meta/clio-coder-test/references/lifecycle-validation.md +30 -0
- package/library/skills/meta/clio-coder-test/references/test-map.md +60 -89
- package/library/skills/registry.yaml +5 -5
- package/library/skills/skill-marketplace.json +12 -14
- package/package.json +1 -1
- package/src/cli/skills-eval.ts +1064 -54
- package/src/cli/usage.ts +2 -2
- package/src/core/clio-repo.ts +3 -0
- package/src/core/tool-names.ts +1 -0
- package/src/domains/context/budget/inspection.ts +9 -0
- package/src/domains/context/budget/live-view.ts +418 -0
- package/src/domains/context/budget/pressure.ts +393 -0
- package/src/domains/context/budget/request-fit.ts +15 -0
- package/src/domains/evidence/build.ts +24 -0
- package/src/domains/gateway/mcp/client.ts +1 -1
- package/src/domains/memory/commit-state.ts +160 -0
- package/src/domains/memory/operations.ts +18 -10
- package/src/domains/memory/prompt-cache.ts +95 -0
- package/src/domains/memory/prompt-section.ts +50 -7
- package/src/domains/memory/relevance.ts +93 -0
- package/src/domains/memory/restoration.ts +78 -0
- package/src/domains/memory/store.ts +39 -1
- package/src/domains/middleware/memory-intervention.ts +116 -24
- package/src/domains/observability/background-memory-usage.ts +1 -1
- package/src/domains/observability/cost.ts +3 -3
- package/src/domains/observability/extension.ts +1 -1
- package/src/domains/observability/metrics.ts +1 -1
- package/src/domains/prompts/compiler.ts +1 -1
- package/src/domains/prompts/extension.ts +38 -1
- package/src/domains/quota/anthropic-max-provider.ts +134 -0
- package/src/domains/quota/anthropic-usage.ts +220 -0
- package/src/domains/quota/antigravity-provider.ts +339 -0
- package/src/domains/quota/cache.ts +87 -0
- package/src/domains/quota/claude-code-provider.ts +169 -0
- package/src/domains/quota/codex-provider.ts +237 -0
- package/src/domains/quota/presentation.ts +240 -0
- package/src/domains/quota/registry.ts +23 -0
- package/src/domains/quota/service.ts +93 -0
- package/src/domains/quota/summary-feed.ts +86 -0
- package/src/domains/quota/types.ts +84 -0
- package/src/domains/resources/index.ts +11 -0
- package/src/domains/resources/skills/catalog-view.ts +571 -0
- package/src/domains/resources/skills/lexical-match.ts +136 -0
- package/src/domains/resources/skills/loader.ts +38 -0
- package/src/domains/resources/skills/promotion.ts +1 -55
- package/src/domains/resources/skills/provenance-pin.ts +50 -20
- package/src/domains/safety/action-classifier.ts +1 -0
- package/src/domains/session/compaction/branch-summary.ts +4 -1
- package/src/domains/session/compaction/compact.ts +18 -2
- package/src/domains/session/compaction/cut-point.ts +20 -1
- package/src/domains/session/compaction/tokens.ts +48 -3
- package/src/domains/session/context-accounting.ts +8 -1
- package/src/domains/session/continuity/carry.ts +59 -0
- package/src/domains/session/continuity/contract.ts +592 -0
- package/src/domains/session/continuity/evidence.ts +290 -0
- package/src/domains/session/continuity/fold.ts +1075 -0
- package/src/domains/session/continuity/note.ts +79 -0
- package/src/domains/session/continuity/operator-request.ts +104 -0
- package/src/domains/session/continuity/persistence.ts +408 -0
- package/src/domains/session/continuity/ports.ts +231 -0
- package/src/domains/session/continuity/projection.ts +538 -0
- package/src/domains/session/continuity/validate.ts +352 -0
- package/src/domains/session/entries.ts +50 -3
- package/src/domains/session/index.ts +48 -0
- package/src/domains/session/migrations/index.ts +7 -3
- package/src/domains/session/tree/fork.ts +15 -1
- package/src/domains/session/usage.ts +2 -2
- package/src/engine/acp/commands.ts +1 -1
- package/src/engine/agent.ts +133 -9
- package/src/engine/session.ts +13 -6
- package/src/entry/orchestrator.ts +116 -33
- package/src/interactive/chat-loop-messages.ts +2 -2
- package/src/interactive/chat-loop.ts +219 -63
- package/src/interactive/chat-renderer.ts +99 -8
- package/src/interactive/context-overlay.ts +24 -3
- package/src/interactive/continuity-controller.ts +526 -0
- package/src/interactive/dispatch-board.ts +21 -3
- package/src/interactive/footer/dashboard.ts +25 -1
- package/src/interactive/footer/key-hints.ts +2 -2
- package/src/interactive/footer/pages.ts +70 -27
- package/src/interactive/footer/widgets.ts +32 -7
- package/src/interactive/footer-panel.ts +1 -1
- package/src/interactive/interactive-application.ts +7 -2
- package/src/interactive/interactive-input-runtime.ts +2 -2
- package/src/interactive/interactive-presentation.ts +15 -0
- package/src/interactive/interactive-slash-runtime.ts +45 -8
- package/src/interactive/interactive-tickers.ts +7 -1
- package/src/interactive/model-session-replay.ts +130 -3
- package/src/interactive/output-reserve.ts +35 -0
- package/src/interactive/overlay-general-openers.ts +12 -8
- package/src/interactive/overlay-key-routing.ts +6 -9
- package/src/interactive/overlay-lifecycle.ts +10 -6
- package/src/interactive/overlay-session-lifecycle.ts +21 -14
- package/src/interactive/quota-view.ts +229 -0
- package/src/interactive/session-last-turn.ts +1 -1
- package/src/interactive/session-transcript.ts +2 -1
- package/src/interactive/session-usage-reseed.ts +2 -2
- package/src/interactive/side-question.ts +2 -2
- package/src/interactive/slash-commands.ts +28 -9
- package/src/interactive/turn-context.ts +686 -63
- package/src/interactive/turn-middleware.ts +39 -6
- package/src/interactive/turn-persistence.ts +15 -2
- package/src/interactive/turn-prewarm.ts +1 -1
- package/src/interactive/turn-runtime.ts +48 -5
- package/src/interactive/{cost-overlay.ts → usage-overlay.ts} +121 -33
- package/src/interactive/welcome-dashboard.ts +26 -3
- package/src/tools/agent-tools.ts +3 -1
- package/src/tools/bootstrap.ts +6 -0
- package/src/tools/builtin-tool-catalog.ts +9 -0
- package/src/tools/context/index.ts +148 -113
- package/src/tools/context/surface.ts +11 -7
- package/src/tools/core-bootstrap.ts +4 -0
- package/src/tools/observation.ts +8 -2
- package/src/tools/policy.ts +3 -0
- package/src/tools/self-compact.ts +31 -0
- package/src/tools/surface.ts +1 -0
- package/src/tools/tasks.ts +1 -1
- package/dist/chunk-CTFPFW3H.js +0 -44
- package/dist/chunk-EOJPDNUP.js +0 -1325
- package/dist/chunk-I4Y4WKZR.js +0 -386
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { canonicalMemoryRepositoryIdentity } from "./operations.js";
|
|
3
|
+
import { buildMemoryPromptSection, type MemoryPromptOptions } from "./prompt-section.js";
|
|
4
|
+
import { MEMORY_RELEVANCE_VERSION } from "./relevance.js";
|
|
5
|
+
import { readMemoryStoreSnapshot } from "./store.js";
|
|
6
|
+
|
|
7
|
+
/** Host-owned attempt identity, distinct from the ledger ID allocated after preflight. */
|
|
8
|
+
export interface MemoryPromptRequest {
|
|
9
|
+
readonly turnId: string | null;
|
|
10
|
+
readonly sessionAuthority: string;
|
|
11
|
+
readonly cwd: string;
|
|
12
|
+
readonly targetId: string;
|
|
13
|
+
readonly runtimeId: string;
|
|
14
|
+
readonly modelId: string;
|
|
15
|
+
readonly taskText: string;
|
|
16
|
+
readonly activePaths: readonly string[];
|
|
17
|
+
readonly activeSymbols?: readonly string[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface MemoryPromptReaderOptions {
|
|
21
|
+
getDataDir: () => string;
|
|
22
|
+
/** Explicit experiment only; production leaves relevance off. */
|
|
23
|
+
experimentalRelevance?: boolean;
|
|
24
|
+
selection?: Pick<MemoryPromptOptions, "scopes" | "tokenBudget" | "maxItems">;
|
|
25
|
+
/** Source seam for deterministic read-count tests; production reads the real bounded store. */
|
|
26
|
+
readStore?: typeof readMemoryStoreSnapshot;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Single-entry immutable text cache. A new prepared operator attempt rereads the
|
|
31
|
+
* store; ordinary external edits become visible at the next attempt. Within an
|
|
32
|
+
* attempt/its admitted continuations, selection is frozen, except authority
|
|
33
|
+
* changes (session/branch, data root, canonical repo, actual target/runtime/model).
|
|
34
|
+
* Calls without a prepared turn (boot/reset prewarm) read each time. Later
|
|
35
|
+
* prewarm can reuse the preceding snapshot; a fresh attempt always rereads.
|
|
36
|
+
*/
|
|
37
|
+
export function createMemoryPromptReader(options: MemoryPromptReaderOptions): (request: MemoryPromptRequest) => string {
|
|
38
|
+
// Copy options now: mutable caller arrays must not silently change eligibility.
|
|
39
|
+
const selection = { ...options.selection, scopes: [...(options.selection?.scopes ?? ["global", "repo", "runtime"])] };
|
|
40
|
+
const experimentalRelevance = options.experimentalRelevance === true;
|
|
41
|
+
const readStore = options.readStore ?? readMemoryStoreSnapshot;
|
|
42
|
+
let frame: { key: string; section: string } | null = null;
|
|
43
|
+
let selected: { key: string; section: string } | null = null;
|
|
44
|
+
return (request) => {
|
|
45
|
+
const dataDir = options.getDataDir();
|
|
46
|
+
const activeRepository = canonicalMemoryRepositoryIdentity(request.cwd);
|
|
47
|
+
const authority = JSON.stringify([
|
|
48
|
+
dataDir,
|
|
49
|
+
request.sessionAuthority,
|
|
50
|
+
request.cwd,
|
|
51
|
+
activeRepository,
|
|
52
|
+
request.targetId,
|
|
53
|
+
request.runtimeId,
|
|
54
|
+
request.modelId,
|
|
55
|
+
]);
|
|
56
|
+
const frameKey = JSON.stringify([request.turnId, authority]);
|
|
57
|
+
if (request.turnId !== null && frame?.key === frameKey) return frame.section;
|
|
58
|
+
let section = "";
|
|
59
|
+
try {
|
|
60
|
+
const snapshot = readStore(dataDir);
|
|
61
|
+
const relevance = {
|
|
62
|
+
taskText: request.taskText,
|
|
63
|
+
activePaths: [...request.activePaths],
|
|
64
|
+
activeSymbols: [...(request.activeSymbols ?? [])],
|
|
65
|
+
};
|
|
66
|
+
const key = createHash("sha256")
|
|
67
|
+
.update(
|
|
68
|
+
JSON.stringify([
|
|
69
|
+
snapshot.revision,
|
|
70
|
+
authority,
|
|
71
|
+
selection,
|
|
72
|
+
relevance,
|
|
73
|
+
experimentalRelevance,
|
|
74
|
+
MEMORY_RELEVANCE_VERSION,
|
|
75
|
+
]),
|
|
76
|
+
)
|
|
77
|
+
.digest("hex");
|
|
78
|
+
if (selected?.key === key) section = selected.section;
|
|
79
|
+
else {
|
|
80
|
+
section = buildMemoryPromptSection(snapshot.records, {
|
|
81
|
+
...selection,
|
|
82
|
+
activeRepository,
|
|
83
|
+
activeRuntime: { kind: "runtime", key: request.runtimeId },
|
|
84
|
+
...(experimentalRelevance ? { relevance } : {}),
|
|
85
|
+
}).section;
|
|
86
|
+
selected = Object.freeze({ key, section });
|
|
87
|
+
}
|
|
88
|
+
} catch {
|
|
89
|
+
// A failed fresh read revokes old approved content, including its cache.
|
|
90
|
+
selected = null;
|
|
91
|
+
}
|
|
92
|
+
frame = request.turnId === null ? null : Object.freeze({ key: frameKey, section });
|
|
93
|
+
return section;
|
|
94
|
+
};
|
|
95
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ceilChars } from "../session/context-accounting.js";
|
|
2
|
+
import { eligibleMemoryRecords } from "./operations.js";
|
|
3
|
+
import { type MemoryRelevanceInput, type RankedMemoryCandidate, rankMemoryByRelevance } from "./relevance.js";
|
|
2
4
|
import type {
|
|
3
5
|
MemoryAgentIdentity,
|
|
4
6
|
MemoryRecord,
|
|
@@ -21,6 +23,8 @@ export const MEMORY_PROMPT_DEFAULT_MAX_ITEMS = 5;
|
|
|
21
23
|
export const MEMORY_PROMPT_DEFAULT_SCOPES: ReadonlyArray<MemoryScope> = ["global", "repo"];
|
|
22
24
|
|
|
23
25
|
export interface MemoryPromptOptions {
|
|
26
|
+
/** Explicit experiment only; omitted options preserve legacy priority. */
|
|
27
|
+
relevance?: MemoryRelevanceInput;
|
|
24
28
|
scopes?: ReadonlyArray<MemoryScope>;
|
|
25
29
|
tokenBudget?: number;
|
|
26
30
|
maxItems?: number;
|
|
@@ -42,18 +46,51 @@ export function selectMemoryForPrompt(
|
|
|
42
46
|
records: ReadonlyArray<MemoryRecord>,
|
|
43
47
|
options: MemoryPromptOptions = {},
|
|
44
48
|
): MemoryRecord[] {
|
|
49
|
+
return inspectMemoryPromptSelection(records, options).records;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface MemoryPromptDecision {
|
|
53
|
+
id: string;
|
|
54
|
+
reason: "selected" | "token-budget" | "item-limit";
|
|
55
|
+
relevance?: Omit<RankedMemoryCandidate, "record">;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Diagnostics cover eligible candidates only: excluded records are never scored. */
|
|
59
|
+
export function inspectMemoryPromptSelection(
|
|
60
|
+
records: ReadonlyArray<MemoryRecord>,
|
|
61
|
+
options: MemoryPromptOptions = {},
|
|
62
|
+
): { records: MemoryRecord[]; decisions: MemoryPromptDecision[] } {
|
|
45
63
|
const scopes = options.scopes ?? MEMORY_PROMPT_DEFAULT_SCOPES;
|
|
46
64
|
const tokenBudget = options.tokenBudget ?? MEMORY_PROMPT_DEFAULT_TOKEN_BUDGET;
|
|
47
65
|
const maxItems = options.maxItems ?? MEMORY_PROMPT_DEFAULT_MAX_ITEMS;
|
|
48
|
-
if (tokenBudget <= 0 || maxItems
|
|
49
|
-
|
|
66
|
+
if (!Number.isFinite(tokenBudget) || !Number.isFinite(maxItems) || tokenBudget <= 0 || maxItems < 1)
|
|
67
|
+
return { records: [], decisions: [] };
|
|
68
|
+
const eligibility = {
|
|
50
69
|
scopes,
|
|
51
|
-
tokenBudget,
|
|
52
70
|
activeRepository: options.activeRepository ?? null,
|
|
53
71
|
activeRuntime: options.activeRuntime ?? null,
|
|
54
72
|
activeAgent: options.activeAgent ?? null,
|
|
55
|
-
}
|
|
56
|
-
|
|
73
|
+
};
|
|
74
|
+
const candidates =
|
|
75
|
+
options.relevance === undefined
|
|
76
|
+
? eligibleMemoryRecords(records, eligibility).map((record) => ({ record, relevance: undefined }))
|
|
77
|
+
: rankMemoryByRelevance(records, eligibility, options.relevance).map(({ record, ...relevance }) => ({
|
|
78
|
+
record,
|
|
79
|
+
relevance,
|
|
80
|
+
}));
|
|
81
|
+
const selected: MemoryRecord[] = [];
|
|
82
|
+
const decisions: MemoryPromptDecision[] = [];
|
|
83
|
+
for (const { record, relevance } of candidates) {
|
|
84
|
+
const reason =
|
|
85
|
+
selected.length >= Math.floor(maxItems)
|
|
86
|
+
? "item-limit"
|
|
87
|
+
: ceilChars(renderMemoryPromptSection([...selected, record]).length) > tokenBudget
|
|
88
|
+
? "token-budget"
|
|
89
|
+
: "selected";
|
|
90
|
+
decisions.push({ id: record.id, reason, ...(relevance === undefined ? {} : { relevance }) });
|
|
91
|
+
if (reason === "selected") selected.push(record);
|
|
92
|
+
}
|
|
93
|
+
return { records: selected, decisions };
|
|
57
94
|
}
|
|
58
95
|
|
|
59
96
|
/**
|
|
@@ -74,10 +111,16 @@ export function renderMemoryPromptSection(records: ReadonlyArray<MemoryRecord>):
|
|
|
74
111
|
const lesson = record.lesson.replace(/\s+/g, " ").trim();
|
|
75
112
|
const evidence = record.evidenceRefs.join(", ");
|
|
76
113
|
lines.push(`- [${record.id}] (${renderApplicability(record)}) ${lesson} Evidence: ${evidence}.`);
|
|
114
|
+
if (record.appliesWhen.length > 0) lines.push(` Applies when: ${record.appliesWhen.map(compactText).join("; ")}.`);
|
|
115
|
+
if (record.avoidWhen.length > 0) lines.push(` Avoid when: ${record.avoidWhen.map(compactText).join("; ")}.`);
|
|
77
116
|
}
|
|
78
117
|
return lines.join("\n");
|
|
79
118
|
}
|
|
80
119
|
|
|
120
|
+
function compactText(value: string): string {
|
|
121
|
+
return value.replace(/\s+/g, " ").trim();
|
|
122
|
+
}
|
|
123
|
+
|
|
81
124
|
function renderApplicability(record: MemoryRecord): string {
|
|
82
125
|
if (record.scope === "repo") {
|
|
83
126
|
if (record.repository !== undefined) {
|
|
@@ -101,6 +144,6 @@ export function buildMemoryPromptSection(
|
|
|
101
144
|
): { section: string; records: MemoryRecord[]; tokens: number } {
|
|
102
145
|
const selected = selectMemoryForPrompt(records, options);
|
|
103
146
|
const section = renderMemoryPromptSection(selected);
|
|
104
|
-
const tokens =
|
|
147
|
+
const tokens = ceilChars(section.length);
|
|
105
148
|
return { section, records: selected, tokens };
|
|
106
149
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { eligibleMemoryRecords } from "./operations.js";
|
|
2
|
+
import type { MemoryRecord, MemoryRetrievalOptions } from "./types.js";
|
|
3
|
+
|
|
4
|
+
/** Versioned, deterministic heuristic; not a semantic applicability or truth check. */
|
|
5
|
+
export const MEMORY_RELEVANCE_VERSION = "lexical-v1";
|
|
6
|
+
export const MEMORY_RELEVANCE_WEIGHTS = { taskTerm: 1, path: 8, symbol: 4 } as const;
|
|
7
|
+
|
|
8
|
+
export interface MemoryRelevanceInput {
|
|
9
|
+
readonly taskText: string;
|
|
10
|
+
readonly activePaths?: readonly string[];
|
|
11
|
+
readonly activeSymbols?: readonly string[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface RankedMemoryCandidate {
|
|
15
|
+
readonly record: MemoryRecord;
|
|
16
|
+
readonly score: number;
|
|
17
|
+
readonly matches: {
|
|
18
|
+
readonly taskTerms: readonly string[];
|
|
19
|
+
readonly paths: readonly string[];
|
|
20
|
+
readonly symbols: readonly string[];
|
|
21
|
+
};
|
|
22
|
+
readonly fallback: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const STOP_WORDS = new Set(["and", "are", "for", "from", "into", "the", "this", "that", "with", "use", "when"]);
|
|
26
|
+
|
|
27
|
+
function normalized(value: string): string {
|
|
28
|
+
return value.normalize("NFKC").toLowerCase();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function terms(value: string): string[] {
|
|
32
|
+
return [...new Set(normalized(value.replace(/([a-z])([A-Z])/g, "$1 $2")).match(/[\p{L}\p{N}]+/gu) ?? [])].filter(
|
|
33
|
+
(term) => term.length >= 2 && !STOP_WORDS.has(term),
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function pathKey(value: string): string {
|
|
38
|
+
return normalized(value).replaceAll("\\", "/").replace(/^\.\//, "");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Gates are identical to legacy retrieval and run before scoring. Text features
|
|
43
|
+
* use NFKC/lowercase exact Unicode terms; paths/symbols match whole lexical
|
|
44
|
+
* features, never filesystem ancestry or fuzzy substrings. Task features are
|
|
45
|
+
* bounded to 64 terms, 32 explicit paths, and 32 symbols in supplied order.
|
|
46
|
+
* Evidence IDs and avoidWhen do not create positive relevance.
|
|
47
|
+
*
|
|
48
|
+
* One legacy-priority zero-overlap lesson gets an early opportunity after the
|
|
49
|
+
* first positive hit. This can displace a relevant item and cannot guarantee
|
|
50
|
+
* that general constraints fit a finite budget. All other ties retain legacy
|
|
51
|
+
* verification/creation-time then ID order. No input record is mutated.
|
|
52
|
+
*/
|
|
53
|
+
export function rankMemoryByRelevance(
|
|
54
|
+
records: ReadonlyArray<MemoryRecord>,
|
|
55
|
+
options: Omit<MemoryRetrievalOptions, "tokenBudget">,
|
|
56
|
+
input: MemoryRelevanceInput,
|
|
57
|
+
): RankedMemoryCandidate[] {
|
|
58
|
+
const taskTerms = terms(input.taskText).slice(0, 64);
|
|
59
|
+
const paths = [...new Set((input.activePaths ?? []).map(pathKey).filter(Boolean))].slice(0, 32);
|
|
60
|
+
const symbols = [...new Set((input.activeSymbols ?? []).map(normalized).filter(Boolean))].slice(0, 32);
|
|
61
|
+
const candidates = eligibleMemoryRecords(records, options).map((record): RankedMemoryCandidate => {
|
|
62
|
+
const text = [record.key, record.lesson, ...record.appliesWhen].join("\n");
|
|
63
|
+
const textTerms = new Set(terms(text));
|
|
64
|
+
const textPaths = new Set(
|
|
65
|
+
(text.match(/[\p{L}\p{N}_./\\:-]+/gu) ?? []).map((path) => pathKey(path.replace(/[.:]+$/, ""))),
|
|
66
|
+
);
|
|
67
|
+
const textSymbols = new Set(normalized(text).match(/[\p{L}\p{N}_$]+/gu) ?? []);
|
|
68
|
+
const matches = {
|
|
69
|
+
taskTerms: taskTerms.filter((term) => textTerms.has(term)),
|
|
70
|
+
paths: paths.filter((path) => textPaths.has(path)),
|
|
71
|
+
symbols: symbols.filter((symbol) => textSymbols.has(symbol)),
|
|
72
|
+
};
|
|
73
|
+
return {
|
|
74
|
+
record,
|
|
75
|
+
matches,
|
|
76
|
+
fallback: false,
|
|
77
|
+
score:
|
|
78
|
+
matches.taskTerms.length * MEMORY_RELEVANCE_WEIGHTS.taskTerm +
|
|
79
|
+
matches.paths.length * MEMORY_RELEVANCE_WEIGHTS.path +
|
|
80
|
+
matches.symbols.length * MEMORY_RELEVANCE_WEIGHTS.symbol,
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
// Stable sorting preserves the already established legacy tie ordering.
|
|
84
|
+
candidates.sort((a, b) => b.score - a.score);
|
|
85
|
+
if ((candidates[0]?.score ?? 0) > 0) {
|
|
86
|
+
const fallbackIndex = candidates.findIndex((candidate) => candidate.score === 0);
|
|
87
|
+
if (fallbackIndex >= 0) {
|
|
88
|
+
const [fallback] = candidates.splice(fallbackIndex, 1);
|
|
89
|
+
if (fallback !== undefined) candidates.splice(1, 0, { ...fallback, fallback: true });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return candidates;
|
|
93
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { ceilChars } from "../session/context-accounting.js";
|
|
2
|
+
import type { TaskMemoryEntry, TaskMemorySnapshot } from "./task-bank.js";
|
|
3
|
+
|
|
4
|
+
export interface MemoryRestorationInput {
|
|
5
|
+
readonly bank: {
|
|
6
|
+
readonly version: TaskMemorySnapshot["version"];
|
|
7
|
+
readonly status: Readonly<TaskMemoryEntry> | null;
|
|
8
|
+
readonly knowledge: readonly Readonly<TaskMemoryEntry>[];
|
|
9
|
+
readonly procedural: readonly Readonly<TaskMemoryEntry>[];
|
|
10
|
+
};
|
|
11
|
+
/** Already admitted current state. Only nonblank text suppresses historical status; it is never recopied. */
|
|
12
|
+
readonly currentState: { readonly kind: "handoff" | "summary"; readonly text: string } | null;
|
|
13
|
+
readonly maxTokens: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface MemoryRestorationRender {
|
|
17
|
+
readonly message: string;
|
|
18
|
+
readonly tokens: number;
|
|
19
|
+
readonly citedEntryIds: readonly string[];
|
|
20
|
+
readonly omittedEntryIds: readonly string[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const PREFIX = "Memory: remembered execution context (unverified; current instructions take precedence).\n";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Pure conservative renderer. lastTouchedAt is only a recency/usage heuristic,
|
|
27
|
+
* never verification. Equal whitespace-normalized content shares one rendered
|
|
28
|
+
* row with all source IDs; near duplicates remain distinct. Knowledge and
|
|
29
|
+
* procedure have equal access in recency order, with ID as the stable tie.
|
|
30
|
+
* Nothing is deleted, marked injected, promoted, or inferred to be current.
|
|
31
|
+
*/
|
|
32
|
+
export function renderMemoryRestoration(input: MemoryRestorationInput): MemoryRestorationRender {
|
|
33
|
+
const hasCurrentState = input.currentState !== null && input.currentState.text.trim().length > 0;
|
|
34
|
+
const source = [
|
|
35
|
+
...(!hasCurrentState && input.bank.status !== null ? [input.bank.status] : []),
|
|
36
|
+
...input.bank.knowledge,
|
|
37
|
+
...input.bank.procedural,
|
|
38
|
+
].sort((a, b) => {
|
|
39
|
+
const status = Number(b.kind === "status") - Number(a.kind === "status");
|
|
40
|
+
return status || b.lastTouchedAt.localeCompare(a.lastTouchedAt) || a.id.localeCompare(b.id);
|
|
41
|
+
});
|
|
42
|
+
const groups = new Map<string, { ids: string[]; kinds: Set<TaskMemoryEntry["kind"]>; content: string }>();
|
|
43
|
+
for (const entry of source) {
|
|
44
|
+
const content = entry.content.replace(/\s+/gu, " ").trim();
|
|
45
|
+
const group = groups.get(content);
|
|
46
|
+
if (group) {
|
|
47
|
+
if (!group.ids.includes(entry.id)) group.ids.push(entry.id);
|
|
48
|
+
group.kinds.add(entry.kind);
|
|
49
|
+
} else groups.set(content, { ids: [entry.id], kinds: new Set([entry.kind]), content: entry.content });
|
|
50
|
+
}
|
|
51
|
+
const lines: string[] = [];
|
|
52
|
+
const cited: string[] = [];
|
|
53
|
+
const omitted = hasCurrentState && input.bank.status !== null ? [input.bank.status.id] : [];
|
|
54
|
+
const validBudget = Number.isFinite(input.maxTokens) && input.maxTokens > 0;
|
|
55
|
+
for (const [content, group] of groups) {
|
|
56
|
+
const kinds = [...group.kinds]
|
|
57
|
+
.sort()
|
|
58
|
+
.map((kind) => (kind === "status" ? "historical status" : `remembered ${kind}`))
|
|
59
|
+
.join("/");
|
|
60
|
+
// Quote the chosen source losslessly, including its original whitespace.
|
|
61
|
+
// Normalization above is only the dedup key, never a rewrite of evidence.
|
|
62
|
+
const line = `- ${group.ids.map((id) => `[${id}]`).join(" ")} ${kinds}: ${JSON.stringify(group.content)}`;
|
|
63
|
+
const candidate = PREFIX + [...lines, line].join("\n");
|
|
64
|
+
if (!validBudget || content.length === 0 || ceilChars(candidate.length) > input.maxTokens) {
|
|
65
|
+
omitted.push(...group.ids);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
lines.push(line);
|
|
69
|
+
cited.push(...group.ids);
|
|
70
|
+
}
|
|
71
|
+
const message = lines.length > 0 ? PREFIX + lines.join("\n") : "";
|
|
72
|
+
return Object.freeze({
|
|
73
|
+
message,
|
|
74
|
+
tokens: ceilChars(message.length),
|
|
75
|
+
citedEntryIds: Object.freeze(cited),
|
|
76
|
+
omittedEntryIds: Object.freeze(omitted),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { closeSync, openSync, readFileSync, readSync } from "node:fs";
|
|
2
3
|
import { readFile } from "node:fs/promises";
|
|
3
4
|
import { join } from "node:path";
|
|
4
5
|
import { safeResourceWrite } from "../../core/safe-resource-write.js";
|
|
@@ -8,6 +9,43 @@ import { validateMemoryStore } from "./validate.js";
|
|
|
8
9
|
export const MEMORY_STORE_MAX_RECORDS = 500;
|
|
9
10
|
export const MEMORY_STALE_APPROVED_DAYS = 180;
|
|
10
11
|
export const MEMORY_STALE_UNAPPROVED_DAYS = 30;
|
|
12
|
+
/** Prompt compilation fails closed above this read ceiling; store writers are unchanged. */
|
|
13
|
+
export const MEMORY_PROMPT_STORE_MAX_BYTES = 16 * 1024 * 1024;
|
|
14
|
+
|
|
15
|
+
export interface MemoryStoreSnapshot {
|
|
16
|
+
revision: string;
|
|
17
|
+
records: MemoryRecord[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Hash the exact bounded bytes read, so same-size external replacements invalidate selection. */
|
|
21
|
+
export function readMemoryStoreSnapshot(dataDir: string): MemoryStoreSnapshot {
|
|
22
|
+
const path = memoryStorePath(dataDir);
|
|
23
|
+
let fd: number;
|
|
24
|
+
try {
|
|
25
|
+
fd = openSync(path, "r");
|
|
26
|
+
} catch (error) {
|
|
27
|
+
if (isErrorWithCode(error) && error.code === "ENOENT") return { revision: "missing", records: [] };
|
|
28
|
+
throw error;
|
|
29
|
+
}
|
|
30
|
+
const chunks: Buffer[] = [];
|
|
31
|
+
let total = 0;
|
|
32
|
+
try {
|
|
33
|
+
while (true) {
|
|
34
|
+
const chunk = Buffer.allocUnsafe(Math.min(64 * 1024, MEMORY_PROMPT_STORE_MAX_BYTES + 1 - total));
|
|
35
|
+
const count = readSync(fd, chunk);
|
|
36
|
+
if (count === 0) break;
|
|
37
|
+
total += count;
|
|
38
|
+
if (total > MEMORY_PROMPT_STORE_MAX_BYTES) throw new Error("memory prompt store exceeds read ceiling");
|
|
39
|
+
chunks.push(chunk.subarray(0, count));
|
|
40
|
+
}
|
|
41
|
+
} finally {
|
|
42
|
+
closeSync(fd);
|
|
43
|
+
}
|
|
44
|
+
const raw = Buffer.concat(chunks);
|
|
45
|
+
const records = parseAndValidate(raw.toString("utf8"), path);
|
|
46
|
+
if (records.length > MEMORY_STORE_MAX_RECORDS) throw new Error("memory prompt store exceeds record limit");
|
|
47
|
+
return { revision: createHash("sha256").update(raw).digest("hex"), records };
|
|
48
|
+
}
|
|
11
49
|
|
|
12
50
|
export function memoryRoot(dataDir: string): string {
|
|
13
51
|
return join(dataDir, "memory");
|
|
@@ -8,6 +8,13 @@ import {
|
|
|
8
8
|
sanitizeToolResultDigest,
|
|
9
9
|
type ToolResultDigest,
|
|
10
10
|
} from "../../tools/result-disposition.js";
|
|
11
|
+
import {
|
|
12
|
+
type MemoryCommitScope,
|
|
13
|
+
MemoryCommitState,
|
|
14
|
+
type MemoryRestorationOffer,
|
|
15
|
+
type SuccessfulMemoryContextCommit,
|
|
16
|
+
} from "../memory/commit-state.js";
|
|
17
|
+
import type { MemoryRestorationInput } from "../memory/restoration.js";
|
|
11
18
|
import { TASK_MEMORY_DEFAULT_PROCEDURAL_CAP, type TaskMemoryBank, type TaskMemoryEntry } from "../memory/task-bank.js";
|
|
12
19
|
import {
|
|
13
20
|
runTaskMemoryPolicy,
|
|
@@ -119,7 +126,7 @@ export interface MemoryInterventionDeps {
|
|
|
119
126
|
* closed. The composition root buffers it into the next submitted turn, which
|
|
120
127
|
* is where a synchronous turn_end reminder would have landed anyway.
|
|
121
128
|
*/
|
|
122
|
-
onDeferredReminder?: (message: string) => void;
|
|
129
|
+
onDeferredReminder?: (message: string, isCurrent?: () => boolean) => void;
|
|
123
130
|
/**
|
|
124
131
|
* False when no further turn will be submitted in this process. A background
|
|
125
132
|
* step is detached from the boundary that triggered it and a local route
|
|
@@ -145,6 +152,14 @@ export interface MemoryPromptedStepResult extends TaskMemoryPolicyResult {
|
|
|
145
152
|
}
|
|
146
153
|
|
|
147
154
|
export interface MemoryInterventionRegistration extends MiddlewareHookRegistration {
|
|
155
|
+
bindCommitScope(scope: MemoryCommitScope): void;
|
|
156
|
+
notifyContextCommitted(input: SuccessfulMemoryContextCommit): void;
|
|
157
|
+
prepareRestoration(
|
|
158
|
+
currentState: MemoryRestorationInput["currentState"],
|
|
159
|
+
maxTokens: number,
|
|
160
|
+
): MemoryRestorationOffer | null;
|
|
161
|
+
acknowledgeRestoration(offer: MemoryRestorationOffer): boolean;
|
|
162
|
+
isContentCurrent(): () => boolean;
|
|
148
163
|
/** Invalidate pending content and discard all transient session/branch state. */
|
|
149
164
|
reset(): void;
|
|
150
165
|
/** Invalidate immediately without waiting for the model; refuse further work. */
|
|
@@ -177,6 +192,20 @@ export interface MemoryInterventionRegistration extends MiddlewareHookRegistrati
|
|
|
177
192
|
*/
|
|
178
193
|
export function createMemoryInterventionRegistration(deps: MemoryInterventionDeps): MemoryInterventionRegistration {
|
|
179
194
|
let generation = 0;
|
|
195
|
+
let commitState: MemoryCommitState | null = null;
|
|
196
|
+
let commitScope: MemoryCommitScope | null = null;
|
|
197
|
+
let commitBridgeEnabled = false;
|
|
198
|
+
const captureContentGuard = (): (() => boolean) => {
|
|
199
|
+
const capturedGeneration = generation;
|
|
200
|
+
const owner = commitState;
|
|
201
|
+
const scope = commitScope;
|
|
202
|
+
const stamp = owner?.capture();
|
|
203
|
+
return () =>
|
|
204
|
+
!disposed &&
|
|
205
|
+
generation === capturedGeneration &&
|
|
206
|
+
commitState === owner &&
|
|
207
|
+
(!owner || (!!stamp && !!scope && owner.isCurrent(stamp, scope)));
|
|
208
|
+
};
|
|
180
209
|
let generationController = new AbortController();
|
|
181
210
|
let observedSessionId: string | undefined;
|
|
182
211
|
let disposed = false;
|
|
@@ -207,6 +236,40 @@ export function createMemoryInterventionRegistration(deps: MemoryInterventionDep
|
|
|
207
236
|
let consecutiveLlmTimeouts = 0;
|
|
208
237
|
|
|
209
238
|
return {
|
|
239
|
+
bindCommitScope(scope) {
|
|
240
|
+
commitBridgeEnabled = true;
|
|
241
|
+
if (
|
|
242
|
+
commitScope?.sessionId === scope.sessionId &&
|
|
243
|
+
commitScope.branchAnchorTurnId === scope.branchAnchorTurnId &&
|
|
244
|
+
commitState
|
|
245
|
+
)
|
|
246
|
+
return;
|
|
247
|
+
if (commitState) reset();
|
|
248
|
+
commitScope = { ...scope };
|
|
249
|
+
commitState = new MemoryCommitState(scope, generation);
|
|
250
|
+
observedSessionId = scope.sessionId;
|
|
251
|
+
},
|
|
252
|
+
notifyContextCommitted(input) {
|
|
253
|
+
commitState?.notifyContextCommitted(input, generation);
|
|
254
|
+
},
|
|
255
|
+
prepareRestoration(currentState, maxTokens) {
|
|
256
|
+
if (!commitScope || !settings().enabled) return null;
|
|
257
|
+
return (
|
|
258
|
+
commitState?.prepareRestoration({
|
|
259
|
+
scope: commitScope,
|
|
260
|
+
generation,
|
|
261
|
+
bank: deps.bank.snapshot(),
|
|
262
|
+
currentState,
|
|
263
|
+
maxTokens: Math.min(maxTokens, settings().maxTokens),
|
|
264
|
+
}) ?? null
|
|
265
|
+
);
|
|
266
|
+
},
|
|
267
|
+
acknowledgeRestoration(offer) {
|
|
268
|
+
if (!commitScope || !commitState?.acknowledgeRestoration(offer, commitScope, generation)) return false;
|
|
269
|
+
deps.bank.recordInjection(offer.citedEntryIds);
|
|
270
|
+
return true;
|
|
271
|
+
},
|
|
272
|
+
isContentCurrent: captureContentGuard,
|
|
210
273
|
reset,
|
|
211
274
|
dispose(): void {
|
|
212
275
|
disposed = true;
|
|
@@ -257,7 +320,7 @@ export function createMemoryInterventionRegistration(deps: MemoryInterventionDep
|
|
|
257
320
|
// Recall grows the working set; it is an observability point, not
|
|
258
321
|
// context loss that should reactivate compacted task memory.
|
|
259
322
|
if (input.metadata?.stage === "working_set_recall") return NO_EFFECTS;
|
|
260
|
-
reactivateAfterCompaction = true;
|
|
323
|
+
if (!commitBridgeEnabled) reactivateAfterCompaction = true;
|
|
261
324
|
return NO_EFFECTS;
|
|
262
325
|
case "turn_start": {
|
|
263
326
|
if (input.text?.trim()) currentTask = shortText(input.text, 2_000);
|
|
@@ -346,6 +409,7 @@ export function createMemoryInterventionRegistration(deps: MemoryInterventionDep
|
|
|
346
409
|
false);
|
|
347
410
|
rulesInjectedSincePromptedStep = false;
|
|
348
411
|
const stepGeneration = generation;
|
|
412
|
+
const contentCurrent = captureContentGuard();
|
|
349
413
|
promptedStepInFlight = true;
|
|
350
414
|
outstandingStep = runPromptedStep({
|
|
351
415
|
deterministicTrigger: triggers.some((trigger) => trigger !== "interval"),
|
|
@@ -353,14 +417,14 @@ export function createMemoryInterventionRegistration(deps: MemoryInterventionDep
|
|
|
353
417
|
triggerReasons: triggers,
|
|
354
418
|
})
|
|
355
419
|
.then((result) => {
|
|
356
|
-
if (
|
|
420
|
+
if (!contentCurrent()) return;
|
|
357
421
|
// A rules-only reminder can win the visible boundary while the optional
|
|
358
422
|
// background route resolves to null. Preserve the operator-visible
|
|
359
423
|
// injected outcome instead of overwriting it with that no-client silence.
|
|
360
424
|
if (rulesAlreadySpoke && result.decision === "silent") lastDecision = "injected";
|
|
361
425
|
if (result.reminder === null) return;
|
|
362
426
|
lastInjectedMessage = result.reminder;
|
|
363
|
-
deps.onDeferredReminder?.(result.reminder);
|
|
427
|
+
deps.onDeferredReminder?.(result.reminder, contentCurrent);
|
|
364
428
|
})
|
|
365
429
|
.catch(() => {
|
|
366
430
|
// runPromptedStep already resolves failures to silence; this only
|
|
@@ -393,6 +457,7 @@ export function createMemoryInterventionRegistration(deps: MemoryInterventionDep
|
|
|
393
457
|
// Retain completed bank entries, but revoke pending content and delivery
|
|
394
458
|
// authority. Late provider usage still belongs to its captured origin.
|
|
395
459
|
generation += 1;
|
|
460
|
+
commitState?.cancel(generation);
|
|
396
461
|
generationController.abort();
|
|
397
462
|
generationController = new AbortController();
|
|
398
463
|
promptedStepInFlight = false;
|
|
@@ -408,6 +473,9 @@ export function createMemoryInterventionRegistration(deps: MemoryInterventionDep
|
|
|
408
473
|
}
|
|
409
474
|
|
|
410
475
|
function reset(): void {
|
|
476
|
+
commitState?.dispose();
|
|
477
|
+
commitState = null;
|
|
478
|
+
commitScope = null;
|
|
411
479
|
generation += 1;
|
|
412
480
|
generationController.abort();
|
|
413
481
|
generationController = new AbortController();
|
|
@@ -466,7 +534,8 @@ export function createMemoryInterventionRegistration(deps: MemoryInterventionDep
|
|
|
466
534
|
});
|
|
467
535
|
const live = settings();
|
|
468
536
|
const stepGeneration = generation;
|
|
469
|
-
const isCurrent = ()
|
|
537
|
+
const isCurrent = captureContentGuard();
|
|
538
|
+
const isUsageCurrent = () => !disposed && stepGeneration === generation;
|
|
470
539
|
const usageSink = deps.captureStepUsage?.();
|
|
471
540
|
if (disposed || !live.enabled) {
|
|
472
541
|
const result = silent("no_client");
|
|
@@ -504,7 +573,7 @@ export function createMemoryInterventionRegistration(deps: MemoryInterventionDep
|
|
|
504
573
|
isCurrent,
|
|
505
574
|
signal: generationController.signal,
|
|
506
575
|
onStepUsage: (usage) => {
|
|
507
|
-
if (usageSink) usageSink(usage,
|
|
576
|
+
if (usageSink) usageSink(usage, isUsageCurrent());
|
|
508
577
|
else deps.onStepUsage?.(usage);
|
|
509
578
|
},
|
|
510
579
|
task: input.task?.trim() || currentTask,
|
|
@@ -515,7 +584,13 @@ export function createMemoryInterventionRegistration(deps: MemoryInterventionDep
|
|
|
515
584
|
...(input.suppressIntervention === undefined ? {} : { suppressIntervention: input.suppressIntervention }),
|
|
516
585
|
previousReminder: lastInjectedMessage,
|
|
517
586
|
timeoutMs,
|
|
518
|
-
...(deps.onEnvelope === undefined
|
|
587
|
+
...(deps.onEnvelope === undefined
|
|
588
|
+
? {}
|
|
589
|
+
: {
|
|
590
|
+
onEnvelope: (envelope: TaskMemoryEnvelope) => {
|
|
591
|
+
if (isCurrent()) deps.onEnvelope?.(envelope);
|
|
592
|
+
},
|
|
593
|
+
}),
|
|
519
594
|
});
|
|
520
595
|
const remainingMs = () => Math.floor(live.timeoutMs - Number(process.hrtime.bigint() - started) / 1_000_000);
|
|
521
596
|
const initialTimeoutMs = remainingMs();
|
|
@@ -557,17 +632,18 @@ export function createMemoryInterventionRegistration(deps: MemoryInterventionDep
|
|
|
557
632
|
// runTaskMemoryPolicy resolves its own failures, so reaching here means
|
|
558
633
|
// resolving the client itself threw. That is still a broken route.
|
|
559
634
|
promptedResult = silent("client_error");
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
635
|
+
if (isCurrent())
|
|
636
|
+
deps.onEnvelope?.({
|
|
637
|
+
systemPrompt: "",
|
|
638
|
+
userPrompt: "",
|
|
639
|
+
response: "",
|
|
640
|
+
decision: "silent",
|
|
641
|
+
reason: "client_error",
|
|
642
|
+
bankOperations: 0,
|
|
643
|
+
droppedOperations: 0,
|
|
644
|
+
reminder: null,
|
|
645
|
+
error: error instanceof Error ? error.message : "resolving the background model client failed",
|
|
646
|
+
});
|
|
571
647
|
}
|
|
572
648
|
if (!isCurrent()) return { ...promptedResult, reminder: null, effects: NO_EFFECTS };
|
|
573
649
|
if (promptedResult.reason === "endpoint_busy") telemetryDecision = "dropped";
|
|
@@ -705,6 +781,14 @@ export function createMemoryInterventionRegistration(deps: MemoryInterventionDep
|
|
|
705
781
|
};
|
|
706
782
|
trajectory.push(step);
|
|
707
783
|
if (trajectory.length > live.windowSteps) trajectory.splice(0, trajectory.length - live.windowSteps);
|
|
784
|
+
if (input.metadata?.resultKind === "ok") {
|
|
785
|
+
// Only an explicit receipt for this exact operation closes its failure
|
|
786
|
+
// episode. Keep the bank entry and trajectory as history; this does not
|
|
787
|
+
// verify the surrounding task or reinterpret tool exit classifications.
|
|
788
|
+
failures.delete(step.operationFingerprint);
|
|
789
|
+
annotatedThisTurn.delete(step.operationFingerprint);
|
|
790
|
+
if (lastInjectedOperationFingerprint === step.operationFingerprint) lastInjectedOperationFingerprint = null;
|
|
791
|
+
}
|
|
708
792
|
if (outcome !== "error") return NO_EFFECTS;
|
|
709
793
|
rememberFailure(step);
|
|
710
794
|
return annotateRepeatedFailure(step);
|
|
@@ -718,11 +802,15 @@ export function createMemoryInterventionRegistration(deps: MemoryInterventionDep
|
|
|
718
802
|
*/
|
|
719
803
|
function annotateRepeatedFailure(step: TrajectoryStep): ReadonlyArray<MiddlewareEffect> {
|
|
720
804
|
if (annotatedThisTurn.has(step.operationFingerprint)) return NO_EFFECTS;
|
|
805
|
+
const failure = failures.get(step.operationFingerprint);
|
|
806
|
+
if (failure === undefined) return NO_EFFECTS;
|
|
721
807
|
const occurrences = trajectory.filter(
|
|
722
|
-
(candidate) =>
|
|
808
|
+
(candidate) =>
|
|
809
|
+
candidate.outcome === "error" &&
|
|
810
|
+
candidate.operationFingerprint === step.operationFingerprint &&
|
|
811
|
+
candidate.step >= failure.firstStep,
|
|
723
812
|
).length;
|
|
724
|
-
|
|
725
|
-
if (occurrences < 2 || failure === undefined) return NO_EFFECTS;
|
|
813
|
+
if (occurrences < 2) return NO_EFFECTS;
|
|
726
814
|
const message = boundedReminder(
|
|
727
815
|
`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.`,
|
|
728
816
|
settings().maxTokens,
|
|
@@ -780,11 +868,15 @@ export function createMemoryInterventionRegistration(deps: MemoryInterventionDep
|
|
|
780
868
|
) {
|
|
781
869
|
continue;
|
|
782
870
|
}
|
|
871
|
+
const failure = failures.get(step.operationFingerprint);
|
|
872
|
+
if (failure === undefined || step.step < failure.firstStep) continue;
|
|
783
873
|
const occurrences = trajectory.filter(
|
|
784
|
-
(candidate) =>
|
|
874
|
+
(candidate) =>
|
|
875
|
+
candidate.outcome === "error" &&
|
|
876
|
+
candidate.operationFingerprint === step.operationFingerprint &&
|
|
877
|
+
candidate.step >= failure.firstStep,
|
|
785
878
|
).length;
|
|
786
|
-
|
|
787
|
-
if (occurrences < 2 || failure === undefined) continue;
|
|
879
|
+
if (occurrences < 2) continue;
|
|
788
880
|
const message = boundedReminder(
|
|
789
881
|
`Memory: [${failure.entryId}] you already tried ${failure.callDescription} at step ${failure.firstStep} and it failed with ${failure.errorDigest}.`,
|
|
790
882
|
settings().maxTokens,
|