@iowarp/clio-coder 0.5.1 → 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 +24 -0
- package/CONTRIBUTING.md +9 -0
- package/README.md +3 -3
- package/ROADMAP.md +30 -5
- package/dist/{acp-X3N7JDGS.js → acp-F6PGOFZ4.js} +2 -2
- package/dist/{agents-UYGSMAWR.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-DGMY2DWE.js → chunk-23UIXH6Z.js} +3 -3
- package/dist/{chunk-QTYRMBW6.js → chunk-36XVK5P5.js} +5 -5
- package/dist/{chunk-BIQLF3NZ.js → chunk-3DZ4LPLS.js} +6 -6
- package/dist/{chunk-7VNLPEUG.js → chunk-3GSPCWFX.js} +12 -12
- package/dist/{chunk-CRHPCPZP.js → chunk-4KC4CHKJ.js} +2 -2
- package/dist/{chunk-M5FPIZQW.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-A7G7OUND.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-GW2KVAVI.js → chunk-AA3GRZWS.js} +2 -2
- package/dist/{chunk-6B76U65H.js → chunk-ADO3OQIC.js} +3 -3
- 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-AKQ3N3L4.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-7KI4ZSEB.js → chunk-ELDJWJKM.js} +97 -14
- package/dist/{chunk-GCQFN7YI.js → chunk-EMYEQLWO.js} +2 -2
- package/dist/{chunk-7FHSA7VB.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-GGEEG7CD.js → chunk-FOSRJ2VZ.js} +4 -4
- package/dist/{chunk-ZARZ4POR.js → chunk-G5ZDQQE3.js} +3 -3
- package/dist/{chunk-QM7HFPQQ.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-WAISUJNQ.js → chunk-KWMFC7FN.js} +3 -3
- package/dist/{chunk-4D7QM7S5.js → chunk-LXYHCCIZ.js} +9 -9
- package/dist/{chunk-NFSK2VNU.js → chunk-LZMTVVN4.js} +2 -2
- package/dist/{chunk-ZNTGRDFA.js → chunk-M5PMRZSR.js} +123 -37
- 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-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-VWKR2NN5.js → chunk-PF36KOGI.js} +290 -111
- package/dist/{chunk-LFYA3HYH.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-VMTCZ2GR.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-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-OXWRE2QJ.js → chunk-VKMSEO7Y.js} +4 -4
- package/dist/{chunk-FBXTHPNZ.js → chunk-VLX5VZ35.js} +5 -5
- package/dist/{chunk-KIAUZGKF.js → chunk-WAGBMMNX.js} +5 -5
- package/dist/{chunk-BFOSV5EZ.js → chunk-WX2YCH7F.js} +2 -2
- package/dist/{chunk-SAL2SBP4.js → chunk-XBIGUILU.js} +25 -10
- package/dist/{chunk-XOH3SD7H.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 +25 -25
- package/dist/{clio-Z3JTEL2I.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-QYIRZHI7.js → config-343YS25T.js} +42 -40
- package/dist/{config-graph-TS3SGGQW.js → config-graph-WI263KDX.js} +42 -40
- package/dist/{configure-OX7KVJTQ.js → configure-GZDG4HER.js} +21 -21
- package/dist/{context-2BNF6HTP.js → context-4OFO7N7Y.js} +12 -12
- package/dist/{context-GTMUGAR4.js → context-JLK2RLOG.js} +41 -39
- package/dist/{context-NNLBES2Y.js → context-O6DIWCF6.js} +23 -21
- package/dist/{context-clear-FVJQBBL7.js → context-clear-MR7KOGQI.js} +41 -39
- package/dist/{context-working-set-MOE72SD6.js → context-working-set-YZAD3KWO.js} +14 -13
- package/dist/{data-tool-JJPBGMQJ.js → data-tool-EKD7DFHG.js} +7 -7
- package/dist/{detail-QZM3QAAU.js → detail-YZPHFEVJ.js} +42 -40
- package/dist/{dispatch-runner-2KSQO5FU.js → dispatch-runner-VKVXVTWJ.js} +43 -41
- package/dist/{doctor-2U6YR5NH.js → doctor-53AVQXAO.js} +16 -15
- package/dist/{doctor-deep-2EJQSHIZ.js → doctor-deep-BLRB2NCG.js} +5 -5
- package/dist/{eval-ZKK77JWZ.js → eval-P3BQSLQE.js} +21 -20
- package/dist/{evidence-POSEQW4Q.js → evidence-6LLI6SZH.js} +43 -41
- package/dist/{evidence-FMP7KZZ2.js → evidence-HTN3JLUO.js} +41 -39
- package/dist/{evidence-PJTOE7JA.js → evidence-NJRE3R5H.js} +41 -39
- package/dist/{evolve-JT6PAMZE.js → evolve-WWZHJVJP.js} +41 -39
- package/dist/{fleet-4XR2ZEOF.js → fleet-GGZ4BVJE.js} +63 -61
- package/dist/{fleet-6JJKNIMV.js → fleet-QG3IIWWM.js} +43 -41
- 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-SWJMFVDP.js → fleet-inspect-KOLKBC56.js} +43 -41
- package/dist/{fleet-preflight-KSIHWQAU.js → fleet-preflight-44UNCC4Y.js} +26 -24
- package/dist/{fleet-validate-7NLAE26R.js → fleet-validate-K2FXRZVA.js} +13 -13
- package/dist/{fleet-verify-QM6XU2KL.js → fleet-verify-AS444WDW.js} +41 -39
- package/dist/{fleet-view-RLKFHFCM.js → fleet-view-X4JU4J6O.js} +42 -40
- package/dist/gui/ops-worker.js +9 -9
- package/dist/gui/reads-worker.js +9 -8
- package/dist/{init-VBUPHABR.js → init-VSOWFHVL.js} +53 -51
- package/dist/{interactive-JCVBPVYT.js → interactive-JRCBENLZ.js} +154 -92
- package/dist/{interop-J7UOBOSF.js → interop-JR6BCY7R.js} +10 -10
- package/dist/{inventory-54G7FSVY.js → inventory-3JUGLVJI.js} +42 -40
- 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/{memory-5BPQSSOP.js → memory-Q2YVOKK5.js} +41 -39
- package/dist/{models-R6HILX6X.js → models-M7BOCW7F.js} +17 -16
- package/dist/{monitor-HVRKSFEQ.js → monitor-2HD3MC67.js} +46 -44
- package/dist/{orchestrator-OR7V4YWK.js → orchestrator-OUW3ZN6Y.js} +1890 -222
- package/dist/{panes-A743ONZ5.js → panes-TTPPSYCL.js} +3 -3
- package/dist/{preload-KLXFOX2F.js → preload-A47C2NUF.js} +41 -39
- package/dist/{providers-CMETPZNS.js → providers-T43W7EL4.js} +4 -4
- package/dist/{resources-FY6XYFN2.js → resources-TP6X2V6W.js} +20 -11
- package/dist/{run-UIDRUYAY.js → run-LDSBPMCS.js} +60 -58
- package/dist/{share-CAFMMQLM.js → share-URHKKMHR.js} +9 -9
- package/dist/{skills-ERKQUC7L.js → skills-E477MOOO.js} +13 -11
- package/dist/{skills-eval-JDAXOPWC.js → skills-eval-AIBR2L7H.js} +582 -92
- package/dist/{skills-inventory-TVUYJDVN.js → skills-inventory-HLMKY3R5.js} +13 -11
- package/dist/{slash-commands-MNKQ5QP3.js → slash-commands-LIIZKA5Y.js} +26 -24
- package/dist/{startup-background-PJMWTKVO.js → startup-background-JR2KDK6R.js} +42 -40
- package/dist/{steer-KKYXJSH4.js → steer-D7RTAPCM.js} +3 -3
- package/dist/{system-IVVZRKGI.js → system-X2P5N4IY.js} +14 -13
- package/dist/{targets-RFRUMCQ5.js → targets-VFFAKUFM.js} +25 -24
- package/dist/{tasks-6L2JJRC5.js → tasks-ERAWZA22.js} +6 -6
- package/dist/{trace-LA5MVR4Y.js → trace-MA5BT7XY.js} +3 -3
- package/dist/{usage-LA33SG3R.js → usage-WYFY2L6M.js} +49 -47
- 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-HL73TICA.js → wiki-generate-OFKOX2T6.js} +52 -50
- package/dist/worker/entry.js +30 -28
- package/docs/architecture/context-engine.md +2 -0
- package/docs/guide/context-continuity.md +49 -0
- package/docs/guide/proactive-memory.md +3 -0
- 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/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/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/prompts/compiler.ts +1 -1
- package/src/domains/prompts/extension.ts +38 -1
- 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/engine/agent.ts +133 -9
- package/src/engine/session.ts +13 -6
- package/src/entry/orchestrator.ts +114 -31
- package/src/interactive/chat-loop.ts +216 -60
- 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/footer/dashboard.ts +12 -1
- package/src/interactive/footer/pages.ts +15 -7
- package/src/interactive/footer/widgets.ts +32 -7
- package/src/interactive/interactive-application.ts +2 -0
- package/src/interactive/interactive-slash-runtime.ts +43 -6
- package/src/interactive/model-session-replay.ts +130 -3
- package/src/interactive/output-reserve.ts +35 -0
- package/src/interactive/overlay-session-lifecycle.ts +18 -11
- package/src/interactive/session-transcript.ts +2 -1
- package/src/interactive/slash-commands.ts +20 -1
- package/src/interactive/turn-context.ts +685 -62
- package/src/interactive/turn-middleware.ts +39 -6
- package/src/interactive/turn-persistence.ts +15 -2
- package/src/interactive/turn-runtime.ts +48 -5
- 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-3OODBJ45.js +0 -1325
- package/dist/chunk-CTFPFW3H.js +0 -44
- package/dist/chunk-I4Y4WKZR.js +0 -386
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { OutputStyle } from "../../core/defaults.js";
|
|
2
2
|
import { ToolNames } from "../../core/tool-names.js";
|
|
3
|
+
import type { LiveBudgetView } from "../../domains/context/budget/live-view.js";
|
|
3
4
|
import {
|
|
4
5
|
type CostAggregate,
|
|
5
6
|
formatCostAggregate,
|
|
@@ -99,6 +100,8 @@ export interface SessionFacts {
|
|
|
99
100
|
|
|
100
101
|
/** Context engine telemetry. */
|
|
101
102
|
export interface ContextEngineFacts {
|
|
103
|
+
/** Identifies the published accounting used by the meter; ledger stays diagnostic. */
|
|
104
|
+
budget?: Pick<LiveBudgetView, "revision" | "historical" | "inputSource">;
|
|
102
105
|
label: string | null;
|
|
103
106
|
used: number | null;
|
|
104
107
|
contextWindow: number | null;
|
|
@@ -393,14 +396,36 @@ function contextComposition(context: ContextEngineFacts): {
|
|
|
393
396
|
system: finiteNonNegative(breakdown?.systemPromptTokens),
|
|
394
397
|
tools: finiteNonNegative(breakdown?.toolSchemaTokens),
|
|
395
398
|
chat: finiteNonNegative(breakdown?.messageTokens) + finiteNonNegative(breakdown?.pendingUserTokens),
|
|
396
|
-
free:
|
|
399
|
+
free:
|
|
400
|
+
window > 0 && typeof context.used === "number" && Number.isFinite(context.used) ? Math.max(0, window - used) : null,
|
|
397
401
|
};
|
|
398
402
|
}
|
|
399
403
|
|
|
404
|
+
/** Short truthful occupancy label shared by compact and expanded surfaces. */
|
|
405
|
+
export function contextUsageText(context: ContextEngineFacts): string {
|
|
406
|
+
const used = context.budget ? context.used : (context.ledger?.usedTokens ?? context.used);
|
|
407
|
+
const window = context.budget ? context.contextWindow : (context.ledger?.contextWindow ?? context.contextWindow);
|
|
408
|
+
const source = context.budget?.inputSource === "historical" ? "saved " : context.budget && used !== null ? "~" : "";
|
|
409
|
+
return `${source}${used === null ? "?" : formatFooterTokens(used)} / ${window ? formatFooterTokens(window) : "?"}`;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/** Reads published numbers only; never refreshes accounting from a renderer. */
|
|
413
|
+
export function contextOccupancyBar(context: ContextEngineFacts, cells: number, theme: ClioTheme): string {
|
|
414
|
+
if (!context.budget && context.ledger) return renderContextMeterBar(context.ledger, cells, theme);
|
|
415
|
+
return buildSegmentedContextBar(
|
|
416
|
+
theme,
|
|
417
|
+
cells,
|
|
418
|
+
context.contextWindow ?? 0,
|
|
419
|
+
context.used === null ? undefined : contextBreakdownForBar(context),
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
|
|
400
423
|
/** The live window after the percent; null when unknown or the row is too narrow to carry it. */
|
|
401
424
|
export function compactContextWindowLabel(context: ContextEngineFacts, width: number): string | null {
|
|
402
425
|
if (width < 72) return null;
|
|
403
|
-
const window = finiteNonNegative(
|
|
426
|
+
const window = finiteNonNegative(
|
|
427
|
+
context.budget ? context.contextWindow : (context.ledger?.contextWindow ?? context.contextWindow),
|
|
428
|
+
);
|
|
404
429
|
return window > 0 ? formatFooterTokens(window) : null;
|
|
405
430
|
}
|
|
406
431
|
|
|
@@ -427,7 +452,7 @@ export function compactSecondaryLine(
|
|
|
427
452
|
const safeWidth = Math.max(1, Math.floor(width));
|
|
428
453
|
const barCells = compactContextBarWidth(safeWidth);
|
|
429
454
|
let left = "";
|
|
430
|
-
if (context.ledger) {
|
|
455
|
+
if (context.ledger && !context.budget) {
|
|
431
456
|
// The row follows the kv grammar: dim key, muted value. An unmeasured
|
|
432
457
|
// percent renders the ?% placeholder dim because it is scaffolding for a
|
|
433
458
|
// number that has not arrived, not a measurement.
|
|
@@ -437,7 +462,7 @@ export function compactSecondaryLine(
|
|
|
437
462
|
);
|
|
438
463
|
left = `${theme.fg("dim", "ctx")} ${renderContextMeterBar(context.ledger, barCells, theme)} ${percent}`;
|
|
439
464
|
} else {
|
|
440
|
-
left =
|
|
465
|
+
left = `${context.budget?.inputSource === "historical" ? theme.fg("dim", "saved ") : ""}${contextOccupancyBar(context, barCells, theme)}`;
|
|
441
466
|
}
|
|
442
467
|
// A percentage alone does not say whether the window is 128k or 1M, so at
|
|
443
468
|
// widths that can afford it the live window follows the percent. It comes
|
|
@@ -713,7 +738,7 @@ function ledgerBar(theme: ClioTheme, ledger: ContextLedger, cells: number): stri
|
|
|
713
738
|
export function contextQuadrant(facts: ContextEngineFacts, options: ExpandedQuadrantOptions = {}): string[] {
|
|
714
739
|
const theme = clioTheme();
|
|
715
740
|
const ledger = facts.ledger ?? null;
|
|
716
|
-
const hasLedger = ledger !== null && ledger.contextWindow > 0;
|
|
741
|
+
const hasLedger = !facts.budget && ledger !== null && ledger.contextWindow > 0;
|
|
717
742
|
const barCells = expandedContextBarCells(options.width);
|
|
718
743
|
|
|
719
744
|
let bar: string;
|
|
@@ -737,7 +762,7 @@ export function contextQuadrant(facts: ContextEngineFacts, options: ExpandedQuad
|
|
|
737
762
|
? theme.style("frame", `free ${formatFooterTokens(composition.free)}`, { dim: true })
|
|
738
763
|
: null,
|
|
739
764
|
]);
|
|
740
|
-
bar =
|
|
765
|
+
bar = contextOccupancyBar(facts, barCells, theme);
|
|
741
766
|
const filledChar = visibleWidth(GLYPH.contextFull) === 1 ? GLYPH.contextFull : GLYPH.barFull;
|
|
742
767
|
const freeChar = visibleWidth(GLYPH.contextFree) === 1 ? GLYPH.contextFree : GLYPH.barEmpty;
|
|
743
768
|
legendRows = [
|
|
@@ -752,7 +777,7 @@ export function contextQuadrant(facts: ContextEngineFacts, options: ExpandedQuad
|
|
|
752
777
|
"Context",
|
|
753
778
|
[
|
|
754
779
|
statusRow(bar),
|
|
755
|
-
kv("used", formatUsedWindow(usedTokens, windowTokens)),
|
|
780
|
+
kv("used", facts.budget ? contextUsageText(facts) : formatUsedWindow(usedTokens, windowTokens)),
|
|
756
781
|
fill ? styledKv("fill", fill) : statusRow(null),
|
|
757
782
|
chatFree ? styledKv("budget", chatFree) : statusRow(null),
|
|
758
783
|
kv("compact", formatCompaction(facts)),
|
|
@@ -252,6 +252,7 @@ export interface InteractiveDeps {
|
|
|
252
252
|
* engine, and persists a compactionSummary entry.
|
|
253
253
|
*/
|
|
254
254
|
onCompact?: (instructions: string | undefined) => Promise<void>;
|
|
255
|
+
onRecoverHandoff?: (handoffId: string, action: "reduce" | "deliver") => Promise<void>;
|
|
255
256
|
/** Run /context init for the current working directory. */
|
|
256
257
|
onInit?: (options: InitCommandOptions, io?: RunIo) => Promise<void>;
|
|
257
258
|
/** Run /context reset for the current working directory. */
|
|
@@ -827,6 +828,7 @@ export async function createInteractiveApplication(deps: InteractiveDeps): Promi
|
|
|
827
828
|
...(deps.onSelectModel ? { onSelectModel: deps.onSelectModel } : {}),
|
|
828
829
|
...(deps.onSetThinkingLevel ? { onSetThinkingLevel: deps.onSetThinkingLevel } : {}),
|
|
829
830
|
...(deps.onCompact ? { onCompact: deps.onCompact } : {}),
|
|
831
|
+
...(deps.onRecoverHandoff ? { onRecoverHandoff: deps.onRecoverHandoff } : {}),
|
|
830
832
|
...(deps.onInit ? { onInit: deps.onInit } : {}),
|
|
831
833
|
...(deps.onContextClear ? { onContextClear: deps.onContextClear } : {}),
|
|
832
834
|
...(deps.onContextRefresh ? { onContextRefresh: deps.onContextRefresh } : {}),
|
|
@@ -36,6 +36,7 @@ import { appendNotice, appendOperatorAside, appendOperatorCommand, appendReferen
|
|
|
36
36
|
import { runOperatorRecall } from "./context-recall-command.js";
|
|
37
37
|
import { renderSessionHtml } from "./export-html/index.js";
|
|
38
38
|
import { dateLocal } from "./format-time.js";
|
|
39
|
+
import { withContinuityReplay } from "./model-session-replay.js";
|
|
39
40
|
import type { OracleDigestSources } from "./oracle.js";
|
|
40
41
|
import type { PendingModelScope } from "./overlays/model-scope.js";
|
|
41
42
|
import { renderDoctorReport } from "./renderers/doctor-report.js";
|
|
@@ -57,6 +58,15 @@ import type { WorkerEntryState } from "./worker-stream.js";
|
|
|
57
58
|
|
|
58
59
|
const EXPORT_RENDER_WIDTH = 100;
|
|
59
60
|
|
|
61
|
+
/** Longest run of consecutive backticks in the rendered transcript, for fence sizing. */
|
|
62
|
+
function longestBacktickRun(lines: ReadonlyArray<string>): number {
|
|
63
|
+
let longest = 0;
|
|
64
|
+
for (const line of lines) {
|
|
65
|
+
for (const run of line.match(/`+/gu) ?? []) longest = Math.max(longest, run.length);
|
|
66
|
+
}
|
|
67
|
+
return longest;
|
|
68
|
+
}
|
|
69
|
+
|
|
60
70
|
export interface InteractiveSlashSubmitExpansion {
|
|
61
71
|
text: string;
|
|
62
72
|
images: ImageContent[];
|
|
@@ -104,6 +114,7 @@ export interface InteractiveSlashRuntimeDeps {
|
|
|
104
114
|
onSelectModel?: (ref: { target: string; model: string }, scope: "session" | "global") => void;
|
|
105
115
|
onSetThinkingLevel?: (level: ThinkingLevel, scope?: "session" | "global") => void;
|
|
106
116
|
onCompact?: (instructions: string | undefined) => Promise<void>;
|
|
117
|
+
onRecoverHandoff?: (handoffId: string, action: "reduce" | "deliver") => Promise<void>;
|
|
107
118
|
onInit?: (options: InitCommandOptions, io?: RunIo) => Promise<void>;
|
|
108
119
|
onContextClear?: (options: ContextClearCommandOptions, io?: RunIo) => Promise<void>;
|
|
109
120
|
onContextRefresh?: (io?: RunIo) => Promise<void>;
|
|
@@ -584,6 +595,17 @@ export function createInteractiveSlashRuntime(deps: InteractiveSlashRuntimeDeps)
|
|
|
584
595
|
deps.setEditorText(text);
|
|
585
596
|
deps.requestRender();
|
|
586
597
|
},
|
|
598
|
+
runHandoffRecovery: (handoffId, action) => {
|
|
599
|
+
if (!deps.onRecoverHandoff) {
|
|
600
|
+
appendCommandNotice("error", "[/context recover] native handoff recovery is unavailable");
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
void deps
|
|
604
|
+
.onRecoverHandoff(handoffId, action)
|
|
605
|
+
.catch((error) =>
|
|
606
|
+
appendCommandNotice("error", `[/context recover] ${error instanceof Error ? error.message : String(error)}`),
|
|
607
|
+
);
|
|
608
|
+
},
|
|
587
609
|
runCompact: (instructions) => {
|
|
588
610
|
runCompactWithNotice(deps.onCompact, appendCommandNotice, instructions);
|
|
589
611
|
},
|
|
@@ -608,10 +630,19 @@ export function createInteractiveSlashRuntime(deps: InteractiveSlashRuntimeDeps)
|
|
|
608
630
|
// rehydrate exported it as ordinary history (issue #109). No session
|
|
609
631
|
// contract means no pin can exist for this reader, so the file replays whole.
|
|
610
632
|
const leafTurnId = deps.session?.tree(sessionId).leafId ?? null;
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
633
|
+
// The export shows the same continuity the model sees, resolved from
|
|
634
|
+
// the same ledger through the same helper, so the two cannot drift.
|
|
635
|
+
// It is labelled as assistant handoff text and is never rendered as an
|
|
636
|
+
// operator turn.
|
|
637
|
+
rehydrateChatPanelFromTurns(
|
|
638
|
+
exportPanel,
|
|
639
|
+
turns,
|
|
640
|
+
withContinuityReplay(
|
|
641
|
+
turns,
|
|
642
|
+
{ unboundedToolBodies: true, ...(leafTurnId ? { activeLeafTurnId: leafTurnId } : {}) },
|
|
643
|
+
deps.session,
|
|
644
|
+
),
|
|
645
|
+
);
|
|
615
646
|
const ansiLines = exportPanel.render(EXPORT_RENDER_WIDTH);
|
|
616
647
|
// The operator names this file by the day they ran the export, so the
|
|
617
648
|
// date in it is their calendar date. The header below keeps the ISO
|
|
@@ -626,8 +657,14 @@ export function createInteractiveSlashRuntime(deps: InteractiveSlashRuntimeDeps)
|
|
|
626
657
|
mkdirSync(resolve(target, ".."), { recursive: true });
|
|
627
658
|
if (markdown) {
|
|
628
659
|
const lines = ansiLines.map(stripTerminalSequences);
|
|
629
|
-
|
|
630
|
-
|
|
660
|
+
// The fence has to be longer than the longest backtick run the
|
|
661
|
+
// transcript contains, or a body that includes its own fence closes
|
|
662
|
+
// the block early and the rest of the session renders as Markdown.
|
|
663
|
+
// A handoff note is assistant-authored prose that routinely carries
|
|
664
|
+
// fenced code, so this is reachable content, not a hypothetical.
|
|
665
|
+
const fence = "`".repeat(Math.max(3, longestBacktickRun(lines) + 1));
|
|
666
|
+
const header = [`# Clio session ${sessionId}`, "", `Exported ${exportedAt.toISOString()}`, "", `${fence}text`];
|
|
667
|
+
writeFileSync(target, `${[...header, ...lines, fence, ""].join("\n")}`, "utf8");
|
|
631
668
|
} else {
|
|
632
669
|
writeFileSync(target, renderSessionHtml({ sessionId, exportedAt: exportedAt.toISOString(), ansiLines }), "utf8");
|
|
633
670
|
}
|
|
@@ -1,8 +1,123 @@
|
|
|
1
1
|
import { foldWorkingSet } from "../domains/context/working-set/fold.js";
|
|
2
2
|
import { projectWorkingSet } from "../domains/context/working-set/project.js";
|
|
3
|
+
import {
|
|
4
|
+
type ContinuityForkBinding,
|
|
5
|
+
type ContinuityProjection,
|
|
6
|
+
continuityReplayBlocks,
|
|
7
|
+
resolveContinuityProjection,
|
|
8
|
+
} from "../domains/session/continuity/projection.js";
|
|
9
|
+
import type { SessionContract } from "../domains/session/contract.js";
|
|
3
10
|
import type { SessionEntry } from "../domains/session/entries.js";
|
|
4
11
|
import type { AgentMessage } from "../engine/types.js";
|
|
5
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
activeEntriesBeforeCompactionCut,
|
|
14
|
+
buildReplayAgentMessagesFromTurns,
|
|
15
|
+
type RehydrateChatPanelOptions,
|
|
16
|
+
} from "./chat-renderer.js";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The session-ownership facts continuity needs, which a renderer does not have.
|
|
20
|
+
*
|
|
21
|
+
* Supplying this is what separates a fork's own transactions from the ones it
|
|
22
|
+
* inherited. Without it a reader owns nothing: inherited recall still projects,
|
|
23
|
+
* because losing the note is the one outcome that is never safe, but no
|
|
24
|
+
* transaction can reach execution authority.
|
|
25
|
+
*/
|
|
26
|
+
export interface ContinuityReplayContext {
|
|
27
|
+
/** The opened session's actual id, from `session.current()`. */
|
|
28
|
+
sessionId?: string;
|
|
29
|
+
/** `meta.parentSessionId` / `meta.parentTurnId`, when this session is a fork. */
|
|
30
|
+
fork?: ContinuityForkBinding;
|
|
31
|
+
/**
|
|
32
|
+
* True only for a genuine historical cut of this session's own transactions.
|
|
33
|
+
* A live `/tree` selection is not one, even though the renderer passes
|
|
34
|
+
* `uptoTurnId` for both.
|
|
35
|
+
*/
|
|
36
|
+
historical?: boolean;
|
|
37
|
+
/** Parse failures the reader counted, so a torn record cannot become permission. */
|
|
38
|
+
unreadableRecords?: number;
|
|
39
|
+
nowMs?: number;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ModelReplayOptions extends RehydrateChatPanelOptions {
|
|
43
|
+
continuity?: ContinuityReplayContext;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Derive replay ownership from the open session.
|
|
48
|
+
*
|
|
49
|
+
* `meta.parentSessionId` and `meta.parentTurnId` are what `enrichForkMeta`
|
|
50
|
+
* stamps on a child, and together they are the fork binding. The header's
|
|
51
|
+
* `parentSession` is a path to the parent's ledger, a different fact, and is
|
|
52
|
+
* deliberately not used as a session id. A session with no fork pointers
|
|
53
|
+
* publishes no binding, so a foreign-origin record in a session that never
|
|
54
|
+
* forked stays unprojected instead of being read as inheritance.
|
|
55
|
+
*
|
|
56
|
+
* `historical` is never set here. A replay is historical only when a caller
|
|
57
|
+
* standing at a past turn says so; deriving it from the renderer's `uptoTurnId`
|
|
58
|
+
* would make an ordinary `/tree` selection permanently surrender this session's
|
|
59
|
+
* ownership of its own transactions.
|
|
60
|
+
*/
|
|
61
|
+
export function continuityContextFromSession(
|
|
62
|
+
/** Narrowed to the one method this needs, so partial session owners qualify. */
|
|
63
|
+
session: Pick<SessionContract, "current"> | undefined,
|
|
64
|
+
): ContinuityReplayContext {
|
|
65
|
+
const meta = session?.current();
|
|
66
|
+
if (!meta) return {};
|
|
67
|
+
const parentSessionId = meta.parentSessionId ?? null;
|
|
68
|
+
const parentTurnId = meta.parentTurnId ?? null;
|
|
69
|
+
return {
|
|
70
|
+
sessionId: meta.id,
|
|
71
|
+
...(parentSessionId !== null && parentTurnId !== null ? { fork: { parentSessionId, parentTurnId } } : {}),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Resolve the continuity projection for a replay.
|
|
77
|
+
*
|
|
78
|
+
* Folded over the active path **before** the compaction cut and before the
|
|
79
|
+
* working-set projection, because both of those remove records the fold needs:
|
|
80
|
+
* a commit, a pause or an operator control request older than the cut is still
|
|
81
|
+
* the evidence for the state the note describes. `withContinuityReplay` is the
|
|
82
|
+
* exported entry point, so the transcript and the model always render blocks
|
|
83
|
+
* from one resolution rather than two.
|
|
84
|
+
*/
|
|
85
|
+
function resolveReplayContinuity(
|
|
86
|
+
entries: ReadonlyArray<SessionEntry>,
|
|
87
|
+
options: ModelReplayOptions = {},
|
|
88
|
+
): ContinuityProjection {
|
|
89
|
+
const context = options.continuity ?? {};
|
|
90
|
+
return resolveContinuityProjection({
|
|
91
|
+
// The positional cut applies only to a real historical selection. A live
|
|
92
|
+
// /tree switch passes `uptoTurnId` for display and must still see the
|
|
93
|
+
// sidecars anchored after the selected message, because they are part of
|
|
94
|
+
// its own branch's current state.
|
|
95
|
+
entries: activeEntriesBeforeCompactionCut(entries, options, context.historical === true),
|
|
96
|
+
...(context.sessionId === undefined ? {} : { sessionId: context.sessionId }),
|
|
97
|
+
...(context.fork === undefined ? {} : { fork: context.fork }),
|
|
98
|
+
...(context.historical === undefined ? {} : { historical: context.historical }),
|
|
99
|
+
...(context.unreadableRecords === undefined ? {} : { unreadableRecords: context.unreadableRecords }),
|
|
100
|
+
...(context.nowMs === undefined ? {} : { nowMs: context.nowMs }),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Resolve continuity once and hand the same finished blocks to both surfaces.
|
|
106
|
+
*
|
|
107
|
+
* The transcript and the model must show byte-identical note text, so the
|
|
108
|
+
* projection is resolved here and the rendered blocks travel on the options
|
|
109
|
+
* object that `rehydrateChatPanelFromTurns` and
|
|
110
|
+
* `buildModelReplayAgentMessagesFromTurns` both receive. Resolving it twice
|
|
111
|
+
* would be two chances to disagree.
|
|
112
|
+
*/
|
|
113
|
+
export function withContinuityReplay(
|
|
114
|
+
turns: ReadonlyArray<SessionEntry>,
|
|
115
|
+
options: RehydrateChatPanelOptions,
|
|
116
|
+
session: Pick<SessionContract, "current"> | undefined,
|
|
117
|
+
): ModelReplayOptions {
|
|
118
|
+
const merged: ModelReplayOptions = { ...options, continuity: continuityContextFromSession(session) };
|
|
119
|
+
return { ...merged, continuityBlocks: continuityReplayBlocks(resolveReplayContinuity(turns, merged)) };
|
|
120
|
+
}
|
|
6
121
|
|
|
7
122
|
/**
|
|
8
123
|
* Build provider-facing replay messages from the durable session ledger.
|
|
@@ -10,12 +125,24 @@ import { buildReplayAgentMessagesFromTurns, type RehydrateChatPanelOptions } fro
|
|
|
10
125
|
* setting gates creation of new evictions, not replay of durable state.
|
|
11
126
|
* Visible transcript and export callers intentionally keep using the raw
|
|
12
127
|
* rehydration helpers so eviction remains a model projection, not data loss.
|
|
128
|
+
*
|
|
129
|
+
* Continuity is resolved first, from the complete pre-cut ledger, so the
|
|
130
|
+
* accepted note survives both the working-set projection and the compaction
|
|
131
|
+
* cut. It is carried as labelled data and never becomes a synthetic operator
|
|
132
|
+
* instruction.
|
|
13
133
|
*/
|
|
14
134
|
export function buildModelReplayAgentMessagesFromTurns(
|
|
15
135
|
entries: ReadonlyArray<SessionEntry>,
|
|
16
|
-
options:
|
|
136
|
+
options: ModelReplayOptions = {},
|
|
17
137
|
): AgentMessage[] {
|
|
18
138
|
const activeLeafTurnId = options.activeLeafTurnId ?? options.uptoTurnId;
|
|
139
|
+
// A caller that already resolved the projection passes its blocks through,
|
|
140
|
+
// so the transcript beside this replay renders the identical bytes.
|
|
141
|
+
const blocks = options.continuityBlocks ?? continuityReplayBlocks(resolveReplayContinuity(entries, options));
|
|
19
142
|
const projected = projectWorkingSet(entries, foldWorkingSet(entries, activeLeafTurnId), activeLeafTurnId);
|
|
20
|
-
return buildReplayAgentMessagesFromTurns(projected, {
|
|
143
|
+
return buildReplayAgentMessagesFromTurns(projected, {
|
|
144
|
+
...options,
|
|
145
|
+
skillContextEntries: entries,
|
|
146
|
+
continuityBlocks: blocks,
|
|
147
|
+
});
|
|
21
148
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one preflight output reservation a turn asks for.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from the submit guard in `chat-loop.ts` so the guard and the live
|
|
5
|
+
* budget view quote the same number instead of growing a second formula. It
|
|
6
|
+
* resolves through `resolveReservedOutputTokens`, which applies the configured
|
|
7
|
+
* output budget, the model's advertised cap, and the remaining-context clamp
|
|
8
|
+
* the `openai-completions` and `ollama-native` transports already perform on
|
|
9
|
+
* the wire. The capability-resolved cap is the input, because that is what the
|
|
10
|
+
* runtime resolution decided this target may actually be asked for.
|
|
11
|
+
*
|
|
12
|
+
* This is a preflight reservation, not a guarantee of the final wire ceiling:
|
|
13
|
+
* `remainingContextMaxTokens` re-derives the ceiling at request time against
|
|
14
|
+
* the context the transport is about to send.
|
|
15
|
+
*
|
|
16
|
+
* It is not the compaction-threshold reserve. `ContextSnapshot.categories.reserve`
|
|
17
|
+
* is window headroom held back so auto-compaction fires before a request is
|
|
18
|
+
* refused, and the two figures are unrelated.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { resolveReservedOutputTokens } from "../engine/apis/output-budget.js";
|
|
22
|
+
import type { AgentRuntime } from "./turn-state.js";
|
|
23
|
+
|
|
24
|
+
export function resolveTurnOutputReserve(agentRuntime: AgentRuntime, inputTokens: number): number {
|
|
25
|
+
// A runtime resolution always carries capability decisions in production. The
|
|
26
|
+
// optional read is for a partially built runtime: an absent cap is the
|
|
27
|
+
// resolver's own "no advertised limit" case, which falls back to the
|
|
28
|
+
// configured budget and then the product floor, rather than a crash on a path
|
|
29
|
+
// that now runs during ordinary accounting.
|
|
30
|
+
return resolveReservedOutputTokens(agentRuntime.runtimeResolution.capabilityDecisions?.maxTokens, {
|
|
31
|
+
api: agentRuntime.agent.state.model?.api ?? "",
|
|
32
|
+
contextWindow: agentRuntime.runtimeResolution.contextWindowDetails.effectiveContextWindow,
|
|
33
|
+
inputTokens,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
@@ -20,7 +20,7 @@ import type { ChatPanel } from "./chat-panel.js";
|
|
|
20
20
|
import { rehydrateChatPanelFromTurns } from "./chat-renderer.js";
|
|
21
21
|
import { emitCommandNotice } from "./command-fallbacks.js";
|
|
22
22
|
import type { InteractiveNoticeLevel } from "./interactive-subscriptions.js";
|
|
23
|
-
import { buildModelReplayAgentMessagesFromTurns } from "./model-session-replay.js";
|
|
23
|
+
import { buildModelReplayAgentMessagesFromTurns, withContinuityReplay } from "./model-session-replay.js";
|
|
24
24
|
import type { OverlayTransitions } from "./overlay-transitions.js";
|
|
25
25
|
import { openCwdFallbackOverlay } from "./overlays/cwd-fallback.js";
|
|
26
26
|
import { openHandoffReviewOverlay } from "./overlays/handoff-review.js";
|
|
@@ -188,7 +188,7 @@ export function createOverlaySessionLifecycle(deps: OverlaySessionLifecycleDeps)
|
|
|
188
188
|
// after it (a compaction summary covering the leaf, above all)
|
|
189
189
|
// still belong on screen. uptoTurnId is the historical-truncation
|
|
190
190
|
// variant `/tree` uses.
|
|
191
|
-
const replayOptions = leafTurnId ? { activeLeafTurnId: leafTurnId } : {};
|
|
191
|
+
const replayOptions = withContinuityReplay(turns, leafTurnId ? { activeLeafTurnId: leafTurnId } : {}, session);
|
|
192
192
|
deps.resetTranscript();
|
|
193
193
|
rehydrateChatPanelFromTurns(deps.chatPanel, turns, replayOptions);
|
|
194
194
|
const replayMessages = buildModelReplayAgentMessagesFromTurns(turns, replayOptions);
|
|
@@ -240,8 +240,14 @@ export function createOverlaySessionLifecycle(deps: OverlaySessionLifecycleDeps)
|
|
|
240
240
|
if (!sessionId) throw new Error("no current session after turn switch");
|
|
241
241
|
const turns = deps.readStructuredEntries(sessionId);
|
|
242
242
|
deps.resetTranscript();
|
|
243
|
-
|
|
244
|
-
|
|
243
|
+
// `uptoTurnId` truncates the display at the selected turn. It is
|
|
244
|
+
// deliberately not a continuity `historical` flag: a live /tree
|
|
245
|
+
// selection is still this session's own branch, and treating every
|
|
246
|
+
// display truncation as a historical fork would surrender ownership
|
|
247
|
+
// of its own transactions for good.
|
|
248
|
+
const replayOptions = withContinuityReplay(turns, { uptoTurnId: turnId }, session);
|
|
249
|
+
rehydrateChatPanelFromTurns(deps.chatPanel, turns, replayOptions);
|
|
250
|
+
const replayMessages = buildModelReplayAgentMessagesFromTurns(turns, replayOptions);
|
|
245
251
|
deps.chat.resetForSession(turnId, replayMessages);
|
|
246
252
|
// The same branch the transcript above was just scoped to. Without the
|
|
247
253
|
// leaf, /usage, the footer Σ, and the last-turn line kept reporting the
|
|
@@ -324,12 +330,12 @@ export function createOverlaySessionLifecycle(deps: OverlaySessionLifecycleDeps)
|
|
|
324
330
|
function replayFork(forkedSessionId: string, parentTurnId: string, session: SessionContract): void {
|
|
325
331
|
try {
|
|
326
332
|
const turns = deps.readStructuredEntries(forkedSessionId);
|
|
327
|
-
rehydrateChatPanelFromTurns(deps.chatPanel, turns);
|
|
328
333
|
const leafTurnId = session.tree(forkedSessionId).leafId ?? parentTurnId;
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
);
|
|
334
|
+
// The child is already current here, so its own id and fork pointers are
|
|
335
|
+
// what separate the transactions it inherited from any it later mints.
|
|
336
|
+
const replayOptions = withContinuityReplay(turns, leafTurnId ? { activeLeafTurnId: leafTurnId } : {}, session);
|
|
337
|
+
rehydrateChatPanelFromTurns(deps.chatPanel, turns, replayOptions);
|
|
338
|
+
const replayMessages = buildModelReplayAgentMessagesFromTurns(turns, replayOptions);
|
|
333
339
|
deps.chat.resetForSession(leafTurnId, replayMessages);
|
|
334
340
|
rescopeToBranch(session, turns, leafTurnId);
|
|
335
341
|
} catch (error) {
|
|
@@ -617,8 +623,9 @@ export function createOverlaySessionLifecycle(deps: OverlaySessionLifecycleDeps)
|
|
|
617
623
|
try {
|
|
618
624
|
const turns = deps.readStructuredEntries(toSessionId);
|
|
619
625
|
deps.resetTranscript();
|
|
620
|
-
|
|
621
|
-
deps.
|
|
626
|
+
const replayOptions = withContinuityReplay(turns, {}, session);
|
|
627
|
+
rehydrateChatPanelFromTurns(deps.chatPanel, turns, replayOptions);
|
|
628
|
+
deps.chat.resetForSession(null, buildModelReplayAgentMessagesFromTurns(turns, replayOptions));
|
|
622
629
|
rescopeToBranch(session, turns, null);
|
|
623
630
|
} catch (error) {
|
|
624
631
|
deps.stderr(`[/handoff] seeding replay failed: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
@@ -3,7 +3,7 @@ import { collectSessionEntries } from "../domains/session/compaction/session-ent
|
|
|
3
3
|
import type { SessionContract, SessionEntry } from "../domains/session/index.js";
|
|
4
4
|
import { openSession, sessionPaths } from "../engine/session.js";
|
|
5
5
|
import type { ChatLoop } from "./chat-loop.js";
|
|
6
|
-
import { buildModelReplayAgentMessagesFromTurns } from "./model-session-replay.js";
|
|
6
|
+
import { buildModelReplayAgentMessagesFromTurns, continuityContextFromSession } from "./model-session-replay.js";
|
|
7
7
|
|
|
8
8
|
type SessionOwner = Pick<SessionContract, "create" | "current">;
|
|
9
9
|
type SessionChat = Pick<ChatLoop, "resetForSession">;
|
|
@@ -89,6 +89,7 @@ export function createSessionTranscript(deps: SessionTranscriptDeps): SessionTra
|
|
|
89
89
|
leafTurnId,
|
|
90
90
|
buildModelReplayAgentMessagesFromTurns(turns, {
|
|
91
91
|
...(leafTurnId ? { activeLeafTurnId: leafTurnId } : {}),
|
|
92
|
+
continuity: continuityContextFromSession(deps.session),
|
|
92
93
|
}),
|
|
93
94
|
);
|
|
94
95
|
deps.refreshStatus();
|
|
@@ -144,6 +144,7 @@ type SlashCommandVariant =
|
|
|
144
144
|
| { kind: "context-refresh" }
|
|
145
145
|
/** `ref` is the turnId an `[evicted ...]` marker names. */
|
|
146
146
|
| { kind: "context-recall"; ref: string }
|
|
147
|
+
| { kind: "context-recover"; handoffId: string; action: "reduce" | "deliver" }
|
|
147
148
|
| {
|
|
148
149
|
kind: "resources";
|
|
149
150
|
family?: "extensions" | "plugins";
|
|
@@ -823,6 +824,7 @@ export interface SlashCommandContext {
|
|
|
823
824
|
* appends a compactionSummary entry. No-op when no session is open, so the
|
|
824
825
|
* handler prints an actionable stderr line instead.
|
|
825
826
|
*/
|
|
827
|
+
runHandoffRecovery?: (handoffId: string, action: "reduce" | "deliver") => void;
|
|
826
828
|
runCompact: (instructions: string | undefined) => void;
|
|
827
829
|
/**
|
|
828
830
|
* Escape hatch for the `view verify` entry: verify a receipt file on disk
|
|
@@ -1700,9 +1702,10 @@ export const BUILTIN_SLASH_COMMANDS: ReadonlyArray<BuiltinSlashCommand> = [
|
|
|
1700
1702
|
name: "context",
|
|
1701
1703
|
description: "Context hub: window overlay plus compact, recall, init, refresh, and reset",
|
|
1702
1704
|
group: "Inspect",
|
|
1703
|
-
kinds: ["context-view", "compact", "context-recall", "init", "context-clear", "context-refresh"],
|
|
1705
|
+
kinds: ["context-view", "compact", "context-recover", "context-recall", "init", "context-clear", "context-refresh"],
|
|
1704
1706
|
subcommandDescriptions: {
|
|
1705
1707
|
compact: "Compact session context",
|
|
1708
|
+
recover: "Recover a paused handoff: ID reduce|deliver",
|
|
1706
1709
|
recall: "Recall an evicted result",
|
|
1707
1710
|
init: "Initialize project context",
|
|
1708
1711
|
refresh: "Refresh project context",
|
|
@@ -1711,6 +1714,12 @@ export const BUILTIN_SLASH_COMMANDS: ReadonlyArray<BuiltinSlashCommand> = [
|
|
|
1711
1714
|
args: {
|
|
1712
1715
|
subcommands: {
|
|
1713
1716
|
compact: { positionals: [...COMPACT_POSITIONALS] },
|
|
1717
|
+
recover: {
|
|
1718
|
+
positionals: [
|
|
1719
|
+
{ name: "handoffId", required: true },
|
|
1720
|
+
{ name: "action", required: true },
|
|
1721
|
+
],
|
|
1722
|
+
},
|
|
1714
1723
|
recall: { positionals: [...RECALL_POSITIONALS] },
|
|
1715
1724
|
init: {
|
|
1716
1725
|
flags: CONTEXT_INIT_FLAG_TABLE.flatMap(({ flag, aliases = [] }) => [flag, ...aliases].map((name) => ({ name }))),
|
|
@@ -1724,6 +1733,12 @@ export const BUILTIN_SLASH_COMMANDS: ReadonlyArray<BuiltinSlashCommand> = [
|
|
|
1724
1733
|
switch (parsed.subcommand) {
|
|
1725
1734
|
case "compact":
|
|
1726
1735
|
return { kind: "compact", instructions: parsed.rest };
|
|
1736
|
+
case "recover": {
|
|
1737
|
+
const [handoffId, action] = parsed.positionals;
|
|
1738
|
+
return handoffId && (action === "reduce" || action === "deliver")
|
|
1739
|
+
? { kind: "context-recover", handoffId, action }
|
|
1740
|
+
: { kind: "usage-error", command: "context", reason: "Use /context recover <handoffId> <reduce|deliver>" };
|
|
1741
|
+
}
|
|
1727
1742
|
case "recall":
|
|
1728
1743
|
return { kind: "context-recall", ref: parsed.positionals[0] ?? "" };
|
|
1729
1744
|
case "init": {
|
|
@@ -1752,6 +1767,10 @@ export const BUILTIN_SLASH_COMMANDS: ReadonlyArray<BuiltinSlashCommand> = [
|
|
|
1752
1767
|
case "compact":
|
|
1753
1768
|
ctx.runCompact(command.instructions);
|
|
1754
1769
|
return;
|
|
1770
|
+
case "context-recover":
|
|
1771
|
+
if (ctx.runHandoffRecovery) ctx.runHandoffRecovery(command.handoffId, command.action);
|
|
1772
|
+
else ctx.notice("error", "native handoff recovery is unavailable");
|
|
1773
|
+
return;
|
|
1755
1774
|
case "context-recall":
|
|
1756
1775
|
if (ctx.runContextRecall) {
|
|
1757
1776
|
ctx.runContextRecall(command.ref);
|