@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
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import type { SuccessfulMemoryContextCommit } from "../domains/memory/commit-state.js";
|
|
2
|
+
import type { MemoryInterventionRegistration } from "../domains/middleware/memory-intervention.js";
|
|
1
3
|
import { replaceEngineMessages, setEngineSystemPrompt } from "../engine/agent.js";
|
|
4
|
+
import type { ContinuityReductionHooks } from "./continuity-controller.js";
|
|
2
5
|
/**
|
|
3
6
|
* Turn context ownership: the session-prompt compile cache, context-snapshot
|
|
4
7
|
* accounting, prompt-cache honesty, and compaction. `runAutoCompact` is the
|
|
@@ -27,11 +30,23 @@ import type { SafeEventBus } from "../core/event-bus.js";
|
|
|
27
30
|
import { residencyTargetKey } from "../core/residency-target-key.js";
|
|
28
31
|
import type { PendingSkillToolPolicy } from "../core/skill-activation.js";
|
|
29
32
|
import type { ToolName } from "../core/tool-names.js";
|
|
33
|
+
import type { BudgetInspection } from "../domains/context/budget/inspection.js";
|
|
34
|
+
import {
|
|
35
|
+
createLiveBudgetProducer,
|
|
36
|
+
type LiveBudgetBreakdown,
|
|
37
|
+
type LiveBudgetHandoffProjection,
|
|
38
|
+
type LiveBudgetInput,
|
|
39
|
+
type LiveBudgetOutcomeProjection,
|
|
40
|
+
type LiveBudgetView,
|
|
41
|
+
resolveLiveBudgetPolicy,
|
|
42
|
+
} from "../domains/context/budget/live-view.js";
|
|
43
|
+
import { requestFits } from "../domains/context/budget/request-fit.js";
|
|
30
44
|
import { buildEvictionFields, planEviction } from "../domains/context/working-set/engine.js";
|
|
31
45
|
import { foldWorkingSet } from "../domains/context/working-set/fold.js";
|
|
32
46
|
import { isTurnStart } from "../domains/context/working-set/horizon.js";
|
|
33
47
|
import { resolveWorkingSetPolicy } from "../domains/context/working-set/policies/index.js";
|
|
34
48
|
import { selectVisibleEntries } from "../domains/context/working-set/visible.js";
|
|
49
|
+
import type { MemoryPromptRequest } from "../domains/memory/prompt-cache.js";
|
|
35
50
|
import type { ObservabilityContract } from "../domains/observability/contract.js";
|
|
36
51
|
import type { CompiledSessionPrompt, SessionPromptInputs } from "../domains/prompts/compiler.js";
|
|
37
52
|
import type { PromptsContract } from "../domains/prompts/contract.js";
|
|
@@ -39,6 +54,7 @@ import {
|
|
|
39
54
|
type ContextWindowDetails,
|
|
40
55
|
type ContextWindowSource,
|
|
41
56
|
canonicalEndpointKey,
|
|
57
|
+
isOrchestratorEligibleRuntime,
|
|
42
58
|
type ProvidersContract,
|
|
43
59
|
resolveRuntimeTarget,
|
|
44
60
|
} from "../domains/providers/index.js";
|
|
@@ -56,7 +72,6 @@ import {
|
|
|
56
72
|
appendContextSnapshot,
|
|
57
73
|
type CaptureContextSnapshotInput,
|
|
58
74
|
type ContextSnapshot,
|
|
59
|
-
type ContextUsageBreakdown,
|
|
60
75
|
type ContextUsageSnapshot,
|
|
61
76
|
captureContextSnapshot,
|
|
62
77
|
ceilChars,
|
|
@@ -85,7 +100,6 @@ import {
|
|
|
85
100
|
type SessionPromptCompileRecord,
|
|
86
101
|
} from "../domains/session/prompt-manifest.js";
|
|
87
102
|
import { filterEntriesToActivePath } from "../domains/session/tree/active-path.js";
|
|
88
|
-
import { resolveReservedOutputTokens } from "../engine/apis/output-budget.js";
|
|
89
103
|
import type { AgentMessage, Usage } from "../engine/types.js";
|
|
90
104
|
import { resolveToolPromptHint, type ToolRegistry } from "../tools/registry.js";
|
|
91
105
|
import {
|
|
@@ -94,14 +108,17 @@ import {
|
|
|
94
108
|
runtimeSupportsTools,
|
|
95
109
|
sumRunUsage,
|
|
96
110
|
toolNamesFromAgentState,
|
|
111
|
+
toolSignatureFromState,
|
|
97
112
|
} from "./chat-loop-messages.js";
|
|
98
|
-
import { buildModelReplayAgentMessagesFromTurns } from "./model-session-replay.js";
|
|
113
|
+
import { buildModelReplayAgentMessagesFromTurns, continuityContextFromSession } from "./model-session-replay.js";
|
|
114
|
+
import { resolveTurnOutputReserve } from "./output-reserve.js";
|
|
99
115
|
import { attachedToolSchemasFromState, mainPromptCacheIdentity } from "./prompt-cache-identity.js";
|
|
100
116
|
import { renderCompactionSummaryLine, renderEvictionSkipLine } from "./renderers/compaction-summary.js";
|
|
101
117
|
import type { TurnMiddleware } from "./turn-middleware.js";
|
|
102
118
|
import type { AgentRuntime, ChatTurnState } from "./turn-state.js";
|
|
103
119
|
|
|
104
120
|
export interface TurnContextDeps {
|
|
121
|
+
memoryCommitBridge?: MemoryInterventionRegistration | undefined;
|
|
105
122
|
interactiveGuidance?: boolean;
|
|
106
123
|
state: ChatTurnState;
|
|
107
124
|
getSettings: () => Readonly<ClioSettings>;
|
|
@@ -116,13 +133,29 @@ export interface TurnContextDeps {
|
|
|
116
133
|
| ((
|
|
117
134
|
instructions?: string,
|
|
118
135
|
trigger?: CompactionTrigger,
|
|
119
|
-
budget?: Pick<
|
|
136
|
+
budget?: Pick<
|
|
137
|
+
CompactInput,
|
|
138
|
+
| "keepRecentTokens"
|
|
139
|
+
| "preserveUserTurnId"
|
|
140
|
+
| "skillContextState"
|
|
141
|
+
| "signal"
|
|
142
|
+
| "beforeSummaryCall"
|
|
143
|
+
| "checkpointForSummary"
|
|
144
|
+
>,
|
|
120
145
|
) => Promise<CompactResult | null>)
|
|
121
146
|
| undefined;
|
|
122
147
|
/** Test seam for the eviction planner; production uses `planEviction` from the working-set engine. */
|
|
123
148
|
planEviction?: typeof planEviction;
|
|
124
|
-
getMemorySection?: (() => string) | undefined;
|
|
149
|
+
getMemorySection?: ((request: MemoryPromptRequest) => string) | undefined;
|
|
125
150
|
getReadySkillCount?: (() => number) | undefined;
|
|
151
|
+
/**
|
|
152
|
+
* Optional continuity projections carried into the live budget view. They
|
|
153
|
+
* are injected interfaces: this module never reads, writes, or validates the
|
|
154
|
+
* durable records behind them, and a throwing reader degrades to null rather
|
|
155
|
+
* than failing a budget publication.
|
|
156
|
+
*/
|
|
157
|
+
getPendingHandoff?: (() => LiveBudgetHandoffProjection | null) | undefined;
|
|
158
|
+
getLastOutcome?: (() => LiveBudgetOutcomeProjection | null) | undefined;
|
|
126
159
|
middleware: TurnMiddleware;
|
|
127
160
|
emitNotice: (text: string) => void;
|
|
128
161
|
}
|
|
@@ -165,7 +198,23 @@ export type ExpectedColdReason =
|
|
|
165
198
|
*/
|
|
166
199
|
| "background_memory";
|
|
167
200
|
|
|
201
|
+
export type LiveContextUsage = ContextUsageSnapshot &
|
|
202
|
+
Pick<LiveBudgetView, "revision" | "inputSource" | "historical" | "breakdownSource">;
|
|
203
|
+
|
|
168
204
|
export interface TurnContext {
|
|
205
|
+
notifyMemoryCommit(
|
|
206
|
+
commitId: string,
|
|
207
|
+
kind: SuccessfulMemoryContextCommit["kind"],
|
|
208
|
+
outcome: SuccessfulMemoryContextCommit["outcome"],
|
|
209
|
+
): void;
|
|
210
|
+
installMemoryRestoration(runtime: AgentRuntime, pendingUserText?: string): boolean;
|
|
211
|
+
/** Prepare an attempt before preflight; rejected attempts never freeze the next submit. */
|
|
212
|
+
prepareMemoryTurn(
|
|
213
|
+
runtime: AgentRuntime,
|
|
214
|
+
input: { taskText: string; continuation: boolean; images?: ReadonlyArray<unknown> | undefined },
|
|
215
|
+
): void;
|
|
216
|
+
/** Bind first-session creation after append without rereading the prepared snapshot. */
|
|
217
|
+
commitMemoryTurn(runtime: AgentRuntime): void;
|
|
169
218
|
ensureSessionPrompt(agentRuntime: AgentRuntime): Promise<CompiledSessionPrompt | null>;
|
|
170
219
|
logPromptCompileIfPending(): void;
|
|
171
220
|
invalidateSessionPromptCache(): void;
|
|
@@ -188,6 +237,26 @@ export interface TurnContext {
|
|
|
188
237
|
*/
|
|
189
238
|
promptSideTokens(): number;
|
|
190
239
|
liveContextEstimate(agentRuntime: AgentRuntime, pendingUserText?: string): LiveContextEstimate;
|
|
240
|
+
/**
|
|
241
|
+
* Recompute and publish the live budget view.
|
|
242
|
+
*
|
|
243
|
+
* Called at the seams that change the next request (capture, reconcile,
|
|
244
|
+
* reduction, the mid-run tool-batch boundary, run settlement) and by any
|
|
245
|
+
* consumer about to make a decision from it. The only thing it mutates is
|
|
246
|
+
* the accounting cache: no
|
|
247
|
+
* persistence, no model call, and no reduction happens because something
|
|
248
|
+
* asked what the budget is.
|
|
249
|
+
*/
|
|
250
|
+
refreshLiveBudget(pendingUserText?: string): LiveBudgetView;
|
|
251
|
+
/**
|
|
252
|
+
* The published live budget view. A pure getter for renderers and tools:
|
|
253
|
+
* it never rescans the conversation or reprices the footer. The first call
|
|
254
|
+
* of a session publishes once, because a view has to exist before it can be
|
|
255
|
+
* read.
|
|
256
|
+
*/
|
|
257
|
+
liveBudget(): LiveBudgetView;
|
|
258
|
+
/** Inspect only when the native engine owns the request; never initialize it. */
|
|
259
|
+
inspectLiveBudget(): BudgetInspection;
|
|
191
260
|
/**
|
|
192
261
|
* The loaded context window this session already recorded for a target and
|
|
193
262
|
* model, so a resume budgets against it instead of re-probing. Null when the
|
|
@@ -203,6 +272,7 @@ export interface TurnContext {
|
|
|
203
272
|
pendingUserText?: string,
|
|
204
273
|
pendingSkillPolicy?: PendingSkillToolPolicy,
|
|
205
274
|
signal?: AbortSignal,
|
|
275
|
+
handoff?: ContinuityReductionHooks,
|
|
206
276
|
): Promise<boolean>;
|
|
207
277
|
cancelCompaction(): void;
|
|
208
278
|
postToolContinuationGuard(
|
|
@@ -218,7 +288,7 @@ export interface TurnContext {
|
|
|
218
288
|
}
|
|
219
289
|
| undefined
|
|
220
290
|
>;
|
|
221
|
-
contextUsage():
|
|
291
|
+
contextUsage(): LiveContextUsage;
|
|
222
292
|
contextLedger(): ContextLedger;
|
|
223
293
|
emitContextWindowWarningTransition(warning: string | null): void;
|
|
224
294
|
/** Record one known cache disturbance for the current or next call. */
|
|
@@ -235,7 +305,14 @@ export interface TurnContext {
|
|
|
235
305
|
* of a disturbance, it is the prefix the next turn wants already resident.
|
|
236
306
|
*/
|
|
237
307
|
notePrewarm(prewarm: PrewarmStats): void;
|
|
238
|
-
|
|
308
|
+
/**
|
|
309
|
+
* Drop per-session accounting. `branchAnchorTurnId` is the leaf the incoming
|
|
310
|
+
* session's next turn parents under; it becomes the advisory branch identity,
|
|
311
|
+
* which is why it is supplied only here. Ordinary appends advance the leaf
|
|
312
|
+
* without re-arming an advisory; real navigation re-arms it.
|
|
313
|
+
*/
|
|
314
|
+
resetForSession(branchAnchorTurnId?: string | null): void;
|
|
315
|
+
navigationRevision(): number;
|
|
239
316
|
dispose(): void;
|
|
240
317
|
}
|
|
241
318
|
|
|
@@ -260,6 +337,43 @@ function evictionSkipMessage(
|
|
|
260
337
|
});
|
|
261
338
|
}
|
|
262
339
|
|
|
340
|
+
/**
|
|
341
|
+
* The token and route facts of one budget publication, separate from the
|
|
342
|
+
* identity fields that name it. Split out so the live and pre-runtime paths
|
|
343
|
+
* produce the same shape and the reduction basis can be keyed over it after it
|
|
344
|
+
* is resolved rather than before.
|
|
345
|
+
*/
|
|
346
|
+
type LiveBudgetAccounting = Pick<
|
|
347
|
+
LiveBudgetInput,
|
|
348
|
+
| "targetId"
|
|
349
|
+
| "runtimeId"
|
|
350
|
+
| "modelId"
|
|
351
|
+
| "modelApi"
|
|
352
|
+
| "effectiveWindow"
|
|
353
|
+
| "windowSource"
|
|
354
|
+
| "estimatedInputTokens"
|
|
355
|
+
| "anchoredInputTokens"
|
|
356
|
+
| "inputTokens"
|
|
357
|
+
| "inputSource"
|
|
358
|
+
| "historical"
|
|
359
|
+
| "breakdown"
|
|
360
|
+
| "breakdownSource"
|
|
361
|
+
| "outputReserveTokens"
|
|
362
|
+
>;
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Content identity of a message list.
|
|
366
|
+
*
|
|
367
|
+
* The one serialization used for both jobs that need to know whether the same
|
|
368
|
+
* message objects still hold the same bytes: validating a provider anchor's
|
|
369
|
+
* attested prefix, and fingerprinting the live request. Token counts cannot do
|
|
370
|
+
* either, because `estimateAgentMessageTokens` prices by character count and a
|
|
371
|
+
* same-length replacement leaves every figure identical.
|
|
372
|
+
*/
|
|
373
|
+
function messageListDigest(messages: ReadonlyArray<AgentMessage>): string {
|
|
374
|
+
return createHash("sha256").update(JSON.stringify(messages)).digest("hex");
|
|
375
|
+
}
|
|
376
|
+
|
|
263
377
|
export function createTurnContext(deps: TurnContextDeps): TurnContext {
|
|
264
378
|
const { state, middleware } = deps;
|
|
265
379
|
const compactionTrigger = new AutoCompactionTrigger<CompactResult | null>();
|
|
@@ -282,6 +396,15 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
|
|
|
282
396
|
let reconciledAnchor: {
|
|
283
397
|
tokens: number;
|
|
284
398
|
anchoredMessages: ReadonlyArray<AgentMessage>;
|
|
399
|
+
/**
|
|
400
|
+
* Content identity of `anchoredMessages` when the attestation was taken.
|
|
401
|
+
* Object identity alone cannot detect an in-place edit: rewriting a
|
|
402
|
+
* message's text with a different string of the same length leaves the
|
|
403
|
+
* reference, the length, and therefore every chars/4 figure unchanged,
|
|
404
|
+
* while the provider counted different tokens. Without this the anchor
|
|
405
|
+
* would keep lending its authority to a prefix the provider never saw.
|
|
406
|
+
*/
|
|
407
|
+
contentDigest: string;
|
|
285
408
|
runtime: AgentRuntime;
|
|
286
409
|
model: AgentRuntime["agent"]["state"]["model"];
|
|
287
410
|
modelKey: string;
|
|
@@ -322,12 +445,38 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
|
|
|
322
445
|
let resumedPromptHashRead = false;
|
|
323
446
|
let resumedPromptHash: string | null = null;
|
|
324
447
|
const sessionWorkingContextPaths = new Set<string>();
|
|
448
|
+
let memoryTurnSequence = 0;
|
|
449
|
+
let memoryAuthorityEpoch = 0;
|
|
450
|
+
let memoryTurn: {
|
|
451
|
+
id: string;
|
|
452
|
+
sessionId: string | null;
|
|
453
|
+
sessionAuthority: string;
|
|
454
|
+
origin: string;
|
|
455
|
+
taskText: string;
|
|
456
|
+
activePaths: readonly string[];
|
|
457
|
+
} | null = null;
|
|
458
|
+
const memoryOrigin = (runtime: AgentRuntime): string =>
|
|
459
|
+
JSON.stringify([process.cwd(), runtime.targetId, runtime.runtimeId, runtime.wireModelId]);
|
|
325
460
|
let pendingPromptLogEntry: SessionPromptCompileRecord | null = null;
|
|
326
461
|
// Reuse an empty automatic attempt only for identical ledger and provider
|
|
327
462
|
// context. Same-turn tool growth or same-length content replacement must
|
|
328
463
|
// get a fresh cut; ordinary below-threshold checks never fingerprint it.
|
|
329
464
|
let emptyAutoCompactContextKey: string | null = null;
|
|
330
465
|
|
|
466
|
+
// The live budget view and the identity that decides when an advisory
|
|
467
|
+
// re-arms. `branchAnchorTurnId` moves only on real branch navigation, so two
|
|
468
|
+
// tool batches advancing `state.lastTurnId` under the same branch keep one
|
|
469
|
+
// advisory epoch; `navigationEpoch` separates two visits to the same leaf id.
|
|
470
|
+
const budgetProducer = createLiveBudgetProducer();
|
|
471
|
+
let branchAnchorTurnId: string | null = null;
|
|
472
|
+
let navigationEpoch = 0;
|
|
473
|
+
// Reduction-basis key of the last automatic attempt that produced nothing.
|
|
474
|
+
// It is the post-accounting identity, so a recalibrated anchor or a
|
|
475
|
+
// different resolved output cap clears the refusal even when the
|
|
476
|
+
// conversation, the route, and the configuration are byte-identical. The
|
|
477
|
+
// pressure policy reuses a refusal only for this exact key.
|
|
478
|
+
let noUsefulCutBasisKey: string | null = null;
|
|
479
|
+
|
|
331
480
|
// Cache-disturbance honesty (T3.3). Accumulate every known local-runtime
|
|
332
481
|
// disturbance and prefix-byte change since the last settled run. The next
|
|
333
482
|
// submit consumes the set, stamps `promptCache.expectedColdReasons` on its
|
|
@@ -517,7 +666,7 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
|
|
|
517
666
|
return resumedPromptHash;
|
|
518
667
|
};
|
|
519
668
|
|
|
520
|
-
const resolveWindowWithoutRuntime = (): ContextWindowDetails | null => {
|
|
669
|
+
const resolveWindowWithoutRuntime = (allowLedgerRead = true): ContextWindowDetails | null => {
|
|
521
670
|
const settings = deps.getSettings();
|
|
522
671
|
const targetId = settings.chat?.target?.trim();
|
|
523
672
|
const wireModelId = settings.chat?.model?.trim();
|
|
@@ -529,7 +678,13 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
|
|
|
529
678
|
use: "orchestrator",
|
|
530
679
|
requireTools: false,
|
|
531
680
|
requireOutputBudget: true,
|
|
532
|
-
knownLoadedContextWindow:
|
|
681
|
+
knownLoadedContextWindow: allowLedgerRead
|
|
682
|
+
? rememberedLoadedContextWindow(targetId, wireModelId)
|
|
683
|
+
: currentContextSnapshot?.contextWindowSource === "loaded" &&
|
|
684
|
+
currentContextSnapshot.providerId === targetId &&
|
|
685
|
+
currentContextSnapshot.modelId === wireModelId
|
|
686
|
+
? currentContextSnapshot.effectiveContextWindow
|
|
687
|
+
: null,
|
|
533
688
|
});
|
|
534
689
|
return resolved.ok ? resolved.target.contextWindowDetails : null;
|
|
535
690
|
};
|
|
@@ -555,12 +710,14 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
|
|
|
555
710
|
* resumed snapshot's recorded window, which is what the previous process
|
|
556
711
|
* measured the same messages against.
|
|
557
712
|
*/
|
|
558
|
-
const windowWithoutRuntime = (
|
|
713
|
+
const windowWithoutRuntime = (
|
|
714
|
+
allowLedgerRead = true,
|
|
715
|
+
): {
|
|
559
716
|
contextWindow: number;
|
|
560
717
|
contextWindowSource: ContextWindowSource | null;
|
|
561
718
|
contextWindowSlots: ContextWindowDetails["contextWindowSlots"];
|
|
562
719
|
} => {
|
|
563
|
-
const details = resolveWindowWithoutRuntime();
|
|
720
|
+
const details = resolveWindowWithoutRuntime(allowLedgerRead);
|
|
564
721
|
if (details) {
|
|
565
722
|
return {
|
|
566
723
|
contextWindow: details.effectiveContextWindow,
|
|
@@ -589,6 +746,12 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
|
|
|
589
746
|
const anchor = reconciledAnchor;
|
|
590
747
|
if (!anchor) return null;
|
|
591
748
|
const messages = agentRuntime.agent.state.messages.filter((message) => message.role !== "system");
|
|
749
|
+
// The identity, route, and length checks are cheap and run first; `||`
|
|
750
|
+
// short-circuits, so the content digest is only computed for a prefix that
|
|
751
|
+
// still looks like the attested one. The digest covers the attested prefix
|
|
752
|
+
// and never the tail, so an append does not invalidate the anchor; the
|
|
753
|
+
// prefix itself is re-serialized and rehashed on every validation, which
|
|
754
|
+
// is the cost of detecting an in-place edit at all.
|
|
592
755
|
if (
|
|
593
756
|
anchor.runtime !== agentRuntime ||
|
|
594
757
|
anchor.model !== agentRuntime.agent.state.model ||
|
|
@@ -597,7 +760,8 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
|
|
|
597
760
|
anchor.runtimeId !== agentRuntime.runtimeId ||
|
|
598
761
|
anchor.wireModelId !== agentRuntime.wireModelId ||
|
|
599
762
|
anchor.anchoredMessages.length > messages.length ||
|
|
600
|
-
anchor.anchoredMessages.some((message, index) => message !== messages[index])
|
|
763
|
+
anchor.anchoredMessages.some((message, index) => message !== messages[index]) ||
|
|
764
|
+
messageListDigest(messages.slice(0, anchor.anchoredMessages.length)) !== anchor.contentDigest
|
|
601
765
|
) {
|
|
602
766
|
reconciledAnchor = null;
|
|
603
767
|
return null;
|
|
@@ -611,13 +775,14 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
|
|
|
611
775
|
return anchor.tokens + tail;
|
|
612
776
|
};
|
|
613
777
|
|
|
778
|
+
let pendingUserImages: ReadonlyArray<unknown> = [];
|
|
614
779
|
const liveContextEstimate = (agentRuntime: AgentRuntime, pendingUserText?: string): LiveContextEstimate => {
|
|
615
780
|
const contextWindow = agentRuntime.runtimeResolution.contextWindowDetails.effectiveContextWindow;
|
|
616
781
|
const estimateInput = {
|
|
617
782
|
systemPrompt: agentRuntime.agent.state.systemPrompt,
|
|
618
783
|
messages: agentRuntime.agent.state.messages.filter((message) => message.role !== "system"),
|
|
619
784
|
tools: agentRuntime.agent.state.tools,
|
|
620
|
-
...(pendingUserText !== undefined ? { pendingUserText } : {}),
|
|
785
|
+
...(pendingUserText !== undefined ? { pendingUserText, pendingUserImages } : {}),
|
|
621
786
|
};
|
|
622
787
|
const breakdown = estimateAgentContextBreakdown(estimateInput);
|
|
623
788
|
const historyTokens = reconciledHistoryTokens(agentRuntime);
|
|
@@ -648,6 +813,240 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
|
|
|
648
813
|
};
|
|
649
814
|
};
|
|
650
815
|
|
|
816
|
+
const sha256Json = (value: unknown): string => createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* Identity of the exact material the next request would carry, plus the
|
|
820
|
+
* settings that decide its reserve and whether a reduction is even eligible.
|
|
821
|
+
* This is the pre-accounting half: the reduction basis adds the resolved
|
|
822
|
+
* estimate, cap, and reservation on top of it.
|
|
823
|
+
*
|
|
824
|
+
* Token counts alone cannot do this job: `estimateAgentMessageTokens` prices
|
|
825
|
+
* by character count, so replacing a message with another of the same length
|
|
826
|
+
* leaves every figure identical while the request is a different one. The
|
|
827
|
+
* conversation is therefore hashed by content, not by length or count.
|
|
828
|
+
*/
|
|
829
|
+
const liveBudgetContentFingerprint = (
|
|
830
|
+
agentRuntime: AgentRuntime | null,
|
|
831
|
+
pendingUserText: string | undefined,
|
|
832
|
+
promptFingerprint: string | null,
|
|
833
|
+
toolSignature: string | null,
|
|
834
|
+
): string => {
|
|
835
|
+
const settings = deps.getSettings();
|
|
836
|
+
const eligibility = {
|
|
837
|
+
compaction: settings.context.compaction ?? null,
|
|
838
|
+
workingSet: settings.context.workingSet ?? null,
|
|
839
|
+
maxOutputTokens: settings.chat?.maxOutputTokens ?? null,
|
|
840
|
+
};
|
|
841
|
+
if (!agentRuntime) {
|
|
842
|
+
const snapshot = currentContextSnapshot;
|
|
843
|
+
return sha256Json([
|
|
844
|
+
"historical/1",
|
|
845
|
+
settings.chat?.target ?? null,
|
|
846
|
+
settings.chat?.model ?? null,
|
|
847
|
+
snapshot?.snapshotId ?? null,
|
|
848
|
+
snapshot ? snapshotInputTokens(snapshot) : null,
|
|
849
|
+
snapshot?.sources.total ?? null,
|
|
850
|
+
windowWithoutRuntime(false).contextWindow,
|
|
851
|
+
promptFingerprint,
|
|
852
|
+
toolSignature,
|
|
853
|
+
eligibility,
|
|
854
|
+
]);
|
|
855
|
+
}
|
|
856
|
+
const details = agentRuntime.runtimeResolution.contextWindowDetails;
|
|
857
|
+
return sha256Json([
|
|
858
|
+
"live/1",
|
|
859
|
+
agentRuntime.targetId,
|
|
860
|
+
agentRuntime.runtimeId,
|
|
861
|
+
agentRuntime.wireModelId,
|
|
862
|
+
agentRuntime.agent.state.model?.api ?? null,
|
|
863
|
+
details.effectiveContextWindow,
|
|
864
|
+
details.contextWindowSource,
|
|
865
|
+
promptFingerprint,
|
|
866
|
+
toolSignature,
|
|
867
|
+
// Same serialization the anchor validates its attested prefix with, so
|
|
868
|
+
// the two cannot disagree about whether the conversation changed.
|
|
869
|
+
messageListDigest(agentRuntime.agent.state.messages.filter((message) => message.role !== "system")),
|
|
870
|
+
pendingUserText ?? null,
|
|
871
|
+
pendingUserText === undefined ? null : pendingUserImages,
|
|
872
|
+
eligibility,
|
|
873
|
+
]);
|
|
874
|
+
};
|
|
875
|
+
|
|
876
|
+
/**
|
|
877
|
+
* A captured decomposition, labelled as one. The mapping is the same the
|
|
878
|
+
* `/context` overlay already uses, so the two cannot disagree; it is not a
|
|
879
|
+
* fresh measurement of the live agent state and is never presented as one.
|
|
880
|
+
*/
|
|
881
|
+
const capturedBreakdown = (snapshot: ContextSnapshot, pendingTokens: number): LiveBudgetBreakdown => ({
|
|
882
|
+
systemPromptTokens: snapshot.categories.system,
|
|
883
|
+
messageTokens: snapshot.categories.messages + (snapshot.categories.toolResults ?? 0),
|
|
884
|
+
pendingUserTokens: pendingTokens,
|
|
885
|
+
toolSchemaTokens: snapshot.categories.tools,
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
const optionalProjection = <T>(read: (() => T | null) | undefined): T | null => {
|
|
889
|
+
if (!read) return null;
|
|
890
|
+
try {
|
|
891
|
+
return read();
|
|
892
|
+
} catch {
|
|
893
|
+
// A continuity projection is supplemental. Losing it must not stop the
|
|
894
|
+
// budget from being published.
|
|
895
|
+
return null;
|
|
896
|
+
}
|
|
897
|
+
};
|
|
898
|
+
|
|
899
|
+
/**
|
|
900
|
+
* Token facts of the live next request, all from one `liveContextEstimate`
|
|
901
|
+
* call: the same one admission budgets against. Old snapshot categories are
|
|
902
|
+
* never summed to reconstruct the total, and streaming output is never added
|
|
903
|
+
* on top, because the live next-request message list already carries the
|
|
904
|
+
* completed assistant turn and the estimate already carries the pending
|
|
905
|
+
* text. Each is charged exactly once.
|
|
906
|
+
*/
|
|
907
|
+
const liveRuntimeAccounting = (
|
|
908
|
+
agentRuntime: AgentRuntime,
|
|
909
|
+
pendingUserText: string | undefined,
|
|
910
|
+
): LiveBudgetAccounting => {
|
|
911
|
+
const estimate = liveContextEstimate(agentRuntime, pendingUserText);
|
|
912
|
+
const details = agentRuntime.runtimeResolution.contextWindowDetails;
|
|
913
|
+
return {
|
|
914
|
+
targetId: agentRuntime.targetId,
|
|
915
|
+
runtimeId: agentRuntime.runtimeId,
|
|
916
|
+
modelId: agentRuntime.wireModelId,
|
|
917
|
+
modelApi: agentRuntime.agent.state.model?.api ?? null,
|
|
918
|
+
effectiveWindow: details.effectiveContextWindow > 0 ? details.effectiveContextWindow : null,
|
|
919
|
+
windowSource: details.contextWindowSource,
|
|
920
|
+
estimatedInputTokens: estimate.estimatedTokens,
|
|
921
|
+
anchoredInputTokens: estimate.reconciledTokens,
|
|
922
|
+
inputTokens: estimate.tokens,
|
|
923
|
+
inputSource: estimate.reconciledTokens === null ? "estimated" : "anchored-plus-estimated-tail",
|
|
924
|
+
historical: false,
|
|
925
|
+
breakdown: { ...estimate.breakdown },
|
|
926
|
+
breakdownSource: "live",
|
|
927
|
+
outputReserveTokens: resolveTurnOutputReserve(agentRuntime, estimate.tokens),
|
|
928
|
+
};
|
|
929
|
+
};
|
|
930
|
+
|
|
931
|
+
/** Token facts before this process built a runtime: a persisted measurement, labelled as one. */
|
|
932
|
+
const historicalAccounting = (
|
|
933
|
+
snapshot: ContextSnapshot | null,
|
|
934
|
+
settings: Readonly<ClioSettings>,
|
|
935
|
+
): LiveBudgetAccounting => {
|
|
936
|
+
// Inspection uses already-loaded capture facts, never a diagnostic-ledger scan.
|
|
937
|
+
const sameRoute =
|
|
938
|
+
!snapshot || (snapshot.providerId === settings.chat?.target && snapshot.modelId === settings.chat?.model);
|
|
939
|
+
// A saved request from model A cannot acquire model B's window while
|
|
940
|
+
// retaining A's identity and input measurement. Keep that capture coherent.
|
|
941
|
+
const window =
|
|
942
|
+
snapshot && !sameRoute
|
|
943
|
+
? {
|
|
944
|
+
contextWindow: snapshot.effectiveContextWindow,
|
|
945
|
+
contextWindowSource: snapshotWindowSource(snapshot),
|
|
946
|
+
}
|
|
947
|
+
: windowWithoutRuntime(false);
|
|
948
|
+
const pendingTokens = pendingUserInputTokens();
|
|
949
|
+
return {
|
|
950
|
+
targetId: snapshot?.providerId ?? settings.chat?.target ?? null,
|
|
951
|
+
runtimeId: snapshot?.runtimeId ?? null,
|
|
952
|
+
modelId: snapshot?.modelId ?? settings.chat?.model ?? null,
|
|
953
|
+
modelApi: null,
|
|
954
|
+
effectiveWindow: window.contextWindow > 0 ? window.contextWindow : null,
|
|
955
|
+
windowSource: window.contextWindowSource,
|
|
956
|
+
estimatedInputTokens: snapshot?.estimatedTokens ?? null,
|
|
957
|
+
anchoredInputTokens: snapshot?.reconciledTokens ?? null,
|
|
958
|
+
inputTokens: snapshot ? snapshotInputTokens(snapshot) + pendingTokens : null,
|
|
959
|
+
inputSource: snapshot ? "historical" : "unknown",
|
|
960
|
+
historical: true,
|
|
961
|
+
breakdown: snapshot ? capturedBreakdown(snapshot, pendingTokens) : null,
|
|
962
|
+
breakdownSource: snapshot ? "captured" : null,
|
|
963
|
+
// No resolved route means no honest output reservation, and an unpriced
|
|
964
|
+
// reserve is unknown rather than zero.
|
|
965
|
+
outputReserveTokens: null,
|
|
966
|
+
};
|
|
967
|
+
};
|
|
968
|
+
|
|
969
|
+
/**
|
|
970
|
+
* Assemble one publication from the live runtime, or from the persisted
|
|
971
|
+
* capture when this process has not built a runtime yet.
|
|
972
|
+
*/
|
|
973
|
+
const liveBudgetInput = (pendingUserText?: string): LiveBudgetInput => {
|
|
974
|
+
const settings = deps.getSettings();
|
|
975
|
+
const agentRuntime = state.runtime;
|
|
976
|
+
const snapshot = currentContextSnapshot;
|
|
977
|
+
const promptFingerprint = agentRuntime
|
|
978
|
+
? sha256Json(agentRuntime.agent.state.systemPrompt ?? null)
|
|
979
|
+
: (snapshot?.promptHash ?? null);
|
|
980
|
+
const toolSignature = agentRuntime
|
|
981
|
+
? toolSignatureFromState(agentRuntime.agent.state.tools)
|
|
982
|
+
: (snapshot?.toolSignature ?? null);
|
|
983
|
+
const contentFingerprint = liveBudgetContentFingerprint(
|
|
984
|
+
agentRuntime,
|
|
985
|
+
pendingUserText,
|
|
986
|
+
promptFingerprint,
|
|
987
|
+
toolSignature,
|
|
988
|
+
);
|
|
989
|
+
// Two independent existing settings, not one derived from the other: the
|
|
990
|
+
// reduce point is `context.compaction.threshold` and the working-set
|
|
991
|
+
// target is `context.workingSet.target`. A pair the policy refuses is
|
|
992
|
+
// reported as a refusal rather than repaired into a fabricated setting.
|
|
993
|
+
const policyResolution = resolveLiveBudgetPolicy(
|
|
994
|
+
settings.context.compaction?.threshold,
|
|
995
|
+
settings.context.workingSet?.target,
|
|
996
|
+
);
|
|
997
|
+
|
|
998
|
+
// Accounting first, then the identity that describes it. A reduction
|
|
999
|
+
// refusal is only reusable for the request the reducer actually saw, and
|
|
1000
|
+
// the resolved anchor, cap, and reservation are part of that request even
|
|
1001
|
+
// when not a byte of the conversation moved.
|
|
1002
|
+
const accounting = agentRuntime
|
|
1003
|
+
? liveRuntimeAccounting(agentRuntime, pendingUserText)
|
|
1004
|
+
: historicalAccounting(snapshot, settings);
|
|
1005
|
+
const reductionBasisKey = sha256Json([
|
|
1006
|
+
"reduction-basis/1",
|
|
1007
|
+
contentFingerprint,
|
|
1008
|
+
accounting.effectiveWindow,
|
|
1009
|
+
accounting.estimatedInputTokens,
|
|
1010
|
+
accounting.anchoredInputTokens,
|
|
1011
|
+
accounting.inputTokens,
|
|
1012
|
+
accounting.inputSource,
|
|
1013
|
+
accounting.outputReserveTokens,
|
|
1014
|
+
// The resolved cap can move without moving the reservation, because a
|
|
1015
|
+
// route clamp may be the binding constraint. Both belong in the key.
|
|
1016
|
+
agentRuntime?.runtimeResolution.capabilityDecisions?.maxTokens ?? null,
|
|
1017
|
+
snapshot?.categories.reserve ?? null,
|
|
1018
|
+
policyResolution.policy.reduce,
|
|
1019
|
+
policyResolution.policy.target,
|
|
1020
|
+
]);
|
|
1021
|
+
|
|
1022
|
+
return {
|
|
1023
|
+
...accounting,
|
|
1024
|
+
sessionId: deps.session?.current()?.id ?? snapshot?.sessionId ?? null,
|
|
1025
|
+
branchAnchorTurnId,
|
|
1026
|
+
activeLeafTurnId: state.lastTurnId,
|
|
1027
|
+
activeUserTurnId: state.activeUserTurnId,
|
|
1028
|
+
capturedSnapshotId: snapshot?.snapshotId ?? null,
|
|
1029
|
+
// Compaction headroom recorded by the capture. Carried under its own
|
|
1030
|
+
// name so nothing can mistake it for the output reserve.
|
|
1031
|
+
thresholdReserveTokens: snapshot?.categories.reserve ?? null,
|
|
1032
|
+
policy: policyResolution.policy,
|
|
1033
|
+
policyRejection: policyResolution.rejection,
|
|
1034
|
+
// Reduction has not been asked about unless an automatic attempt for
|
|
1035
|
+
// this exact basis already came back empty.
|
|
1036
|
+
reduction: noUsefulCutBasisKey !== null && noUsefulCutBasisKey === reductionBasisKey ? "no-useful-cut" : "unknown",
|
|
1037
|
+
reductionBasisKey,
|
|
1038
|
+
promptFingerprint,
|
|
1039
|
+
toolSignature,
|
|
1040
|
+
navigationEpoch,
|
|
1041
|
+
lastReduction: lastCompactionEvent ? { ...lastCompactionEvent } : null,
|
|
1042
|
+
pendingHandoff: optionalProjection(deps.getPendingHandoff),
|
|
1043
|
+
lastOutcome: optionalProjection(deps.getLastOutcome),
|
|
1044
|
+
};
|
|
1045
|
+
};
|
|
1046
|
+
|
|
1047
|
+
const refreshLiveBudget = (pendingUserText?: string): LiveBudgetView =>
|
|
1048
|
+
budgetProducer.publish(liveBudgetInput(pendingUserText));
|
|
1049
|
+
|
|
651
1050
|
/**
|
|
652
1051
|
* A working-set projection removes tokens from messages the attestation
|
|
653
1052
|
* covered; it does not make the attestation wrong about the rest. Subtract
|
|
@@ -664,10 +1063,15 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
|
|
|
664
1063
|
tokensRemoved: number,
|
|
665
1064
|
): void => {
|
|
666
1065
|
if (!reconciledAnchor || historyTokens === null) return;
|
|
1066
|
+
const anchoredMessages = [...agentRuntime.agent.state.messages.filter((message) => message.role !== "system")];
|
|
667
1067
|
reconciledAnchor = {
|
|
668
1068
|
...reconciledAnchor,
|
|
669
1069
|
tokens: Math.max(0, historyTokens - Math.max(0, tokensRemoved)),
|
|
670
|
-
anchoredMessages
|
|
1070
|
+
anchoredMessages,
|
|
1071
|
+
// Re-anchoring on the projected list is the point of this carry, so its
|
|
1072
|
+
// content identity is retaken here. Keeping the old digest would fail
|
|
1073
|
+
// the next validation and discard the attestation this preserves.
|
|
1074
|
+
contentDigest: messageListDigest(anchoredMessages),
|
|
671
1075
|
};
|
|
672
1076
|
};
|
|
673
1077
|
|
|
@@ -677,6 +1081,10 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
|
|
|
677
1081
|
agentRuntime.agent,
|
|
678
1082
|
buildModelReplayAgentMessagesFromTurns(refreshedEntries, {
|
|
679
1083
|
...(state.lastTurnId ? { activeLeafTurnId: state.lastTurnId } : {}),
|
|
1084
|
+
// The post-compaction rebuild is where an accepted note has to
|
|
1085
|
+
// survive: wiring continuity only into /fork would lose it on the
|
|
1086
|
+
// very refresh the reduction triggers.
|
|
1087
|
+
continuity: continuityContextFromSession(deps.session),
|
|
680
1088
|
}),
|
|
681
1089
|
);
|
|
682
1090
|
state.replayedContextMessages = [];
|
|
@@ -737,7 +1145,7 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
|
|
|
737
1145
|
* the LLM summary directly. Used for `/context compact`, CLIO_CODER_FORCE_COMPACT=1,
|
|
738
1146
|
* and overflow recovery.
|
|
739
1147
|
*/
|
|
740
|
-
const
|
|
1148
|
+
const performAutoCompact = async (
|
|
741
1149
|
agentRuntime: AgentRuntime,
|
|
742
1150
|
force: boolean,
|
|
743
1151
|
instructions?: string,
|
|
@@ -745,11 +1153,14 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
|
|
|
745
1153
|
pendingUserText?: string,
|
|
746
1154
|
pendingSkillPolicy?: PendingSkillToolPolicy,
|
|
747
1155
|
signal?: AbortSignal,
|
|
1156
|
+
handoff?: ContinuityReductionHooks,
|
|
748
1157
|
): Promise<boolean> => {
|
|
749
1158
|
if (!deps.readSessionEntries) return false;
|
|
1159
|
+
const originSession = deps.session?.current()?.id;
|
|
1160
|
+
const originNavigation = navigationEpoch;
|
|
750
1161
|
// Overflow forces a fit attempt even below the automatic threshold, but
|
|
751
1162
|
// still needs the request budget and active task. Manual force keeps its defaults.
|
|
752
|
-
const useRequestBudget = !force || triggerOverride === "overflow";
|
|
1163
|
+
const useRequestBudget = !force || triggerOverride === "overflow" || handoff !== undefined;
|
|
753
1164
|
const activeAutoTurnId = useRequestBudget ? state.activeUserTurnId : null;
|
|
754
1165
|
const skillContextState = mainSkillContextState(
|
|
755
1166
|
filterEntriesToActivePath(deps.readSessionEntries(), state.lastTurnId ?? undefined),
|
|
@@ -760,9 +1171,11 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
|
|
|
760
1171
|
const autoEnabled = cfg?.auto !== false;
|
|
761
1172
|
if (!force && !autoEnabled) return false;
|
|
762
1173
|
const compactionThreshold = cfg?.threshold ?? DEFAULT_COMPACTION_THRESHOLD;
|
|
763
|
-
const
|
|
1174
|
+
const requiredFit = triggerOverride === "overflow";
|
|
1175
|
+
const pressureEstimate = force && !requiredFit ? null : liveContextEstimate(agentRuntime, pendingUserText);
|
|
764
1176
|
if (
|
|
765
1177
|
pressureEstimate &&
|
|
1178
|
+
!requiredFit &&
|
|
766
1179
|
!shouldCompact(pressureEstimate.tokens, compactionThreshold, pressureEstimate.contextWindow).shouldCompact
|
|
767
1180
|
)
|
|
768
1181
|
return false;
|
|
@@ -802,7 +1215,12 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
|
|
|
802
1215
|
// actually follows.
|
|
803
1216
|
let evictionSkipNotice: string | null = null;
|
|
804
1217
|
const rememberEmptyAutomaticAttempt = (): void => {
|
|
805
|
-
if (
|
|
1218
|
+
if (force || preSummaryStageActed || !attemptKey) return;
|
|
1219
|
+
emptyAutoCompactContextKey = attemptKey;
|
|
1220
|
+
// The reducer was actually asked and found nothing. Record the request
|
|
1221
|
+
// it was asked about, so the pressure policy can suppress a repeat
|
|
1222
|
+
// advisory for that exact request and no other.
|
|
1223
|
+
noUsefulCutBasisKey = liveBudgetInput(pendingUserText).reductionBasisKey;
|
|
806
1224
|
};
|
|
807
1225
|
|
|
808
1226
|
const trigger: CompactionTrigger = triggerOverride ?? (force ? "force" : "auto");
|
|
@@ -816,7 +1234,7 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
|
|
|
816
1234
|
// reachable only when explicitly requested.
|
|
817
1235
|
if (deps.session?.current()) {
|
|
818
1236
|
const beforeSnapshotId = currentContextSnapshot?.snapshotId ?? null;
|
|
819
|
-
if (process.env.CLIO_CODER_LEGACY_MASK === "1") {
|
|
1237
|
+
if (process.env.CLIO_CODER_LEGACY_MASK === "1" && !handoff) {
|
|
820
1238
|
let masked: ReturnType<typeof maskStaleObservations>;
|
|
821
1239
|
try {
|
|
822
1240
|
masked = maskStaleObservations(deps.readSessionEntries() ?? [], 6);
|
|
@@ -876,8 +1294,14 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
|
|
|
876
1294
|
`[context engine] mask_observations: ${masked.maskedObservations} observations masked${thinkingNote}; ~${estimate.tokens} tokens -> ~${tokensAfterMask} tokens`,
|
|
877
1295
|
);
|
|
878
1296
|
|
|
1297
|
+
refreshLiveBudget(pendingUserText);
|
|
879
1298
|
const after = liveContextEstimate(agentRuntime, pendingUserText);
|
|
880
|
-
if (
|
|
1299
|
+
if (
|
|
1300
|
+
requiredFit
|
|
1301
|
+
? requestFits(after.tokens, resolveTurnOutputReserve(agentRuntime, after.tokens), after.contextWindow)
|
|
1302
|
+
: !shouldCompact(after.tokens, compactionThreshold, after.contextWindow).shouldCompact
|
|
1303
|
+
)
|
|
1304
|
+
return true;
|
|
881
1305
|
}
|
|
882
1306
|
} else if (settings.context.workingSet.enabled) {
|
|
883
1307
|
let planned: ReturnType<typeof planEviction>;
|
|
@@ -897,8 +1321,18 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
|
|
|
897
1321
|
pressure: {
|
|
898
1322
|
tokens: estimate.tokens,
|
|
899
1323
|
contextWindow: estimate.contextWindow,
|
|
900
|
-
threshold:
|
|
901
|
-
|
|
1324
|
+
threshold: requiredFit
|
|
1325
|
+
? Math.min(
|
|
1326
|
+
compactionThreshold,
|
|
1327
|
+
Math.max(0, 1 - resolveTurnOutputReserve(agentRuntime, estimate.tokens) / estimate.contextWindow),
|
|
1328
|
+
)
|
|
1329
|
+
: compactionThreshold,
|
|
1330
|
+
target: requiredFit
|
|
1331
|
+
? Math.min(
|
|
1332
|
+
settings.context.workingSet.target,
|
|
1333
|
+
Math.max(0, 1 - resolveTurnOutputReserve(agentRuntime, estimate.tokens) / estimate.contextWindow),
|
|
1334
|
+
)
|
|
1335
|
+
: settings.context.workingSet.target,
|
|
902
1336
|
},
|
|
903
1337
|
estimateTokens,
|
|
904
1338
|
});
|
|
@@ -914,7 +1348,7 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
|
|
|
914
1348
|
emitCompactionActivity("started", "compacting context (working-set eviction)");
|
|
915
1349
|
try {
|
|
916
1350
|
const historyTokens = reconciledHistoryTokens(agentRuntime);
|
|
917
|
-
deps.session.appendEntry({
|
|
1351
|
+
const evictionEntry = deps.session.appendEntry({
|
|
918
1352
|
...buildEvictionFields(planned, {
|
|
919
1353
|
trigger: "pressure",
|
|
920
1354
|
pressureBefore: verdict.pressure,
|
|
@@ -924,6 +1358,10 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
|
|
|
924
1358
|
// cursor is the leaf the next message will extend.
|
|
925
1359
|
parentTurnId: state.lastTurnId,
|
|
926
1360
|
});
|
|
1361
|
+
if (deps.session.flushAppends) {
|
|
1362
|
+
deps.session.flushAppends();
|
|
1363
|
+
if (!handoff) notifyMemoryCommit(evictionEntry.turnId, "summary", "evicted");
|
|
1364
|
+
}
|
|
927
1365
|
noteColdReason("working_set_evict");
|
|
928
1366
|
refreshAgentMessagesFromSession(agentRuntime);
|
|
929
1367
|
carryReconciledAnchorThroughProjection(agentRuntime, historyTokens, planned.tokensBefore - planned.tokensAfter);
|
|
@@ -967,8 +1405,19 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
|
|
|
967
1405
|
`[context engine] working set: ${planned.items.length} ${itemsWord} evicted by ${planned.policyId}; ~${planned.tokensBefore} -> ~${planned.tokensAfter} tokens, recall by ref with context(scope="recall")`,
|
|
968
1406
|
);
|
|
969
1407
|
|
|
1408
|
+
// Published only after the carried anchor and the post-eviction
|
|
1409
|
+
// capture are both in place: `liveContextEstimate` clears an
|
|
1410
|
+
// anchor whose message prefix no longer matches, so pricing the
|
|
1411
|
+
// rebuilt list any earlier would discard the attestation the
|
|
1412
|
+
// projection carry exists to preserve.
|
|
1413
|
+
refreshLiveBudget(pendingUserText);
|
|
970
1414
|
const after = liveContextEstimate(agentRuntime, pendingUserText);
|
|
971
|
-
if (
|
|
1415
|
+
if (
|
|
1416
|
+
requiredFit
|
|
1417
|
+
? requestFits(after.tokens, resolveTurnOutputReserve(agentRuntime, after.tokens), after.contextWindow)
|
|
1418
|
+
: !shouldCompact(after.tokens, compactionThreshold, after.contextWindow).shouldCompact
|
|
1419
|
+
)
|
|
1420
|
+
return true;
|
|
972
1421
|
} catch (error) {
|
|
973
1422
|
emitCompactionActivity("failed", compactionFailureMessage(error));
|
|
974
1423
|
throw error;
|
|
@@ -1017,18 +1466,19 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
|
|
|
1017
1466
|
snapshotMetadata,
|
|
1018
1467
|
);
|
|
1019
1468
|
let budget:
|
|
1020
|
-
| Pick<
|
|
1469
|
+
| Pick<
|
|
1470
|
+
CompactInput,
|
|
1471
|
+
| "keepRecentTokens"
|
|
1472
|
+
| "preserveUserTurnId"
|
|
1473
|
+
| "skillContextState"
|
|
1474
|
+
| "signal"
|
|
1475
|
+
| "beforeSummaryCall"
|
|
1476
|
+
| "checkpointForSummary"
|
|
1477
|
+
>
|
|
1021
1478
|
| undefined = skillContextState !== undefined ? { skillContextState } : undefined;
|
|
1022
1479
|
if (useRequestBudget) {
|
|
1023
1480
|
const estimate = liveContextEstimate(agentRuntime, pendingUserText);
|
|
1024
|
-
const output =
|
|
1025
|
-
resolveReservedOutputTokens(agentRuntime.agent.state.model?.maxTokens, {
|
|
1026
|
-
api: agentRuntime.agent.state.model?.api ?? "",
|
|
1027
|
-
contextWindow: estimate.contextWindow,
|
|
1028
|
-
inputTokens: estimate.tokens,
|
|
1029
|
-
}),
|
|
1030
|
-
settings.chat.maxOutputTokens > 0 ? settings.chat.maxOutputTokens : Number.POSITIVE_INFINITY,
|
|
1031
|
-
);
|
|
1481
|
+
const output = resolveTurnOutputReserve(agentRuntime, estimate.tokens);
|
|
1032
1482
|
const inputTarget = Math.min(estimate.contextWindow * compactionThreshold, estimate.contextWindow - output);
|
|
1033
1483
|
const staticTokens = estimate.breakdown.systemPromptTokens + estimate.breakdown.toolSchemaTokens;
|
|
1034
1484
|
const calibration = Math.max(
|
|
@@ -1055,6 +1505,7 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
|
|
|
1055
1505
|
return (
|
|
1056
1506
|
(await deps.autoCompact?.(instructions, trigger, {
|
|
1057
1507
|
...budget,
|
|
1508
|
+
...handoff,
|
|
1058
1509
|
signal: summarySignal,
|
|
1059
1510
|
})) ?? null
|
|
1060
1511
|
);
|
|
@@ -1083,9 +1534,17 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
|
|
|
1083
1534
|
|
|
1084
1535
|
// The checkpoint identifies the selected summary route; older results
|
|
1085
1536
|
// fall back to the active chat route. The ledger carries usage for reseed; this is the
|
|
1086
|
-
// live sink, so `/
|
|
1537
|
+
// live sink, so `/usage` and the footer move the moment /context compact
|
|
1087
1538
|
// returns instead of staying byte-identical to before it ran.
|
|
1088
1539
|
recordCompactionUsage(agentRuntime, result);
|
|
1540
|
+
if (deps.memoryCommitBridge && !handoff && deps.session?.flushAppends) {
|
|
1541
|
+
deps.session.flushAppends();
|
|
1542
|
+
await deps.session.checkpoint("context-summary");
|
|
1543
|
+
if (originSession !== deps.session.current()?.id || originNavigation !== navigationEpoch || signal?.aborted)
|
|
1544
|
+
throw new Error("Context ownership changed during checkpoint.");
|
|
1545
|
+
const summary = [...deps.readSessionEntries()].reverse().find((entry) => entry.kind === "compactionSummary");
|
|
1546
|
+
if (summary) notifyMemoryCommit(summary.turnId, "summary", "summarized");
|
|
1547
|
+
}
|
|
1089
1548
|
|
|
1090
1549
|
refreshAgentMessagesFromSession(agentRuntime);
|
|
1091
1550
|
// A summary replaces the conversation the attestation described, so no
|
|
@@ -1129,6 +1588,75 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
|
|
|
1129
1588
|
isSplitTurn: result.isSplitTurn,
|
|
1130
1589
|
}),
|
|
1131
1590
|
);
|
|
1591
|
+
refreshLiveBudget(pendingUserText);
|
|
1592
|
+
return true;
|
|
1593
|
+
};
|
|
1594
|
+
|
|
1595
|
+
let reductionInFlight: Promise<boolean> | null = null;
|
|
1596
|
+
const runAutoCompact: TurnContext["runAutoCompact"] = (...args) => {
|
|
1597
|
+
if (reductionInFlight) {
|
|
1598
|
+
if (args[7]) return Promise.reject(new Error("Another context reduction is already in progress."));
|
|
1599
|
+
return reductionInFlight;
|
|
1600
|
+
}
|
|
1601
|
+
const operation = performAutoCompact(...args);
|
|
1602
|
+
reductionInFlight = operation;
|
|
1603
|
+
void operation
|
|
1604
|
+
.finally(() => {
|
|
1605
|
+
if (reductionInFlight === operation) reductionInFlight = null;
|
|
1606
|
+
})
|
|
1607
|
+
.catch(() => {});
|
|
1608
|
+
return operation;
|
|
1609
|
+
};
|
|
1610
|
+
|
|
1611
|
+
const bindMemoryScope = () => {
|
|
1612
|
+
const sessionId = deps.session?.current()?.id;
|
|
1613
|
+
if (!sessionId) return null;
|
|
1614
|
+
const scope = { sessionId, branchAnchorTurnId };
|
|
1615
|
+
deps.memoryCommitBridge?.bindCommitScope(scope);
|
|
1616
|
+
return scope;
|
|
1617
|
+
};
|
|
1618
|
+
const notifyMemoryCommit: TurnContext["notifyMemoryCommit"] = (commitId, kind, outcome) => {
|
|
1619
|
+
const scope = bindMemoryScope();
|
|
1620
|
+
if (scope) deps.memoryCommitBridge?.notifyContextCommitted({ ...scope, commitId, kind, outcome });
|
|
1621
|
+
};
|
|
1622
|
+
const installMemoryRestoration: TurnContext["installMemoryRestoration"] = (runtime, pendingText) => {
|
|
1623
|
+
const bridge = deps.memoryCommitBridge;
|
|
1624
|
+
if (!bridge || !bindMemoryScope()) return false;
|
|
1625
|
+
const entries = filterEntriesToActivePath(deps.readSessionEntries?.() ?? [], state.lastTurnId ?? undefined);
|
|
1626
|
+
const latest = [...entries]
|
|
1627
|
+
.reverse()
|
|
1628
|
+
.find((entry) => entry.kind === "continuityCommit" || entry.kind === "compactionSummary");
|
|
1629
|
+
const currentState =
|
|
1630
|
+
latest?.kind === "continuityCommit"
|
|
1631
|
+
? { kind: "handoff" as const, text: latest.continuity.accepted.note }
|
|
1632
|
+
: latest?.kind === "compactionSummary"
|
|
1633
|
+
? { kind: "summary" as const, text: latest.summary }
|
|
1634
|
+
: null;
|
|
1635
|
+
const view = refreshLiveBudget(pendingText);
|
|
1636
|
+
const available = Math.max(
|
|
1637
|
+
0,
|
|
1638
|
+
(view.effectiveWindow ?? 0) - (view.outputReserveTokens ?? 0) - (view.inputTokens ?? 0) - 64,
|
|
1639
|
+
);
|
|
1640
|
+
const offer = bridge.prepareRestoration(currentState, available);
|
|
1641
|
+
if (!offer) return false;
|
|
1642
|
+
if (!offer.message) {
|
|
1643
|
+
bridge.acknowledgeRestoration(offer);
|
|
1644
|
+
return false;
|
|
1645
|
+
}
|
|
1646
|
+
const message: AgentMessage = {
|
|
1647
|
+
role: "user",
|
|
1648
|
+
content: `<system-reminder>\n${offer.message}\n</system-reminder>`,
|
|
1649
|
+
timestamp: Date.now(),
|
|
1650
|
+
};
|
|
1651
|
+
const cost = estimateAgentMessageTokens(message);
|
|
1652
|
+
if (!requestFits((view.inputTokens ?? Infinity) + cost, view.outputReserveTokens, view.effectiveWindow)) return false;
|
|
1653
|
+
const prior = [...runtime.agent.state.messages];
|
|
1654
|
+
replaceEngineMessages(runtime.agent, [...prior, message]);
|
|
1655
|
+
if (!bridge.acknowledgeRestoration(offer)) {
|
|
1656
|
+
replaceEngineMessages(runtime.agent, prior);
|
|
1657
|
+
return false;
|
|
1658
|
+
}
|
|
1659
|
+
refreshLiveBudget(pendingText);
|
|
1132
1660
|
return true;
|
|
1133
1661
|
};
|
|
1134
1662
|
|
|
@@ -1148,21 +1676,74 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
|
|
|
1148
1676
|
});
|
|
1149
1677
|
|
|
1150
1678
|
return {
|
|
1679
|
+
notifyMemoryCommit,
|
|
1680
|
+
installMemoryRestoration,
|
|
1681
|
+
prepareMemoryTurn(runtime, input): void {
|
|
1682
|
+
bindMemoryScope();
|
|
1683
|
+
pendingUserImages = [...(input.images ?? [])];
|
|
1684
|
+
if (input.continuation && memoryTurn !== null) return;
|
|
1685
|
+
const sessionId = deps.session?.current()?.id ?? null;
|
|
1686
|
+
memoryTurnSequence += 1;
|
|
1687
|
+
memoryTurn = {
|
|
1688
|
+
id: String(memoryTurnSequence),
|
|
1689
|
+
sessionId,
|
|
1690
|
+
sessionAuthority: JSON.stringify([memoryAuthorityEpoch, sessionId ?? `pending:${memoryTurnSequence}`]),
|
|
1691
|
+
origin: memoryOrigin(runtime),
|
|
1692
|
+
taskText: input.taskText,
|
|
1693
|
+
activePaths: Object.freeze([...sessionWorkingContextPaths]),
|
|
1694
|
+
};
|
|
1695
|
+
},
|
|
1696
|
+
|
|
1697
|
+
commitMemoryTurn(runtime): void {
|
|
1698
|
+
pendingUserImages = [];
|
|
1699
|
+
// appendSubmittedUserTurn creates the first session synchronously.
|
|
1700
|
+
// Only that null -> bound transition with unchanged origin can alias
|
|
1701
|
+
// its prepared authority; navigation never uses this exception.
|
|
1702
|
+
if (memoryTurn?.sessionId === null && memoryTurn.origin === memoryOrigin(runtime)) {
|
|
1703
|
+
memoryTurn.sessionId = deps.session?.current()?.id ?? null;
|
|
1704
|
+
}
|
|
1705
|
+
},
|
|
1706
|
+
|
|
1151
1707
|
captureRuntimeContextSnapshot,
|
|
1152
1708
|
persistContextSnapshot,
|
|
1153
1709
|
liveContextEstimate,
|
|
1710
|
+
refreshLiveBudget,
|
|
1154
1711
|
rememberedLoadedContextWindow,
|
|
1155
1712
|
refreshAgentMessagesFromSession,
|
|
1156
1713
|
runAutoCompact,
|
|
1714
|
+
navigationRevision: () => navigationEpoch,
|
|
1157
1715
|
cancelCompaction: () => compactionController?.abort(),
|
|
1158
1716
|
|
|
1717
|
+
liveBudget: (): LiveBudgetView => budgetProducer.current() ?? refreshLiveBudget(),
|
|
1718
|
+
inspectLiveBudget(): BudgetInspection {
|
|
1719
|
+
const targetId = deps.getSettings().chat?.target;
|
|
1720
|
+
const target = !state.runtime && targetId ? deps.providers.getTarget(targetId) : null;
|
|
1721
|
+
const runtime =
|
|
1722
|
+
state.runtime?.runtimeResolution.runtime ?? (target ? deps.providers.getRuntime(target.runtime) : null);
|
|
1723
|
+
if (runtime && (!isOrchestratorEligibleRuntime(runtime) || runtime.externalAgentLoop))
|
|
1724
|
+
return {
|
|
1725
|
+
status: "unsupported",
|
|
1726
|
+
reason: "The external agent owns its context; native request accounting is unavailable.",
|
|
1727
|
+
};
|
|
1728
|
+
if (!runtime)
|
|
1729
|
+
return {
|
|
1730
|
+
status: "unavailable",
|
|
1731
|
+
reason: "No native runtime descriptor is resolved. Inspection does not initialize a runtime.",
|
|
1732
|
+
};
|
|
1733
|
+
return { status: "available", capability: "native", view: refreshLiveBudget() };
|
|
1734
|
+
},
|
|
1735
|
+
|
|
1159
1736
|
setCurrentSnapshot(snapshot: ContextSnapshot): void {
|
|
1160
1737
|
currentContextSnapshot = snapshot;
|
|
1738
|
+
refreshLiveBudget(snapshot.pendingUserInput);
|
|
1161
1739
|
},
|
|
1162
1740
|
|
|
1163
1741
|
flushReconciledSnapshot(): void {
|
|
1164
|
-
if (
|
|
1165
|
-
|
|
1742
|
+
if (snapshotPersistPending && currentContextSnapshot) persistContextSnapshot(currentContextSnapshot);
|
|
1743
|
+
// Run settlement. Its callers are `agent_end` and submit's finally, so
|
|
1744
|
+
// this is where the finished run's accounting is published, not the
|
|
1745
|
+
// mid-run tool-batch boundary; that one is `postToolContinuationGuard`.
|
|
1746
|
+
refreshLiveBudget();
|
|
1166
1747
|
},
|
|
1167
1748
|
|
|
1168
1749
|
reconcileUsage(usage: Usage): void {
|
|
@@ -1172,11 +1753,13 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
|
|
|
1172
1753
|
const runtime = state.runtime;
|
|
1173
1754
|
if (promptTokens > 0 && runtime) {
|
|
1174
1755
|
const breakdown = estimateAgentContextBreakdown({ ...runtime.agent.state, messages: [] });
|
|
1756
|
+
const anchoredMessages = [...runtime.agent.state.messages.filter((message) => message.role !== "system")];
|
|
1175
1757
|
// The output of this call is part of the next call's prompt for the
|
|
1176
1758
|
// same messages, which is why it is folded in here.
|
|
1177
1759
|
reconciledAnchor = {
|
|
1178
1760
|
tokens: promptTokens + (usage.output || 0),
|
|
1179
|
-
anchoredMessages
|
|
1761
|
+
anchoredMessages,
|
|
1762
|
+
contentDigest: messageListDigest(anchoredMessages),
|
|
1180
1763
|
runtime,
|
|
1181
1764
|
model: runtime.agent.state.model,
|
|
1182
1765
|
modelKey: anchorModelKey(runtime),
|
|
@@ -1187,7 +1770,12 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
|
|
|
1187
1770
|
toolSchemaTokens: breakdown.toolSchemaTokens,
|
|
1188
1771
|
};
|
|
1189
1772
|
}
|
|
1190
|
-
if (!currentContextSnapshot)
|
|
1773
|
+
if (!currentContextSnapshot) {
|
|
1774
|
+
// The anchor still moved, and it is what the next request budgets
|
|
1775
|
+
// against, so the view is republished with or without a snapshot.
|
|
1776
|
+
refreshLiveBudget();
|
|
1777
|
+
return;
|
|
1778
|
+
}
|
|
1191
1779
|
// Reconcile in memory on every API call so the live meters
|
|
1192
1780
|
// track usage; persistence waits for the run to settle.
|
|
1193
1781
|
if (runtime) {
|
|
@@ -1208,6 +1796,9 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
|
|
|
1208
1796
|
}
|
|
1209
1797
|
currentContextSnapshot = reconcileSnapshot(currentContextSnapshot, usage);
|
|
1210
1798
|
snapshotPersistPending = true;
|
|
1799
|
+
// A reconcile moves the anchor and therefore the figure the next
|
|
1800
|
+
// request budgets against, so it is a publication point of its own.
|
|
1801
|
+
refreshLiveBudget();
|
|
1211
1802
|
},
|
|
1212
1803
|
|
|
1213
1804
|
promptSideTokens(): number {
|
|
@@ -1271,7 +1862,20 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
|
|
|
1271
1862
|
};
|
|
1272
1863
|
if (deps.getMemorySection) {
|
|
1273
1864
|
try {
|
|
1274
|
-
|
|
1865
|
+
if (memoryTurn !== null && memoryTurn.sessionId !== (sessionId || null)) {
|
|
1866
|
+
memoryAuthorityEpoch += 1;
|
|
1867
|
+
memoryTurn = null;
|
|
1868
|
+
}
|
|
1869
|
+
const memorySection = deps.getMemorySection({
|
|
1870
|
+
turnId: memoryTurn?.id ?? null,
|
|
1871
|
+
sessionAuthority: memoryTurn?.sessionAuthority ?? JSON.stringify([memoryAuthorityEpoch, sessionId]),
|
|
1872
|
+
cwd,
|
|
1873
|
+
targetId: agentRuntime.targetId,
|
|
1874
|
+
runtimeId: agentRuntime.runtimeId,
|
|
1875
|
+
modelId: agentRuntime.wireModelId,
|
|
1876
|
+
taskText: memoryTurn?.taskText ?? "",
|
|
1877
|
+
activePaths: memoryTurn?.activePaths ?? [],
|
|
1878
|
+
});
|
|
1275
1879
|
if (memorySection.length > 0) sessionInputs.memorySection = memorySection;
|
|
1276
1880
|
} catch (err) {
|
|
1277
1881
|
deps.emitNotice(
|
|
@@ -1387,16 +1991,29 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
|
|
|
1387
1991
|
// ledger turn. Preserve its newly appended context tail if compaction
|
|
1388
1992
|
// rebuilds messages from the ledger, and count it in both estimates.
|
|
1389
1993
|
const reminder = contextChanged ? agentRuntime.agent.state.messages.at(-1) : undefined;
|
|
1994
|
+
// The tool batch appended results the last publication predates, and the
|
|
1995
|
+
// guard is about to decide on them. Publish before that inspection, not
|
|
1996
|
+
// after it.
|
|
1997
|
+
const beforeView = refreshLiveBudget();
|
|
1390
1998
|
const before = liveContextEstimate(agentRuntime);
|
|
1391
|
-
if (before.contextWindow <= 0
|
|
1999
|
+
if (before.contextWindow <= 0) throw new Error("Context window is unavailable; continuation refused.");
|
|
1392
2000
|
|
|
1393
2001
|
const settings = deps.getSettings();
|
|
1394
2002
|
const threshold = settings.context.compaction?.threshold ?? DEFAULT_COMPACTION_THRESHOLD;
|
|
1395
2003
|
const verdict = shouldCompact(before.tokens, threshold, before.contextWindow);
|
|
1396
2004
|
let compacted = false;
|
|
1397
|
-
|
|
2005
|
+
const mustFit = !requestFits(beforeView.inputTokens, beforeView.outputReserveTokens, beforeView.effectiveWindow);
|
|
2006
|
+
if (mustFit || verdict.shouldCompact) {
|
|
1398
2007
|
try {
|
|
1399
|
-
compacted = await runAutoCompact(
|
|
2008
|
+
compacted = await runAutoCompact(
|
|
2009
|
+
agentRuntime,
|
|
2010
|
+
mustFit,
|
|
2011
|
+
undefined,
|
|
2012
|
+
mustFit ? "overflow" : "auto",
|
|
2013
|
+
undefined,
|
|
2014
|
+
undefined,
|
|
2015
|
+
signal,
|
|
2016
|
+
);
|
|
1400
2017
|
} catch (err) {
|
|
1401
2018
|
throw new Error(
|
|
1402
2019
|
`[Clio Coder] post-tool context guard could not compact before continuation: ${err instanceof Error ? err.message : String(err)}`,
|
|
@@ -1407,32 +2024,28 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
|
|
|
1407
2024
|
if (reminder && !agentRuntime.agent.state.messages.includes(reminder)) {
|
|
1408
2025
|
agentRuntime.agent.state.messages.push(reminder);
|
|
1409
2026
|
}
|
|
1410
|
-
|
|
1411
|
-
|
|
2027
|
+
// The mid-run settled tool-batch boundary: whatever the guard did or
|
|
2028
|
+
// declined to do, this is the context the continuation will send.
|
|
2029
|
+
const afterView = refreshLiveBudget();
|
|
2030
|
+
if (!requestFits(afterView.inputTokens, afterView.outputReserveTokens, afterView.effectiveWindow)) {
|
|
1412
2031
|
throw new Error(
|
|
1413
|
-
`[Clio Coder] post-tool context guard stopped continuation before provider call: estimated ${
|
|
2032
|
+
`[Clio Coder] post-tool context guard stopped continuation before provider call: estimated input ${afterView.inputTokens} plus reserved output ${afterView.outputReserveTokens} tokens does not fit context window ${afterView.effectiveWindow}. Use /context compact, narrower reads, or a follow-up turn with smaller observations.`,
|
|
1414
2033
|
);
|
|
1415
2034
|
}
|
|
1416
2035
|
return compacted ? continuationContextUpdate(agentRuntime) : undefined;
|
|
1417
2036
|
},
|
|
1418
2037
|
|
|
1419
|
-
contextUsage():
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
const breakdown: ContextUsageBreakdown = {
|
|
1430
|
-
systemPromptTokens: currentContextSnapshot.categories.system,
|
|
1431
|
-
messageTokens: currentContextSnapshot.categories.messages + (currentContextSnapshot.categories.toolResults ?? 0),
|
|
1432
|
-
pendingUserTokens: pendingTokens,
|
|
1433
|
-
toolSchemaTokens: currentContextSnapshot.categories.tools,
|
|
2038
|
+
contextUsage(): LiveContextUsage {
|
|
2039
|
+
// Footer reads project the last publication. They never rescan the
|
|
2040
|
+
// ledger or add streaming output to the next-request message estimate.
|
|
2041
|
+
const view = budgetProducer.current() ?? refreshLiveBudget();
|
|
2042
|
+
return {
|
|
2043
|
+
...contextUsageSnapshot(view.inputTokens, view.effectiveWindow, view.breakdown ?? undefined),
|
|
2044
|
+
revision: view.revision,
|
|
2045
|
+
inputSource: view.inputSource,
|
|
2046
|
+
historical: view.historical,
|
|
2047
|
+
breakdownSource: view.breakdownSource,
|
|
1434
2048
|
};
|
|
1435
|
-
return contextUsageSnapshot(totalUsed > 0 ? totalUsed : null, effectiveWindow, breakdown);
|
|
1436
2049
|
},
|
|
1437
2050
|
|
|
1438
2051
|
contextLedger(): ContextLedger {
|
|
@@ -1449,7 +2062,7 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
|
|
|
1449
2062
|
contextWindowSource: state.runtime.runtimeResolution.contextWindowDetails.contextWindowSource,
|
|
1450
2063
|
contextWindowSlots: state.runtime.runtimeResolution.contextWindowDetails.contextWindowSlots,
|
|
1451
2064
|
}
|
|
1452
|
-
: windowWithoutRuntime();
|
|
2065
|
+
: windowWithoutRuntime(false);
|
|
1453
2066
|
const provider = state.runtime?.targetId ?? settings.chat?.target ?? null;
|
|
1454
2067
|
const model = state.runtime?.wireModelId ?? settings.chat?.model ?? null;
|
|
1455
2068
|
const liveToolCount = state.runtime?.agent.state.tools.length ?? 0;
|
|
@@ -1581,7 +2194,9 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
|
|
|
1581
2194
|
}
|
|
1582
2195
|
},
|
|
1583
2196
|
|
|
1584
|
-
resetForSession(): void {
|
|
2197
|
+
resetForSession(incomingBranchAnchorTurnId: string | null = null): void {
|
|
2198
|
+
memoryTurn = null;
|
|
2199
|
+
memoryAuthorityEpoch += 1;
|
|
1585
2200
|
compactionController?.abort();
|
|
1586
2201
|
// An in-process switch replaces the whole prefix: the backend's slot
|
|
1587
2202
|
// still holds the outgoing session's prompt and history, so the first
|
|
@@ -1602,7 +2217,15 @@ export function createTurnContext(deps: TurnContextDeps): TurnContext {
|
|
|
1602
2217
|
sessionWorkingContextPaths.clear();
|
|
1603
2218
|
pendingPromptLogEntry = null;
|
|
1604
2219
|
emptyAutoCompactContextKey = null;
|
|
2220
|
+
noUsefulCutBasisKey = null;
|
|
1605
2221
|
reconciledAnchor = null;
|
|
2222
|
+
// Real branch navigation. The epoch separates two visits to the same
|
|
2223
|
+
// leaf id, so an advisory announced on the branch this session left is
|
|
2224
|
+
// re-armed rather than treated as already said.
|
|
2225
|
+
branchAnchorTurnId = incomingBranchAnchorTurnId;
|
|
2226
|
+
navigationEpoch += 1;
|
|
2227
|
+
bindMemoryScope();
|
|
2228
|
+
budgetProducer.reset();
|
|
1606
2229
|
const session = deps.session?.current();
|
|
1607
2230
|
currentContextSnapshot = session ? getLatestContextSnapshot(session) : null;
|
|
1608
2231
|
},
|