@iowarp/clio-coder 0.3.3 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/CONTRIBUTING.md +1 -1
- package/README.md +3 -3
- package/dist/{acp-P2AQILE2.js → acp-S5R4RR5B.js} +7 -6
- package/dist/{agents-72W3BI7I.js → agents-P6DMMVZY.js} +24 -21
- package/dist/assets/codewiki.json +1 -1
- package/dist/{auth-5TWEIYDN.js → auth-2XCZLPKS.js} +12 -8
- package/dist/{chunk-GGXXDWE4.js → chunk-22NAGB7X.js} +2 -2
- package/dist/{chunk-2DJ2KNFG.js → chunk-2LZI5CAG.js} +133 -13
- package/dist/{chunk-OAO4GE4M.js → chunk-2TZWSW76.js} +2 -2
- package/dist/{chunk-OOJYHWRB.js → chunk-34475P3I.js} +2 -2
- package/dist/{chunk-4XUGQOHA.js → chunk-35MKKU5R.js} +4 -4
- package/dist/{chunk-LM5TQCJZ.js → chunk-3HZ5RWN2.js} +5 -5
- package/dist/{chunk-AGYYIBLL.js → chunk-3JLKSKD7.js} +2 -2
- package/dist/{chunk-KZWTDYJF.js → chunk-4JUF2NNX.js} +7 -7
- package/dist/{chunk-ZDOOVTXZ.js → chunk-4OC57DA6.js} +27 -4
- package/dist/chunk-5M54SPOL.js +926 -0
- package/dist/{chunk-STBPMHSX.js → chunk-7RXG6QRZ.js} +51 -11
- package/dist/{chunk-X6IAEBZR.js → chunk-A2GZF7DC.js} +5 -5
- package/dist/{chunk-A3CYT5EX.js → chunk-AD2SYQYC.js} +55 -2
- package/dist/chunk-AOCYTWAV.js +449 -0
- package/dist/chunk-BEY543CS.js +258 -0
- package/dist/{chunk-6N5PTWMY.js → chunk-BP4OYD6A.js} +32 -13
- package/dist/chunk-BPGS2WCQ.js +612 -0
- package/dist/{chunk-V6RTAOC2.js → chunk-BRXQQJFP.js} +8 -8
- package/dist/chunk-CFGTUFWB.js +67 -0
- package/dist/{chunk-CBCAPZAA.js → chunk-E25LMLRW.js} +2 -2
- package/dist/{chunk-DUYJ5IO6.js → chunk-EDRHSCIE.js} +4 -4
- package/dist/{chunk-XBXAASKX.js → chunk-EFADSJET.js} +2 -2
- package/dist/{chunk-M6SHUN7Q.js → chunk-FO5ZOVUY.js} +2 -2
- package/dist/chunk-FYYLNIL5.js +313 -0
- package/dist/{chunk-FNTMWMX5.js → chunk-HV5X7OR2.js} +14 -12
- package/dist/{chunk-TZTZS7QK.js → chunk-HXG4IURW.js} +5 -3
- package/dist/{chunk-5UFT4SUX.js → chunk-K6WL7QZT.js} +3 -3
- package/dist/chunk-K7VKOLQQ.js +15 -0
- package/dist/{chunk-BMEMKKIT.js → chunk-KOHPCX4K.js} +2 -2
- package/dist/{chunk-OC7FIQPC.js → chunk-KRPY7NTG.js} +10 -7
- package/dist/chunk-LL4KHSZI.js +22 -0
- package/dist/{chunk-DSELYM6W.js → chunk-MEQ45TQ4.js} +15 -9
- package/dist/{chunk-5UUP6MWO.js → chunk-MV3K5QF2.js} +5 -436
- package/dist/{chunk-6SGHMWE3.js → chunk-N4CZJQRK.js} +5 -5
- package/dist/{chunk-TZK7PACC.js → chunk-NILBFAPG.js} +14 -8
- package/dist/chunk-OZNBF4L3.js +23 -0
- package/dist/{verify-375KUB3Y.js → chunk-PCZJO5TI.js} +127 -42
- package/dist/chunk-QQK64KLB.js +1360 -0
- package/dist/{chunk-SRDMMSEP.js → chunk-QQL5RT5M.js} +979 -1619
- package/dist/{chunk-LZSJBIVT.js → chunk-QWU7ZBO7.js} +70 -720
- package/dist/{chunk-2TLUCQVG.js → chunk-RD5U66HV.js} +3 -3
- package/dist/{chunk-OKGUZO2U.js → chunk-SPULKLCF.js} +4 -3
- package/dist/{chunk-OQ33BKR3.js → chunk-TTNYS3EA.js} +3 -60
- package/dist/chunk-TW3WDMVS.js +677 -0
- package/dist/chunk-TZSKNMZG.js +434 -0
- package/dist/{chunk-7MNJORFF.js → chunk-UL3WSD3F.js} +6 -1
- package/dist/{chunk-PIWWS5BL.js → chunk-UZHIZC5S.js} +7 -7
- package/dist/{chunk-UFIIWP2H.js → chunk-VAWWTKDP.js} +8 -8
- package/dist/{chunk-COU2UHX6.js → chunk-VEZEGCGW.js} +170 -2
- package/dist/{chunk-LW6DSM3M.js → chunk-VMNQ6OZA.js} +98 -202
- package/dist/chunk-VSNATDE6.js +122 -0
- package/dist/chunk-W6GROXXM.js +69 -0
- package/dist/chunk-WPQLXFOZ.js +375 -0
- package/dist/{chunk-ORBHGJC5.js → chunk-WR67VIZY.js} +3 -3
- package/dist/{chunk-PAJK6MAQ.js → chunk-X6COSD2O.js} +5 -5
- package/dist/chunk-ZGVHUX3M.js +66 -0
- package/dist/{chunk-LWLEKMDQ.js → chunk-ZYKPLLNQ.js} +510 -547
- package/dist/cli/index.js +27 -23
- package/dist/{clio-JOU4FXVA.js → clio-J5JIOIDS.js} +7 -6
- package/dist/{code-nav-7AX6FYE6.js → code-nav-AXCXSBHX.js} +5 -3
- package/dist/{config-XCDVKR23.js → config-OEBMIN2U.js} +37 -27
- package/dist/{configure-4GAP54ZW.js → configure-PUQOSIXQ.js} +16 -13
- package/dist/{context-5VKGUVJJ.js → context-EKDCKUUZ.js} +82 -7
- package/dist/{context-4UOGGLQ5.js → context-MGSE4Z2T.js} +33 -23
- package/dist/{context-77FM5DV5.js → context-URSXPBCK.js} +17 -9
- package/dist/{context-clear-XXJRLCJJ.js → context-clear-KDAJRNUK.js} +33 -23
- package/dist/context-working-set-SBKMPPI2.js +1552 -0
- package/dist/{dispatch-runner-QPRDDBDX.js → dispatch-runner-MSWN72NK.js} +43 -29
- package/dist/{doctor-HR46URBJ.js → doctor-7BSE27PJ.js} +10 -10
- package/dist/{eval-XSSNATB4.js → eval-IZGDOO4H.js} +9 -8
- package/dist/{evidence-6HG2PY2B.js → evidence-SR7WXB5B.js} +51 -23
- package/dist/{evolve-K7YU3NCY.js → evolve-K7VE2CBX.js} +30 -20
- package/dist/{fleet-VY3HHKN6.js → fleet-7XMJNQNF.js} +48 -38
- package/dist/{fleet-preflight-DDN536IT.js → fleet-preflight-AQNAH644.js} +3 -3
- package/dist/{init-JYGXI3FK.js → init-JGNPAYXT.js} +41 -31
- package/dist/{memory-WFZMGYHX.js → memory-4ALKDJ4Q.js} +32 -22
- package/dist/{models-I5QWSEOM.js → models-ZMMLFJNN.js} +22 -19
- package/dist/{monitor-GE4ID3IA.js → monitor-2F3T5KHP.js} +55 -43
- package/dist/{orchestrator-EM5MC3HM.js → orchestrator-ORHT43JB.js} +572 -388
- package/dist/{reset-L2FQEE3E.js → reset-NXGTYNUO.js} +4 -3
- package/dist/{run-ZU3QMZPZ.js → run-RF4WJGMT.js} +51 -41
- package/dist/{share-S5BZQC5I.js → share-UT3W6E4M.js} +5 -4
- package/dist/{skills-X5VXCRNQ.js → skills-PSACKC5Q.js} +2 -2
- package/dist/{skills-eval-WKIHWTHR.js → skills-eval-WJSI55RZ.js} +34 -24
- package/dist/{targets-SNCPI2NR.js → targets-PIIRAOYS.js} +23 -20
- package/dist/{terminal-lease-BNAHVHBS.js → terminal-lease-ULWXWNVY.js} +4 -3
- package/dist/{upgrade-JQHHPQ4K.js → upgrade-346TZ6AV.js} +18 -17
- package/dist/{usage-OR4O5SMZ.js → usage-6KKXR32N.js} +34 -24
- package/dist/verifiers-4UUM6TEE.js +1214 -0
- package/dist/verify-X5HDROLA.js +25 -0
- package/dist/{wiki-generate-UEXP2ARI.js → wiki-generate-7STOCIFZ.js} +42 -31
- package/dist/worker/entry.js +33 -24
- package/docs/README.md +7 -6
- package/docs/acp.md +1 -1
- package/docs/alcf-provider.md +1 -1
- package/docs/architecture.md +2 -2
- package/docs/artifact-versions.md +1 -1
- package/docs/built-in-agents.md +1 -1
- package/docs/capacity-and-scheduling.md +1 -1
- package/docs/commands-and-modes.md +53 -21
- package/docs/config-knobs-audit.md +1 -2
- package/docs/configuration-and-targets.md +15 -1
- package/docs/context-engine.md +64 -12
- package/docs/context-working-set.md +194 -0
- package/docs/development-pipeline.md +1 -1
- package/docs/documentation-coverage.md +5 -5
- package/docs/documentation-guide.md +6 -5
- package/docs/environment-variables.md +2 -1
- package/docs/eval-runner.md +1 -1
- package/docs/evals-internal.md +14 -1
- package/docs/evidence-and-memory.md +74 -2
- package/docs/evolution.md +1 -1
- package/docs/exit-codes-and-output.md +1 -1
- package/docs/extensions-and-sharing.md +2 -2
- package/docs/fleet-dispatch.md +22 -7
- package/docs/glossary.md +21 -1
- package/docs/installation-and-lifecycle.md +2 -2
- package/docs/middleware-and-components.md +1 -1
- package/docs/model-catalog.md +7 -9
- package/docs/observability.md +4 -4
- package/docs/proactive-memory.md +1 -1
- package/docs/prompt-envelope-and-tools.md +4 -4
- package/docs/provider-adapter-cookbook.md +1 -1
- package/docs/release-cut-checklist.md +31 -31
- package/docs/safety-model.md +23 -4
- package/docs/scientific-validation.md +21 -3
- package/docs/session-lifecycle.md +3 -3
- package/docs/skills-marketplace.md +1 -1
- package/docs/tool-usage.md +79 -12
- package/docs/trace-store.md +1 -1
- package/docs/troubleshooting.md +1 -1
- package/docs/tui-design.md +1 -1
- package/docs/worker-dispatch-mechanics.md +11 -1
- package/package.json +8 -11
- package/skills/meta/clio-test/SKILL.md +20 -17
- package/skills/meta/clio-test/evals.md +3 -3
- package/skills/meta/clio-test/references/harness.md +35 -6
- package/skills/meta/clio-test/references/test-map.md +20 -10
- package/skills/registry.yaml +2 -2
- package/skills/skill-marketplace.json +1 -1
- package/src/cli/context-working-set.ts +513 -0
- package/src/cli/context.ts +8 -0
- package/src/cli/evidence.ts +20 -2
- package/src/cli/index.ts +4 -0
- package/src/cli/verifiers.ts +325 -0
- package/src/core/bash-exec.ts +39 -14
- package/src/core/bus-events.ts +19 -4
- package/src/core/config.ts +54 -0
- package/src/core/defaults.ts +50 -3
- package/src/core/verification-scripts.ts +6 -0
- package/src/domains/agents/builtins/verifier.md +3 -0
- package/src/domains/config/classify.ts +1 -0
- package/src/domains/context/working-set/contract.ts +161 -0
- package/src/domains/context/working-set/defaults.ts +28 -0
- package/src/domains/context/working-set/engine.ts +203 -0
- package/src/domains/context/working-set/fold.ts +62 -0
- package/src/domains/context/working-set/horizon.ts +38 -0
- package/src/domains/context/working-set/marker.ts +103 -0
- package/src/domains/context/working-set/path-index.ts +436 -0
- package/src/domains/context/working-set/payload.ts +152 -0
- package/src/domains/context/working-set/policies/age-horizon.ts +55 -0
- package/src/domains/context/working-set/policies/index.ts +21 -0
- package/src/domains/context/working-set/policies/structural.ts +160 -0
- package/src/domains/context/working-set/project.ts +132 -0
- package/src/domains/context/working-set/protect.ts +109 -0
- package/src/domains/context/working-set/recall.ts +177 -0
- package/src/domains/context/working-set/replay/controls.ts +112 -0
- package/src/domains/context/working-set/replay/load-clio.ts +199 -0
- package/src/domains/context/working-set/replay/metrics.ts +185 -0
- package/src/domains/context/working-set/replay/reference-graph.ts +79 -0
- package/src/domains/context/working-set/replay/report.ts +139 -0
- package/src/domains/context/working-set/replay/runner.ts +325 -0
- package/src/domains/context/working-set/replay/synthetic.ts +422 -0
- package/src/domains/context/working-set/replay/trace.ts +21 -0
- package/src/domains/context/working-set/visible.ts +54 -0
- package/src/domains/evidence/build.ts +112 -45
- package/src/domains/evidence/eval.ts +24 -7
- package/src/domains/evidence/index.ts +53 -0
- package/src/domains/evidence/ordering.ts +12 -0
- package/src/domains/evidence/run-trust.ts +221 -0
- package/src/domains/evidence/store.ts +46 -6
- package/src/domains/evidence/trust-status.ts +854 -0
- package/src/domains/evidence/types.ts +26 -0
- package/src/domains/middleware/memory-intervention.ts +3 -0
- package/src/domains/middleware/stalled-turn.ts +165 -4
- package/src/domains/safety/autonomy.ts +1 -1
- package/src/domains/safety/default-path-policy.ts +8 -0
- package/src/domains/safety/finish-contract.ts +4 -3
- package/src/domains/safety/policy-engine.ts +48 -6
- package/src/domains/session/compaction/compact.ts +23 -1
- package/src/domains/session/compaction/cut-point.ts +2 -0
- package/src/domains/session/compaction/tokens.ts +16 -1
- package/src/domains/session/context-ledger.ts +2 -0
- package/src/domains/session/entries.ts +107 -1
- package/src/domains/session/manager.ts +9 -2
- package/src/domains/session/migrations/index.ts +22 -3
- package/src/engine/acp/server.ts +3 -0
- package/src/engine/agent.ts +18 -1
- package/src/engine/session.ts +9 -3
- package/src/entry/orchestrator.ts +16 -4
- package/src/interactive/chat-loop-messages.ts +18 -6
- package/src/interactive/chat-panel.ts +17 -1
- package/src/interactive/chat-renderer.ts +30 -21
- package/src/interactive/context-meter.ts +10 -0
- package/src/interactive/context-overlay.ts +81 -6
- package/src/interactive/context-recall-command.ts +110 -0
- package/src/interactive/interactive-slash-runtime.ts +37 -1
- package/src/interactive/model-session-replay.ts +21 -0
- package/src/interactive/overlay-general-openers.ts +6 -0
- package/src/interactive/overlay-session-lifecycle.ts +8 -4
- package/src/interactive/renderers/tool-execution.ts +18 -2
- package/src/interactive/session-transcript.ts +2 -2
- package/src/interactive/slash-commands.ts +29 -2
- package/src/interactive/turn-context.ts +238 -88
- package/src/interactive/turn-middleware.ts +6 -6
- package/src/tools/agent-tools.ts +11 -4
- package/src/tools/bash.ts +144 -82
- package/src/tools/builtin-tool-catalog.ts +11 -5
- package/src/tools/context/index.ts +105 -3
- package/src/tools/context/surface.ts +3 -2
- package/src/tools/core-bootstrap.ts +21 -0
- package/src/tools/dispatch-runner.ts +9 -7
- package/src/tools/monitor.ts +28 -20
- package/src/tools/registry.ts +59 -7
- package/src/tools/result-disposition.ts +550 -0
- package/src/tools/result-shaping.ts +262 -19
- package/src/tools/safe-exec.ts +2 -0
- package/src/tools/verify/authoring.ts +1119 -0
- package/src/tools/verify/catalog.ts +346 -0
- package/src/tools/verify/index.ts +13 -3
- package/src/tools/verify/scripts.ts +135 -37
- package/src/tools/verify/surface.ts +9 -5
- package/src/tools/worker-evidence.ts +35 -12
- package/dist/chunk-J7CWMCQD.js +0 -255
- package/dist/chunk-T6YILFSB.js +0 -80
- package/dist/chunk-VAKQQHWR.js +0 -434
- package/dist/chunk-VPAYEGVX.js +0 -184
|
@@ -2,7 +2,15 @@
|
|
|
2
2
|
* Strict session-format version reader.
|
|
3
3
|
*
|
|
4
4
|
* Runs before every session resume. Earlier pre-1.0 formats are disposable
|
|
5
|
-
* local state and must not be transformed into the current format.
|
|
5
|
+
* local state and must not be transformed into the current format. A version
|
|
6
|
+
* from the future is refused for the opposite reason: the file is not
|
|
7
|
+
* disposable, it belongs to a newer Clio, and this build would silently drop
|
|
8
|
+
* whatever that build understood and this one does not. Downgrading and
|
|
9
|
+
* resuming would then write the truncated reading back over the original.
|
|
10
|
+
*
|
|
11
|
+
* Version 3 to 4 is the one additive step: 4 only adds the working-set kinds
|
|
12
|
+
* (`contextEviction`, `contextRecall`), so a version-3 ledger is read as-is
|
|
13
|
+
* and the metadata is restamped. Nothing in the file changes.
|
|
6
14
|
*/
|
|
7
15
|
|
|
8
16
|
import { CURRENT_SESSION_FORMAT_VERSION } from "../../../engine/session.js";
|
|
@@ -10,6 +18,9 @@ import type { SessionMeta } from "../contract.js";
|
|
|
10
18
|
|
|
11
19
|
export { CURRENT_SESSION_FORMAT_VERSION };
|
|
12
20
|
|
|
21
|
+
/** Oldest version this build reads without transforming the ledger. */
|
|
22
|
+
export const OLDEST_READABLE_SESSION_FORMAT_VERSION = 3;
|
|
23
|
+
|
|
13
24
|
export interface MigrationResult {
|
|
14
25
|
migrated: boolean;
|
|
15
26
|
from: number;
|
|
@@ -18,10 +29,18 @@ export interface MigrationResult {
|
|
|
18
29
|
|
|
19
30
|
export function runMigrations(meta: SessionMeta, sessionPath: string): MigrationResult {
|
|
20
31
|
const from = meta.sessionFormatVersion ?? 1;
|
|
21
|
-
if (from <
|
|
32
|
+
if (from < OLDEST_READABLE_SESSION_FORMAT_VERSION) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`session metadata has an unsupported format version (expected version ${CURRENT_SESSION_FORMAT_VERSION}, got ${from}): ${sessionPath}. Remove the session directory to start a new session.`,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
if (from > CURRENT_SESSION_FORMAT_VERSION) {
|
|
22
38
|
throw new Error(
|
|
23
|
-
`session
|
|
39
|
+
`session was written by a newer Clio (format version ${from}, this build reads version ${CURRENT_SESSION_FORMAT_VERSION}): ${sessionPath}. Upgrade clio-coder to resume this session.`,
|
|
24
40
|
);
|
|
25
41
|
}
|
|
42
|
+
if (from < CURRENT_SESSION_FORMAT_VERSION) {
|
|
43
|
+
return { migrated: true, from, to: CURRENT_SESSION_FORMAT_VERSION };
|
|
44
|
+
}
|
|
26
45
|
return { migrated: false, from, to: from };
|
|
27
46
|
}
|
package/src/engine/acp/server.ts
CHANGED
|
@@ -12,6 +12,7 @@ import type { SessionContract, SessionMeta } from "../../domains/session/contrac
|
|
|
12
12
|
import type { MessageEntry, SessionEntry } from "../../domains/session/entries.js";
|
|
13
13
|
import { filterEntriesToActivePath } from "../../domains/session/tree/active-path.js";
|
|
14
14
|
import type { ToolRegistry } from "../../tools/registry.js";
|
|
15
|
+
import { toolResultPresentationText } from "../../tools/result-disposition.js";
|
|
15
16
|
import type { AgentMessage } from "../types.js";
|
|
16
17
|
import { ACP_TURN_FAILED_MESSAGE, AcpRequestError, AcpTimeoutError, acpErrorMessage } from "./errors.js";
|
|
17
18
|
import type { AcpJsonRpcPeerTransport } from "./transport.js";
|
|
@@ -537,6 +538,8 @@ function applyStopReason(active: ActivePrompt, message: unknown): void {
|
|
|
537
538
|
function outputText(value: unknown): string {
|
|
538
539
|
if (typeof value === "string") return value;
|
|
539
540
|
if (!isRecord(value)) return "";
|
|
541
|
+
const presentationText = toolResultPresentationText(value);
|
|
542
|
+
if (presentationText !== null) return presentationText;
|
|
540
543
|
if (Array.isArray(value.content)) return contentText(value.content);
|
|
541
544
|
if (typeof value.output === "string") return value.output;
|
|
542
545
|
if (typeof value.text === "string") return value.text;
|
package/src/engine/agent.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { Agent, type AgentOptions, type StreamFn } from "@earendil-works/pi-agent-core";
|
|
14
|
+
import { isDispositionedToolResultError } from "../tools/result-disposition.js";
|
|
14
15
|
import { engineStreamSimple } from "./api-registry.js";
|
|
15
16
|
|
|
16
17
|
export type EngineAgentOptions = Omit<AgentOptions, "streamFn"> & { streamFn?: StreamFn };
|
|
@@ -20,8 +21,24 @@ export interface EngineAgentHandle {
|
|
|
20
21
|
state(): Agent["state"];
|
|
21
22
|
}
|
|
22
23
|
|
|
24
|
+
function dispositionAwareAfterToolCall(
|
|
25
|
+
delegate: AgentOptions["afterToolCall"],
|
|
26
|
+
): NonNullable<AgentOptions["afterToolCall"]> {
|
|
27
|
+
return async (context, signal) => {
|
|
28
|
+
const override = await delegate?.(context, signal);
|
|
29
|
+
const effectiveResult =
|
|
30
|
+
override?.details === undefined ? context.result : { ...context.result, details: override.details };
|
|
31
|
+
if (isDispositionedToolResultError(effectiveResult)) return { ...override, isError: true };
|
|
32
|
+
return override;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
23
36
|
export function createEngineAgent(options: EngineAgentOptions = {}): EngineAgentHandle {
|
|
24
|
-
const agent = new Agent({
|
|
37
|
+
const agent = new Agent({
|
|
38
|
+
streamFn: engineStreamSimple,
|
|
39
|
+
...options,
|
|
40
|
+
afterToolCall: dispositionAwareAfterToolCall(options.afterToolCall),
|
|
41
|
+
});
|
|
25
42
|
return {
|
|
26
43
|
agent,
|
|
27
44
|
state: () => agent.state,
|
package/src/engine/session.ts
CHANGED
|
@@ -57,13 +57,19 @@ export interface ClioSessionMeta {
|
|
|
57
57
|
platform: string;
|
|
58
58
|
nodeVersion: string;
|
|
59
59
|
/**
|
|
60
|
-
* Version
|
|
61
|
-
*
|
|
60
|
+
* Version 4 on every new session. Readers reject earlier versions, sessions
|
|
61
|
+
* that omit this field, and versions from the future.
|
|
62
62
|
*/
|
|
63
63
|
sessionFormatVersion?: number;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Version 4 adds the working-set ledger kinds (`contextEviction`,
|
|
68
|
+
* `contextRecall`). A version-3 reader would parse those entries as unknown
|
|
69
|
+
* kinds and replay a session as if nothing had been evicted, so the bump is
|
|
70
|
+
* what makes an older Clio refuse the file instead of misreading it.
|
|
71
|
+
*/
|
|
72
|
+
export const CURRENT_SESSION_FORMAT_VERSION = 4;
|
|
67
73
|
|
|
68
74
|
export interface ClioSessionJsonlHeader {
|
|
69
75
|
type: "session";
|
|
@@ -161,8 +161,8 @@ import {
|
|
|
161
161
|
import { openSession, readSessionTailTurns, sessionCurrentPath, sessionPaths } from "../engine/session.js";
|
|
162
162
|
import type { EngineModel } from "../engine/types.js";
|
|
163
163
|
import { createChatLoop } from "../interactive/chat-loop.js";
|
|
164
|
-
import { buildReplayAgentMessagesFromTurns } from "../interactive/chat-renderer.js";
|
|
165
164
|
import { type RunIo, startInteractive } from "../interactive/index.js";
|
|
165
|
+
import { buildModelReplayAgentMessagesFromTurns } from "../interactive/model-session-replay.js";
|
|
166
166
|
import type { BootOptions } from "./boot-options.js";
|
|
167
167
|
|
|
168
168
|
export type { BootOptions, HeadlessSamplingOverrides } from "./boot-options.js";
|
|
@@ -1236,7 +1236,16 @@ export async function bootOrchestrator(options: BootOptions = {}): Promise<BootR
|
|
|
1236
1236
|
// one object the dispatch tool and the keypress both hold.
|
|
1237
1237
|
const dispatchBackground = createDispatchBackgroundRegistry();
|
|
1238
1238
|
registerAllTools(toolRegistry, {
|
|
1239
|
-
...(session
|
|
1239
|
+
...(session
|
|
1240
|
+
? {
|
|
1241
|
+
session,
|
|
1242
|
+
readSessionEntries: () => {
|
|
1243
|
+
const meta = session.current();
|
|
1244
|
+
return meta ? readSessionEntriesForCompact(meta.id) : [];
|
|
1245
|
+
},
|
|
1246
|
+
onContextRecalled: (payload) => bus.emit(BusChannels.ContextRecalled, payload),
|
|
1247
|
+
}
|
|
1248
|
+
: {}),
|
|
1240
1249
|
taskBoard,
|
|
1241
1250
|
userTasks,
|
|
1242
1251
|
dispatch,
|
|
@@ -1612,7 +1621,10 @@ export async function bootOrchestrator(options: BootOptions = {}): Promise<BootR
|
|
|
1612
1621
|
// parent onto.
|
|
1613
1622
|
chat.resetForSession(
|
|
1614
1623
|
leafTurnId,
|
|
1615
|
-
|
|
1624
|
+
buildModelReplayAgentMessagesFromTurns(
|
|
1625
|
+
readCurrentSessionEntries(),
|
|
1626
|
+
leafTurnId ? { activeLeafTurnId: leafTurnId } : {},
|
|
1627
|
+
),
|
|
1616
1628
|
);
|
|
1617
1629
|
} catch (err) {
|
|
1618
1630
|
chat.resetForSession(leafTurnId);
|
|
@@ -1664,7 +1676,7 @@ export async function bootOrchestrator(options: BootOptions = {}): Promise<BootR
|
|
|
1664
1676
|
? {
|
|
1665
1677
|
readSessionEntries: readSessionEntriesForCompact,
|
|
1666
1678
|
buildReplayMessages: (entries: ReadonlyArray<SessionEntry>, leafTurnId: string | null) =>
|
|
1667
|
-
|
|
1679
|
+
buildModelReplayAgentMessagesFromTurns(entries, leafTurnId === null ? {} : { activeLeafTurnId: leafTurnId }),
|
|
1668
1680
|
}
|
|
1669
1681
|
: {}),
|
|
1670
1682
|
providers,
|
|
@@ -555,21 +555,33 @@ export function toolResultSummary(result: unknown): Record<string, unknown> {
|
|
|
555
555
|
const bytes = Buffer.byteLength(text, "utf8");
|
|
556
556
|
const details = recordValue(obj?.details);
|
|
557
557
|
const size = recordValue(details?.resultSize);
|
|
558
|
+
const disposition = recordValue(details?.resultDisposition);
|
|
558
559
|
const truncation = recordValue(details?.truncation);
|
|
559
560
|
const observation = recordValue(details?.observation);
|
|
560
561
|
const offloadPath =
|
|
561
|
-
typeof
|
|
562
|
-
?
|
|
563
|
-
: typeof
|
|
564
|
-
?
|
|
565
|
-
:
|
|
562
|
+
typeof disposition?.offloadPath === "string"
|
|
563
|
+
? disposition.offloadPath
|
|
564
|
+
: typeof observation?.offloadPath === "string"
|
|
565
|
+
? observation.offloadPath
|
|
566
|
+
: typeof size?.offloadPath === "string"
|
|
567
|
+
? size.offloadPath
|
|
568
|
+
: null;
|
|
569
|
+
const displayedBytes =
|
|
570
|
+
typeof disposition?.displayedBytes === "number" && Number.isFinite(disposition.displayedBytes)
|
|
571
|
+
? disposition.displayedBytes
|
|
572
|
+
: bytes;
|
|
566
573
|
return {
|
|
567
|
-
bytes,
|
|
574
|
+
bytes: displayedBytes,
|
|
568
575
|
truncated:
|
|
576
|
+
disposition?.presentationTruncated === true ||
|
|
569
577
|
size?.truncated === true ||
|
|
570
578
|
truncation?.truncated === true ||
|
|
571
579
|
observation?.truncated === true ||
|
|
572
580
|
text.includes("[tool result truncated]"),
|
|
581
|
+
...(typeof disposition?.capturedBytes === "number" ? { capturedBytes: disposition.capturedBytes } : {}),
|
|
582
|
+
...(typeof disposition?.displayedBytes === "number" ? { displayedBytes: disposition.displayedBytes } : {}),
|
|
583
|
+
...(typeof disposition?.contextBytes === "number" ? { contextBytes: disposition.contextBytes } : {}),
|
|
584
|
+
...(disposition?.contextTruncated === true ? { contextTruncated: true } : {}),
|
|
573
585
|
...(typeof size?.policy === "string" ? { policy: size.policy } : {}),
|
|
574
586
|
...(typeof size?.followUpHint === "string" ? { followUpHint: size.followUpHint } : {}),
|
|
575
587
|
...(offloadPath !== null ? { offloadPath } : {}),
|
|
@@ -5,6 +5,7 @@ import { rawDurationMs } from "../core/timers.js";
|
|
|
5
5
|
import { type Component, Markdown, truncateToWidth, wrapTextWithAnsi } from "../engine/tui.js";
|
|
6
6
|
import type { AgentMessage } from "../engine/types.js";
|
|
7
7
|
import { toolPresentationPolicy } from "../tools/presentation.js";
|
|
8
|
+
import { toolResultPresentationPolicy } from "../tools/result-disposition.js";
|
|
8
9
|
import type { ChatLoopEvent, RetryStatusPayload } from "./chat-loop.js";
|
|
9
10
|
import { extractText, isSelfExplainingAbort } from "./chat-loop-messages.js";
|
|
10
11
|
import type { ApprovalRequestView } from "./permission-overlay.js";
|
|
@@ -202,6 +203,12 @@ type ToolSegment = {
|
|
|
202
203
|
* was refused and leaves the operator no way to learn why.
|
|
203
204
|
*/
|
|
204
205
|
blockReason?: string | undefined;
|
|
206
|
+
/**
|
|
207
|
+
* Working-set eviction reason for a replayed result whose body the
|
|
208
|
+
* projection has replaced. Set only on rehydrate, from the folded ledger;
|
|
209
|
+
* a live call is never evicted while it is still being rendered.
|
|
210
|
+
*/
|
|
211
|
+
evictedReason?: string | undefined;
|
|
205
212
|
/** View-only marker: historical calls render mutation diffs without live color. */
|
|
206
213
|
replayed?: true;
|
|
207
214
|
};
|
|
@@ -835,7 +842,7 @@ function styleStatusVerb(text: string, toneHint: VerbRender["toneHint"]): string
|
|
|
835
842
|
function policySegmentFold(seg: ToolSegment, detail: TranscriptDetailPolicy): Fold {
|
|
836
843
|
if (!seg.finished) return policyRunningToolFold(detail);
|
|
837
844
|
if (seg.isError && detail.errors === "body") return "expanded";
|
|
838
|
-
return policyToolFold(detail, toolPresentationPolicy(seg.name, seg.args));
|
|
845
|
+
return policyToolFold(detail, toolResultPresentationPolicy(seg.result) ?? toolPresentationPolicy(seg.name, seg.args));
|
|
839
846
|
}
|
|
840
847
|
|
|
841
848
|
/** Effective state of a tool segment: the operator's override, else the policy. */
|
|
@@ -884,6 +891,7 @@ function renderToolSegmentLines(
|
|
|
884
891
|
resultSummary: seg.resultSummary,
|
|
885
892
|
outcome: seg.settlement,
|
|
886
893
|
blockReason: seg.blockReason,
|
|
894
|
+
evictedReason: seg.evictedReason,
|
|
887
895
|
}
|
|
888
896
|
: { toolCallId: seg.id, toolName: seg.name, args: seg.args, elapsedMs, phase },
|
|
889
897
|
width,
|
|
@@ -916,6 +924,7 @@ function renderToolSegmentLines(
|
|
|
916
924
|
resultSummary: seg.resultSummary,
|
|
917
925
|
outcome: seg.settlement,
|
|
918
926
|
blockReason: seg.blockReason,
|
|
927
|
+
evictedReason: seg.evictedReason,
|
|
919
928
|
},
|
|
920
929
|
width,
|
|
921
930
|
{ unbounded: unboundedToolBodies, diffStyle: seg.replayed === true ? "plain" : "color" },
|
|
@@ -1936,7 +1945,14 @@ export function createChatPanel(options: ChatPanelOptions = {}): ChatPanel {
|
|
|
1936
1945
|
resultSummary?: unknown;
|
|
1937
1946
|
outcome?: unknown;
|
|
1938
1947
|
blockReason?: unknown;
|
|
1948
|
+
evictedReason?: unknown;
|
|
1939
1949
|
};
|
|
1950
|
+
// Replay-only: the rehydrator reads the working-set fold and tags
|
|
1951
|
+
// the rows whose bodies the projection has replaced for the model.
|
|
1952
|
+
tool.evictedReason =
|
|
1953
|
+
typeof enriched.evictedReason === "string" && enriched.evictedReason.length > 0
|
|
1954
|
+
? enriched.evictedReason
|
|
1955
|
+
: undefined;
|
|
1940
1956
|
// Settlement is that verdict, never an inference from result text.
|
|
1941
1957
|
// The text of a tool result is the tool's own output: `node --test`
|
|
1942
1958
|
// prints `cancelled 0` on every run and a linter can print
|
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { ToolNames } from "../core/tool-names.js";
|
|
17
|
+
import { foldWorkingSet } from "../domains/context/working-set/fold.js";
|
|
18
|
+
import { compactionCut } from "../domains/context/working-set/visible.js";
|
|
17
19
|
import type {
|
|
18
20
|
BashExecutionEntry,
|
|
19
21
|
BranchSummaryEntry,
|
|
@@ -40,6 +42,7 @@ import {
|
|
|
40
42
|
import { wrapTextWithAnsi } from "../engine/tui.js";
|
|
41
43
|
import type { AgentMessage } from "../engine/types.js";
|
|
42
44
|
import { toolPresentationPolicy } from "../tools/presentation.js";
|
|
45
|
+
import { toolResultPresentationText } from "../tools/result-disposition.js";
|
|
43
46
|
import type { ChatLoopEvent, RetryStatusPayload } from "./chat-loop.js";
|
|
44
47
|
import { isSelfExplainingAbort } from "./chat-loop-messages.js";
|
|
45
48
|
import type { ChatPanel } from "./chat-panel.js";
|
|
@@ -482,7 +485,11 @@ function toolResultContent(result: unknown, unbounded = false): unknown[] {
|
|
|
482
485
|
}
|
|
483
486
|
|
|
484
487
|
function displayReplayToolResult(result: unknown, unbounded = false): unknown {
|
|
485
|
-
const
|
|
488
|
+
const presentationText = toolResultPresentationText(result);
|
|
489
|
+
const content =
|
|
490
|
+
presentationText === null
|
|
491
|
+
? toolResultContent(result, unbounded)
|
|
492
|
+
: [{ type: "text", text: unbounded ? presentationText : truncateReplayText(presentationText) }];
|
|
486
493
|
// Preserve the details record (observation envelope, exec records) so the
|
|
487
494
|
// replayed ledger line carries the same outcome facts as the live one.
|
|
488
495
|
const details = payloadObject(result)?.details;
|
|
@@ -835,13 +842,6 @@ function truncateAtTurn(entries: ReadonlyArray<SessionEntry>, uptoTurnId?: strin
|
|
|
835
842
|
return entries.slice(0, index + 1);
|
|
836
843
|
}
|
|
837
844
|
|
|
838
|
-
function latestCompactionIndex(entries: ReadonlyArray<SessionEntry>): number {
|
|
839
|
-
for (let i = entries.length - 1; i >= 0; i--) {
|
|
840
|
-
if (entries[i]?.kind === "compactionSummary") return i;
|
|
841
|
-
}
|
|
842
|
-
return -1;
|
|
843
|
-
}
|
|
844
|
-
|
|
845
845
|
function toolCallIdsInEntry(entry: SessionEntry): string[] {
|
|
846
846
|
if (entry.kind !== "message") return [];
|
|
847
847
|
if (entry.role === "tool_call") return [extractToolCall(entry).id];
|
|
@@ -919,19 +919,13 @@ export function selectReplayEntries(
|
|
|
919
919
|
): SessionEntry[] {
|
|
920
920
|
const active = filterEntriesToActivePath(turns, options.activeLeafTurnId ?? options.uptoTurnId);
|
|
921
921
|
const entries = truncateAtTurn(active, options.uptoTurnId);
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
const
|
|
927
|
-
const
|
|
928
|
-
|
|
929
|
-
: -1;
|
|
930
|
-
if (firstKeptIndex >= 0 && firstKeptIndex < compactionIndex) {
|
|
931
|
-
selected.push(...entries.slice(firstKeptIndex, compactionIndex));
|
|
932
|
-
}
|
|
933
|
-
selected.push(...entries.slice(compactionIndex + 1));
|
|
934
|
-
return dropLegacyToolResultAssistantDuplicates(repairToolResultOrphans(entries, selected, compactionIndex));
|
|
922
|
+
// The cut itself is the working-set layer's definition of "what the model
|
|
923
|
+
// can see", shared with the eviction policy input so the two cannot drift.
|
|
924
|
+
const cut = compactionCut(entries);
|
|
925
|
+
if (cut.compactionIndex < 0) return dropLegacyToolResultAssistantDuplicates(entries);
|
|
926
|
+
const compaction = entries[cut.compactionIndex] as CompactionSummaryEntry;
|
|
927
|
+
const selected: SessionEntry[] = [compaction, ...cut.visible];
|
|
928
|
+
return dropLegacyToolResultAssistantDuplicates(repairToolResultOrphans(entries, selected, cut.compactionIndex));
|
|
935
929
|
}
|
|
936
930
|
|
|
937
931
|
function dropLegacyToolResultAssistantDuplicates(entries: ReadonlyArray<SessionEntry>): SessionEntry[] {
|
|
@@ -1043,6 +1037,11 @@ export function buildReplayAgentMessagesFromTurns(
|
|
|
1043
1037
|
// It reaches the model only when an operator shares it, and a share
|
|
1044
1038
|
// is already a user message by the time it lands in the ledger.
|
|
1045
1039
|
case "workerRun":
|
|
1040
|
+
// Working-set entries are folded into a view by the context domain
|
|
1041
|
+
// and applied as a projection before this builder runs; the
|
|
1042
|
+
// entries themselves never become messages.
|
|
1043
|
+
case "contextEviction":
|
|
1044
|
+
case "contextRecall":
|
|
1046
1045
|
break;
|
|
1047
1046
|
}
|
|
1048
1047
|
}
|
|
@@ -1080,6 +1079,11 @@ export function rehydrateChatPanelFromTurns(
|
|
|
1080
1079
|
const pendingToolIds: string[] = [];
|
|
1081
1080
|
let runAssistantMessages: AgentMessage[] = [];
|
|
1082
1081
|
const selected = selectReplayEntries(turns, options);
|
|
1082
|
+
// The transcript shows the ledger, never the projection: an evicted result
|
|
1083
|
+
// still renders its full body here, tagged with the reason it left the
|
|
1084
|
+
// model's working set. Folded once over the same active path the replay
|
|
1085
|
+
// uses, so a /tree switch cannot tag a row from an abandoned branch.
|
|
1086
|
+
const workingSet = foldWorkingSet(turns, options.activeLeafTurnId ?? options.uptoTurnId);
|
|
1083
1087
|
// One block per assignment, drawn where its first attempt started. Later
|
|
1084
1088
|
// attempts of the same assignment fold into that block as `↻` rail lines, so
|
|
1085
1089
|
// a failover replays as the one run it was rather than as two.
|
|
@@ -1127,6 +1131,7 @@ export function rehydrateChatPanelFromTurns(
|
|
|
1127
1131
|
}
|
|
1128
1132
|
if (entry.role === "tool_result") {
|
|
1129
1133
|
const result = extractToolResult(entry);
|
|
1134
|
+
const evictedReason = workingSet.evicted.get(entry.turnId)?.reason;
|
|
1130
1135
|
const fallbackId = result.id ?? pendingToolIds.pop() ?? null;
|
|
1131
1136
|
if (fallbackId) {
|
|
1132
1137
|
const pendingIndex = pendingToolIds.indexOf(fallbackId);
|
|
@@ -1141,6 +1146,7 @@ export function rehydrateChatPanelFromTurns(
|
|
|
1141
1146
|
...(result.resultSummary !== undefined ? { resultSummary: result.resultSummary } : {}),
|
|
1142
1147
|
...(result.outcome !== undefined ? { outcome: result.outcome } : {}),
|
|
1143
1148
|
...(result.blockReason !== undefined ? { blockReason: result.blockReason } : {}),
|
|
1149
|
+
...(evictedReason !== undefined ? { evictedReason } : {}),
|
|
1144
1150
|
} as ChatLoopEvent);
|
|
1145
1151
|
} else {
|
|
1146
1152
|
chatPanel.appendReplayBlock((width) =>
|
|
@@ -1154,6 +1160,7 @@ export function rehydrateChatPanelFromTurns(
|
|
|
1154
1160
|
...(result.resultSummary !== undefined ? { resultSummary: result.resultSummary } : {}),
|
|
1155
1161
|
...(result.outcome === "blocked" ? { outcome: "blocked" as const } : {}),
|
|
1156
1162
|
...(result.blockReason !== undefined ? { blockReason: result.blockReason } : {}),
|
|
1163
|
+
...(evictedReason !== undefined ? { evictedReason } : {}),
|
|
1157
1164
|
},
|
|
1158
1165
|
width,
|
|
1159
1166
|
{ unbounded: options.unboundedToolBodies === true },
|
|
@@ -1239,6 +1246,8 @@ export function rehydrateChatPanelFromTurns(
|
|
|
1239
1246
|
case "label":
|
|
1240
1247
|
case "taskLedger":
|
|
1241
1248
|
case "decisionLedger":
|
|
1249
|
+
case "contextEviction":
|
|
1250
|
+
case "contextRecall":
|
|
1242
1251
|
break;
|
|
1243
1252
|
}
|
|
1244
1253
|
}
|
|
@@ -156,6 +156,16 @@ export function renderContextMeterGrid(
|
|
|
156
156
|
return lines;
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Evicted tokens live outside the window: the working-set projection removed
|
|
161
|
+
* them from what the model sees, so they are not a meter category and must
|
|
162
|
+
* not claim cells or a legend row. One line after the legend names them.
|
|
163
|
+
*/
|
|
164
|
+
export function renderEvictedTokensLine(evictedTokens: number, theme: ClioTheme = clioTheme()): string {
|
|
165
|
+
const tokens = Math.round(Math.max(0, evictedTokens)).toLocaleString("en-US");
|
|
166
|
+
return `${theme.fg("dim", GLYPH.contextReserve)} ${theme.fg("muted", "evicted (outside window)")} ${theme.fg("dim", `${tokens} tokens`)}`;
|
|
167
|
+
}
|
|
168
|
+
|
|
159
169
|
/** A small colored swatch for a category, for inline legends. */
|
|
160
170
|
export function contextCategorySwatch(category: ContextLedgerCategory, theme: ClioTheme = clioTheme()): string {
|
|
161
171
|
return theme.fg(CONTEXT_CATEGORY_TOKEN[category], contextCategoryGlyph(category));
|
|
@@ -2,9 +2,10 @@ import { homedir } from "node:os";
|
|
|
2
2
|
import { relative } from "node:path";
|
|
3
3
|
import { BusChannels } from "../core/bus-events.js";
|
|
4
4
|
import type { SafeEventBus } from "../core/event-bus.js";
|
|
5
|
+
import type { WorkingSetView } from "../domains/context/working-set/contract.js";
|
|
5
6
|
import type { ContextLedger, ContextLedgerGroup } from "../domains/session/context-ledger.js";
|
|
6
7
|
import { type OverlayHandle, Text, type TUI, visibleWidth } from "../engine/tui.js";
|
|
7
|
-
import { contextCategorySwatch, renderContextMeterGrid } from "./context-meter.js";
|
|
8
|
+
import { contextCategorySwatch, renderContextMeterGrid, renderEvictedTokensLine } from "./context-meter.js";
|
|
8
9
|
import { buildHint, showClioOverlayFrame } from "./overlay-frame.js";
|
|
9
10
|
import { abbreviateModelId, type ClioToken, clioTheme, formatContextPercent } from "./theme/index.js";
|
|
10
11
|
|
|
@@ -81,7 +82,62 @@ function legendRow(group: ContextLedgerGroup, contentWidth: number): string {
|
|
|
81
82
|
return `${swatch} ${theme.fg(labelToken, labelText)} ${theme.fg("muted", right)}`;
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
|
|
85
|
+
function evictedTokens(view: WorkingSetView): number {
|
|
86
|
+
let total = 0;
|
|
87
|
+
for (const state of view.evicted.values()) total += state.tokensFreed;
|
|
88
|
+
return total;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function formatChurn(view: WorkingSetView): string {
|
|
92
|
+
if (view.itemsEvicted === 0) return "n/a";
|
|
93
|
+
return (view.recalls / view.itemsEvicted).toFixed(2);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Prose for one cache-disturbance reason. The wire values are stamped by
|
|
98
|
+
* `noteColdReason` in turn-context.ts and persisted on the assistant entry's
|
|
99
|
+
* `promptCache.expectedColdReasons`; the overlay reads them back, so an unknown
|
|
100
|
+
* reason renders as itself rather than disappearing.
|
|
101
|
+
*/
|
|
102
|
+
function coldReasonLabel(reason: string): string {
|
|
103
|
+
switch (reason) {
|
|
104
|
+
case "working_set_evict":
|
|
105
|
+
return "working-set eviction";
|
|
106
|
+
case "compaction":
|
|
107
|
+
return "compaction";
|
|
108
|
+
case "dispatch":
|
|
109
|
+
return "dispatch traffic";
|
|
110
|
+
default:
|
|
111
|
+
return reason;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* The working-set section: what the projection has taken out of the window
|
|
117
|
+
* and how often the model has asked for it back. Churn is recalls over items
|
|
118
|
+
* evicted; a high number means the policy evicts what is still needed.
|
|
119
|
+
*/
|
|
120
|
+
function renderWorkingSetLines(view: WorkingSetView): string[] {
|
|
121
|
+
const theme = clioTheme();
|
|
122
|
+
const items = view.evicted.size;
|
|
123
|
+
const summary = [
|
|
124
|
+
`${items} evicted item${items === 1 ? "" : "s"}`,
|
|
125
|
+
`${formatTokens(evictedTokens(view))} tokens`,
|
|
126
|
+
`${view.evictionEvents} event${view.evictionEvents === 1 ? "" : "s"}`,
|
|
127
|
+
`${view.recalls} recall${view.recalls === 1 ? "" : "s"}`,
|
|
128
|
+
`churn ${formatChurn(view)}`,
|
|
129
|
+
].join(" · ");
|
|
130
|
+
return [
|
|
131
|
+
`${theme.fg("muted", "working set")} ${theme.fg("dim", "·")} ${theme.fg("accent", `policy ${view.lastPolicyId ?? "none"}`)}`,
|
|
132
|
+
theme.fg("dim", summary),
|
|
133
|
+
];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function renderContextLedgerLines(
|
|
137
|
+
ledger: ContextLedger,
|
|
138
|
+
contentWidth: number,
|
|
139
|
+
workingSet?: WorkingSetView | null,
|
|
140
|
+
): string[] {
|
|
85
141
|
const theme = clioTheme();
|
|
86
142
|
const lines: string[] = [];
|
|
87
143
|
|
|
@@ -110,8 +166,13 @@ export function renderContextLedgerLines(ledger: ContextLedger, contentWidth: nu
|
|
|
110
166
|
lines.push("");
|
|
111
167
|
|
|
112
168
|
for (const group of ledger.meter) lines.push(legendRow(group, contentWidth));
|
|
169
|
+
if (workingSet && workingSet.evicted.size > 0) lines.push(renderEvictedTokensLine(evictedTokens(workingSet), theme));
|
|
113
170
|
|
|
114
171
|
lines.push("");
|
|
172
|
+
if (workingSet) {
|
|
173
|
+
for (const line of renderWorkingSetLines(workingSet)) lines.push(line);
|
|
174
|
+
lines.push("");
|
|
175
|
+
}
|
|
115
176
|
if (ledger.projectPreload && ledger.groups.some((group) => group.category === "project")) {
|
|
116
177
|
lines.push(theme.fg("dim", `project preload: ${ledger.projectPreload}`));
|
|
117
178
|
}
|
|
@@ -141,11 +202,21 @@ export function renderContextLedgerLines(ledger: ContextLedger, contentWidth: nu
|
|
|
141
202
|
const uncached =
|
|
142
203
|
cache.uncachedInputTokens !== null ? `uncached input ${formatTokens(cache.uncachedInputTokens)}` : null;
|
|
143
204
|
const line = ["prompt cache:", shell, "·", backend, "·", read, ...(uncached ? ["·", uncached] : [])].join(" ");
|
|
205
|
+
// Reasons Clio recorded before the run: working-set eviction, summary
|
|
206
|
+
// compaction, and dispatch traffic all move the byte prefix a local
|
|
207
|
+
// single-slot backend caches, so a cold turn after one of them is the
|
|
208
|
+
// expected outcome rather than a provider surprise.
|
|
209
|
+
const coldReasons = cache.backendVerdict === "cold" ? (cache.expectedColdReasons ?? []) : [];
|
|
144
210
|
// A reused shell with a cold backend means Clio kept the bytes stable
|
|
145
211
|
// but the provider re-prefilled anyway; surface that disagreement
|
|
146
|
-
// instead of hiding it.
|
|
147
|
-
|
|
212
|
+
// instead of hiding it. An expected reason explains the same numbers, so
|
|
213
|
+
// it is reported on its own line and not as a warning.
|
|
214
|
+
const misleading = cache.shellReused && cache.backendVerdict === "cold" && coldReasons.length === 0;
|
|
148
215
|
lines.push(theme.fg(misleading ? "warning" : "dim", line));
|
|
216
|
+
if (coldReasons.length > 0) {
|
|
217
|
+
const reasons = coldReasons.map(coldReasonLabel).join(", ");
|
|
218
|
+
lines.push(theme.fg("dim", `last cold turn: ${reasons} (expected)`));
|
|
219
|
+
}
|
|
149
220
|
}
|
|
150
221
|
|
|
151
222
|
if (ledger.lastCompaction) {
|
|
@@ -170,6 +241,8 @@ export interface OpenContextOverlayOptions {
|
|
|
170
241
|
onEvent(handler: (event: { type: string }) => void): () => void;
|
|
171
242
|
isStreaming(): boolean;
|
|
172
243
|
};
|
|
244
|
+
/** Working-set fold at the live leaf; null or absent hides the section. */
|
|
245
|
+
getWorkingSet?: () => WorkingSetView | null;
|
|
173
246
|
}
|
|
174
247
|
|
|
175
248
|
/**
|
|
@@ -183,7 +256,9 @@ export function openContextOverlay(
|
|
|
183
256
|
getLedger: () => ContextLedger,
|
|
184
257
|
options?: OpenContextOverlayOptions,
|
|
185
258
|
): OverlayHandle {
|
|
186
|
-
const
|
|
259
|
+
const render = (): string =>
|
|
260
|
+
renderContextLedgerLines(getLedger(), DEFAULT_CONTENT_WIDTH, options?.getWorkingSet?.() ?? null).join("\n");
|
|
261
|
+
const text = new Text(render(), 0, 0);
|
|
187
262
|
const handle = showClioOverlayFrame(tui, text, {
|
|
188
263
|
anchor: "center",
|
|
189
264
|
width: CONTEXT_OVERLAY_WIDTH,
|
|
@@ -192,7 +267,7 @@ export function openContextOverlay(
|
|
|
192
267
|
});
|
|
193
268
|
|
|
194
269
|
const refresh = (): void => {
|
|
195
|
-
text.setText(
|
|
270
|
+
text.setText(render());
|
|
196
271
|
text.invalidate();
|
|
197
272
|
tui.requestRender();
|
|
198
273
|
};
|