@iowarp/clio-coder 0.3.2 → 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 +269 -458
- package/CONTRIBUTING.md +1 -1
- package/README.md +3 -3
- package/dist/{acp-BIYHVZIM.js → acp-S5R4RR5B.js} +7 -6
- package/dist/{agents-YT6SSRIT.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-WMSVI4G2.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-WVO7V2QY.js → chunk-35MKKU5R.js} +4 -4
- package/dist/{chunk-LBNRH5WM.js → chunk-3HZ5RWN2.js} +5 -5
- package/dist/{chunk-AGYYIBLL.js → chunk-3JLKSKD7.js} +2 -2
- package/dist/{chunk-MBS4V7ZP.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-77VKQEHF.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-J5HN4RYU.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-G2DE3C7R.js → chunk-EDRHSCIE.js} +4 -4
- package/dist/{chunk-4KLWL3UC.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-EPVUXGXG.js → chunk-HV5X7OR2.js} +14 -12
- package/dist/{chunk-TZTZS7QK.js → chunk-HXG4IURW.js} +5 -3
- package/dist/{chunk-IGLFWIYI.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-V4RXGQ5Q.js → chunk-KRPY7NTG.js} +10 -7
- package/dist/chunk-LL4KHSZI.js +22 -0
- package/dist/{chunk-KJ5LWLOE.js → chunk-MEQ45TQ4.js} +15 -9
- package/dist/{chunk-5UUP6MWO.js → chunk-MV3K5QF2.js} +5 -436
- package/dist/{chunk-AO4RKG4M.js → chunk-N4CZJQRK.js} +5 -5
- package/dist/{chunk-ARBGF5F7.js → chunk-NILBFAPG.js} +14 -8
- package/dist/chunk-OZNBF4L3.js +23 -0
- package/dist/{verify-G6V4D2G7.js → chunk-PCZJO5TI.js} +127 -42
- package/dist/chunk-QQK64KLB.js +1360 -0
- package/dist/{chunk-6EJV5X2W.js → chunk-QQL5RT5M.js} +979 -1619
- package/dist/{chunk-LZSJBIVT.js → chunk-QWU7ZBO7.js} +70 -720
- package/dist/{chunk-2EHAIA3X.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-7EYHLWU7.js → chunk-UZHIZC5S.js} +7 -7
- package/dist/{chunk-QTYWRVRA.js → chunk-VAWWTKDP.js} +8 -8
- package/dist/{chunk-X75S7HFS.js → chunk-VEZEGCGW.js} +214 -20
- package/dist/{chunk-OHHN2SO4.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-3ZXDFGR5.js → chunk-X6COSD2O.js} +5 -5
- package/dist/chunk-ZGVHUX3M.js +66 -0
- package/dist/{chunk-MAW544W2.js → chunk-ZWMF7253.js} +4 -4
- package/dist/{chunk-MQSRRFWA.js → chunk-ZYKPLLNQ.js} +563 -546
- package/dist/cli/index.js +27 -23
- package/dist/{clio-4LY5K2AC.js → clio-J5JIOIDS.js} +7 -6
- package/dist/{code-nav-7AX6FYE6.js → code-nav-AXCXSBHX.js} +5 -3
- package/dist/{config-GTLUW2PR.js → config-OEBMIN2U.js} +37 -27
- package/dist/{configure-R6A64DHX.js → configure-PUQOSIXQ.js} +16 -13
- package/dist/{context-5VKGUVJJ.js → context-EKDCKUUZ.js} +82 -7
- package/dist/{context-RW5HC47S.js → context-MGSE4Z2T.js} +33 -23
- package/dist/{context-JFZEJ7W5.js → context-URSXPBCK.js} +17 -9
- package/dist/{context-clear-6ZHBAZZT.js → context-clear-KDAJRNUK.js} +33 -23
- package/dist/context-working-set-SBKMPPI2.js +1552 -0
- package/dist/{dispatch-runner-VKBRCWQC.js → dispatch-runner-MSWN72NK.js} +43 -29
- package/dist/{doctor-KI767GSN.js → doctor-7BSE27PJ.js} +10 -10
- package/dist/{eval-XSSNATB4.js → eval-IZGDOO4H.js} +9 -8
- package/dist/{evidence-UA6AWDQQ.js → evidence-SR7WXB5B.js} +51 -23
- package/dist/{evolve-QNTFGV6Z.js → evolve-K7VE2CBX.js} +30 -20
- package/dist/{fleet-Q7UOMUSG.js → fleet-7XMJNQNF.js} +48 -38
- package/dist/{fleet-preflight-DDN536IT.js → fleet-preflight-AQNAH644.js} +3 -3
- package/dist/{init-WBB65ZHQ.js → init-JGNPAYXT.js} +41 -31
- package/dist/{memory-MD3O64RI.js → memory-4ALKDJ4Q.js} +32 -22
- package/dist/{models-BZU34YWD.js → models-ZMMLFJNN.js} +22 -19
- package/dist/{monitor-MEQA5C3I.js → monitor-2F3T5KHP.js} +55 -43
- package/dist/{orchestrator-CGFKEP27.js → orchestrator-ORHT43JB.js} +2507 -1896
- package/dist/{reset-L2FQEE3E.js → reset-NXGTYNUO.js} +4 -3
- package/dist/{run-IV4Q6RLN.js → run-RF4WJGMT.js} +51 -41
- package/dist/{share-S5BZQC5I.js → share-UT3W6E4M.js} +5 -4
- package/dist/{skills-LQEKRDTN.js → skills-PSACKC5Q.js} +2 -2
- package/dist/{skills-eval-3DC4HEWS.js → skills-eval-WJSI55RZ.js} +34 -24
- package/dist/{targets-C4SSGQOB.js → targets-PIIRAOYS.js} +23 -20
- package/dist/{terminal-lease-IT5JW2NR.js → terminal-lease-ULWXWNVY.js} +5 -4
- package/dist/{upgrade-7TT7SQ3G.js → upgrade-346TZ6AV.js} +18 -17
- package/dist/{usage-GV4PKT3M.js → usage-6KKXR32N.js} +34 -24
- package/dist/verifiers-4UUM6TEE.js +1214 -0
- package/dist/verify-X5HDROLA.js +25 -0
- package/dist/{wiki-generate-DQF6Z66B.js → wiki-generate-7STOCIFZ.js} +42 -31
- package/dist/worker/entry.js +33 -24
- package/docs/README.md +8 -7
- 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 +6 -6
- package/docs/middleware-and-components.md +1 -1
- package/docs/model-catalog.md +7 -9
- package/docs/observability.md +4 -4
- package/docs/performance-methodology.md +2 -2
- 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 +35 -35
- 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 +2 -2
- 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/git-commit-attribution.ts +46 -21
- 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/config/keybindings.ts +3 -3
- 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 +571 -244
- package/src/interactive/chat-renderer.ts +79 -39
- 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/editor-submit.ts +26 -1
- package/src/interactive/footer/widgets.ts +22 -20
- package/src/interactive/footer-panel.ts +6 -1
- package/src/interactive/interactive-application.ts +2 -0
- package/src/interactive/interactive-event-projection.ts +12 -0
- package/src/interactive/interactive-slash-runtime.ts +49 -8
- 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/overlays/ask-user.ts +146 -24
- package/src/interactive/renderers/tool-execution.ts +167 -56
- package/src/interactive/session-transcript.ts +2 -2
- package/src/interactive/slash-commands.ts +29 -2
- package/src/interactive/status/index.ts +12 -1
- package/src/interactive/status/reasoning.ts +87 -0
- package/src/interactive/status/summary.ts +13 -2
- package/src/interactive/transcript-detail.ts +120 -0
- 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 +18 -6
- 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/presentation.ts +107 -0
- package/src/tools/registry.ts +65 -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-MNA4JGU4.js +0 -255
- package/dist/chunk-SRF2PJNW.js +0 -184
- package/dist/chunk-T6YILFSB.js +0 -80
- package/dist/chunk-VAKQQHWR.js +0 -434
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { SessionEntry } from "../../../session/entries.js";
|
|
2
|
+
import type { PathIndex, PathObservation } from "../path-index.js";
|
|
3
|
+
import type { Trace } from "./trace.js";
|
|
4
|
+
|
|
5
|
+
export interface ReferenceEdge {
|
|
6
|
+
/** Ref key of the earlier tool_result. */
|
|
7
|
+
from: string;
|
|
8
|
+
toTurnIndex: number;
|
|
9
|
+
kind: "file_reread" | "file_discovery" | "file_rewrite";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ReferenceGraph {
|
|
13
|
+
edges: ReadonlyArray<ReferenceEdge>;
|
|
14
|
+
/** Critical future references only: file_reread and file_discovery. */
|
|
15
|
+
futureTurnsOf: ReadonlyMap<string, ReadonlyArray<number>>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const READ_CLASS_OPS = new Set<PathObservation["op"]>(["read", "grep", "find", "ls", "code_nav"]);
|
|
19
|
+
const MUTATION_OPS = new Set<PathObservation["op"]>(["write", "edit"]);
|
|
20
|
+
|
|
21
|
+
function isToolResult(entry: SessionEntry | undefined): boolean {
|
|
22
|
+
return entry?.kind === "message" && entry.role === "tool_result";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function isReadableObservation(observation: PathObservation): boolean {
|
|
26
|
+
return !observation.isError && observation.path.length > 0 && READ_CLASS_OPS.has(observation.op);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function edgeKey(edge: ReferenceEdge): string {
|
|
30
|
+
return `${edge.from}\u0000${edge.toTurnIndex}\u0000${edge.kind}`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Label future path use without inspecting result prose. Rewrites are emitted
|
|
35
|
+
* for diagnosis but deliberately stay out of `futureTurnsOf`: a mutation makes
|
|
36
|
+
* the earlier read stale rather than critical to retain.
|
|
37
|
+
*/
|
|
38
|
+
export function buildReferenceGraph(trace: Trace, index: PathIndex): ReferenceGraph {
|
|
39
|
+
const entryById = new Map(trace.entries.map((entry) => [entry.turnId, entry]));
|
|
40
|
+
const edges: ReferenceEdge[] = [];
|
|
41
|
+
const seenEdges = new Set<string>();
|
|
42
|
+
const future = new Map<string, Set<number>>();
|
|
43
|
+
|
|
44
|
+
const add = (edge: ReferenceEdge): void => {
|
|
45
|
+
const key = edgeKey(edge);
|
|
46
|
+
if (seenEdges.has(key)) return;
|
|
47
|
+
seenEdges.add(key);
|
|
48
|
+
edges.push(edge);
|
|
49
|
+
if (edge.kind === "file_rewrite") return;
|
|
50
|
+
const turns = future.get(edge.from);
|
|
51
|
+
if (turns === undefined) future.set(edge.from, new Set([edge.toTurnIndex]));
|
|
52
|
+
else turns.add(edge.toTurnIndex);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
for (const earlier of index.observations) {
|
|
56
|
+
if (!isToolResult(entryById.get(earlier.ref.entry)) || !isReadableObservation(earlier)) continue;
|
|
57
|
+
const surfaced = new Set(earlier.surfaced);
|
|
58
|
+
for (const later of index.observations) {
|
|
59
|
+
if (later.entryIndex <= earlier.entryIndex) continue;
|
|
60
|
+
if (isReadableObservation(later) && later.path === earlier.path) {
|
|
61
|
+
add({ from: earlier.ref.entry, toTurnIndex: later.turnIndex, kind: "file_reread" });
|
|
62
|
+
}
|
|
63
|
+
if (isReadableObservation(later) && surfaced.has(later.path)) {
|
|
64
|
+
add({ from: earlier.ref.entry, toTurnIndex: later.turnIndex, kind: "file_discovery" });
|
|
65
|
+
}
|
|
66
|
+
if (later.path === earlier.path && MUTATION_OPS.has(later.op)) {
|
|
67
|
+
add({ from: earlier.ref.entry, toTurnIndex: later.turnIndex, kind: "file_rewrite" });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const futureTurnsOf = new Map<string, ReadonlyArray<number>>();
|
|
73
|
+
for (const [ref, turns] of future)
|
|
74
|
+
futureTurnsOf.set(
|
|
75
|
+
ref,
|
|
76
|
+
[...turns].sort((a, b) => a - b),
|
|
77
|
+
);
|
|
78
|
+
return { edges, futureTurnsOf };
|
|
79
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { WorkingSetSettings } from "../../../../core/defaults.js";
|
|
2
|
+
import type { ReplayLoadCascade } from "./load-clio.js";
|
|
3
|
+
import type { ReplayMetricAggregate, ReplayMetrics } from "./metrics.js";
|
|
4
|
+
|
|
5
|
+
export interface ReplayReportConfig {
|
|
6
|
+
policies: ReadonlyArray<string>;
|
|
7
|
+
budgets: ReadonlyArray<number>;
|
|
8
|
+
threshold: number;
|
|
9
|
+
target: number;
|
|
10
|
+
seed: number;
|
|
11
|
+
/** Synthetic corpus ids replayed, if any; ledgers from --sessions are "ledgers". */
|
|
12
|
+
corpus: ReadonlyArray<string>;
|
|
13
|
+
filter: "default" | "none";
|
|
14
|
+
settings: WorkingSetSettings;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ReplayPolicyResult {
|
|
18
|
+
budgetTokens: number;
|
|
19
|
+
policyId: string;
|
|
20
|
+
metrics: ReplayMetricAggregate;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ReplayReportInput {
|
|
24
|
+
config: ReplayReportConfig;
|
|
25
|
+
cascade: ReplayLoadCascade;
|
|
26
|
+
results: ReadonlyArray<ReplayPolicyResult>;
|
|
27
|
+
gitSha: string | null;
|
|
28
|
+
commandLine: ReadonlyArray<string>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function metricObject(metrics: ReplayMetrics, turnsToFirstSummaryCount: number): Record<string, number | null> {
|
|
32
|
+
return {
|
|
33
|
+
traces: metrics.traces,
|
|
34
|
+
retention: metrics.retention,
|
|
35
|
+
retentionCovered: metrics.retentionCovered,
|
|
36
|
+
retentionAt10: metrics.retentionAt10,
|
|
37
|
+
evictionPrecision: metrics.evictionPrecision,
|
|
38
|
+
tokensEvicted: metrics.tokensEvicted,
|
|
39
|
+
recallTokens: metrics.recallTokens,
|
|
40
|
+
coldPrefixTokens: metrics.coldPrefixTokens,
|
|
41
|
+
evictionEvents: metrics.evictionEvents,
|
|
42
|
+
saturatedEvents: metrics.saturatedEvents,
|
|
43
|
+
turnsToFirstSummary: metrics.turnsToFirstSummary,
|
|
44
|
+
turnsToFirstSummaryCount,
|
|
45
|
+
summaries: metrics.summaries,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function renderReplayJson(input: ReplayReportInput): string {
|
|
50
|
+
const filtered = Object.fromEntries(Object.entries(input.cascade.filtered).sort(([a], [b]) => a.localeCompare(b)));
|
|
51
|
+
const artifact = {
|
|
52
|
+
schema: "clio-context-replay-v2",
|
|
53
|
+
config: {
|
|
54
|
+
policies: [...input.config.policies],
|
|
55
|
+
budgets: [...input.config.budgets],
|
|
56
|
+
threshold: input.config.threshold,
|
|
57
|
+
target: input.config.target,
|
|
58
|
+
seed: input.config.seed,
|
|
59
|
+
corpus: [...input.config.corpus],
|
|
60
|
+
filter: input.config.filter,
|
|
61
|
+
settings: {
|
|
62
|
+
enabled: input.config.settings.enabled,
|
|
63
|
+
policy: input.config.settings.policy,
|
|
64
|
+
target: input.config.settings.target,
|
|
65
|
+
protectLastTurns: input.config.settings.protectLastTurns,
|
|
66
|
+
minEvictableTokens: input.config.settings.minEvictableTokens,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
provenance: {
|
|
70
|
+
gitSha: input.gitSha,
|
|
71
|
+
commandLine: [...input.commandLine],
|
|
72
|
+
},
|
|
73
|
+
cascade: {
|
|
74
|
+
found: input.cascade.found,
|
|
75
|
+
unreadable: input.cascade.unreadable,
|
|
76
|
+
filtered,
|
|
77
|
+
kept: input.cascade.kept,
|
|
78
|
+
},
|
|
79
|
+
results: input.results.map((result) => ({
|
|
80
|
+
budgetTokens: result.budgetTokens,
|
|
81
|
+
policyId: result.policyId,
|
|
82
|
+
metrics: {
|
|
83
|
+
mean: metricObject(result.metrics.mean, result.metrics.turnsToFirstSummaryCount),
|
|
84
|
+
pooledRetention: result.metrics.pooledRetention,
|
|
85
|
+
pooledRetentionCovered: result.metrics.pooledRetentionCovered,
|
|
86
|
+
pooledRetentionAt10: result.metrics.pooledRetentionAt10,
|
|
87
|
+
},
|
|
88
|
+
})),
|
|
89
|
+
};
|
|
90
|
+
return `${JSON.stringify(artifact, null, "\t")}\n`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function ratio(value: number): string {
|
|
94
|
+
return value.toFixed(3);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function quantity(value: number): string {
|
|
98
|
+
return Number.isInteger(value) ? String(value) : value.toFixed(1);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function cascadeRows(cascade: ReplayLoadCascade): string[] {
|
|
102
|
+
return [
|
|
103
|
+
`| found | ${cascade.found} |`,
|
|
104
|
+
`| unreadable | ${cascade.unreadable} |`,
|
|
105
|
+
...Object.entries(cascade.filtered).map(([stage, count]) => `| ${stage} | ${count} |`),
|
|
106
|
+
`| kept | ${cascade.kept} |`,
|
|
107
|
+
];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function renderReplayMarkdown(input: ReplayReportInput): string {
|
|
111
|
+
const lines = [
|
|
112
|
+
"# Clio working-set replay",
|
|
113
|
+
"",
|
|
114
|
+
"## Inclusion cascade",
|
|
115
|
+
"",
|
|
116
|
+
"| stage | traces |",
|
|
117
|
+
"| --- | ---: |",
|
|
118
|
+
...cascadeRows(input.cascade),
|
|
119
|
+
];
|
|
120
|
+
for (const budget of input.config.budgets) {
|
|
121
|
+
lines.push(
|
|
122
|
+
"",
|
|
123
|
+
`## Budget ${budget}`,
|
|
124
|
+
"",
|
|
125
|
+
"| policy | n | retention (mean) | retention (pooled) | retention covered (mean) | retention covered (pooled) | retention@10 (mean) | eviction precision (mean) | tokens evicted (mean) | recall tokens (mean) | cold prefix tokens (mean) | eviction events (mean) | saturated events | turns to first summary (mean) | summaries (mean) |",
|
|
126
|
+
"| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |",
|
|
127
|
+
);
|
|
128
|
+
for (const policy of input.config.policies) {
|
|
129
|
+
const result = input.results.find((entry) => entry.budgetTokens === budget && entry.policyId === policy);
|
|
130
|
+
if (result === undefined) continue;
|
|
131
|
+
const metrics = result.metrics.mean;
|
|
132
|
+
lines.push(
|
|
133
|
+
`| ${policy} | ${metrics.traces} | ${ratio(metrics.retention)} | ${ratio(result.metrics.pooledRetention)} | ${ratio(metrics.retentionCovered)} | ${ratio(result.metrics.pooledRetentionCovered)} | ${ratio(metrics.retentionAt10)} | ${ratio(metrics.evictionPrecision)} | ${quantity(metrics.tokensEvicted)} | ${quantity(metrics.recallTokens)} | ${quantity(metrics.coldPrefixTokens)} | ${quantity(metrics.evictionEvents)} | ${ratio(metrics.saturatedEvents)} | ${metrics.turnsToFirstSummary === null ? "—" : quantity(metrics.turnsToFirstSummary)} (n=${result.metrics.turnsToFirstSummaryCount}) | ${quantity(metrics.summaries)} |`,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
lines.push("");
|
|
138
|
+
return lines.join("\n");
|
|
139
|
+
}
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import type { WorkingSetSettings } from "../../../../core/defaults.js";
|
|
2
|
+
import { findCutPoint } from "../../../session/compaction/cut-point.js";
|
|
3
|
+
import { estimateTokens } from "../../../session/compaction/tokens.js";
|
|
4
|
+
import type {
|
|
5
|
+
CompactionSummaryEntry,
|
|
6
|
+
ContextEvictionEntry,
|
|
7
|
+
EvictedItem,
|
|
8
|
+
SessionEntry,
|
|
9
|
+
} from "../../../session/entries.js";
|
|
10
|
+
import { EMPTY_WORKING_SET_VIEW, type PolicyInput, type WorkingSetPolicy, type WorkingSetView } from "../contract.js";
|
|
11
|
+
import { buildEvictionFields, planEviction } from "../engine.js";
|
|
12
|
+
import { foldWorkingSet } from "../fold.js";
|
|
13
|
+
import { isTurnStart } from "../horizon.js";
|
|
14
|
+
import { projectWorkingSet } from "../project.js";
|
|
15
|
+
import { selectVisibleEntries } from "../visible.js";
|
|
16
|
+
import type { ReplayCandidatePoolPolicy } from "./controls.js";
|
|
17
|
+
import type { Trace } from "./trace.js";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The summary stage, modeled. Live, when the projection is still over the
|
|
21
|
+
* threshold after an eviction, Clio summarizes: it cuts the history at
|
|
22
|
+
* `findCutPoint(entries, keepRecentTokens)` and replaces everything before
|
|
23
|
+
* the cut with one paraphrase. On iterative compaction, the cut search starts
|
|
24
|
+
* strictly after the previous `compactionSummary`; the retained suffix that
|
|
25
|
+
* replay still shows is canonical input to the new paraphrase, not new raw
|
|
26
|
+
* history to price a second time. A long trace replayed without that stage
|
|
27
|
+
* spends most of its length in a regime the product never enters, because
|
|
28
|
+
* operator text, call arguments, and markers accumulate past any budget and
|
|
29
|
+
* no policy can evict them. The model appends the same `compactionSummary`
|
|
30
|
+
* record the live path would, with a constant-size summary standing in for the
|
|
31
|
+
* paraphrase, and counts it, so a policy is judged by how rarely it forces the
|
|
32
|
+
* one lossy, token-spending stage.
|
|
33
|
+
*/
|
|
34
|
+
export interface ReplaySummaryModel {
|
|
35
|
+
/** Mirrors `compaction.keepRecentTokens`: the cut keeps at least this many recent tokens. */
|
|
36
|
+
keepRecentTokens: number;
|
|
37
|
+
/** Tokens the stand-in summary occupies. */
|
|
38
|
+
summaryTokens: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ReplayConfig {
|
|
42
|
+
policyId: string;
|
|
43
|
+
budgetTokens: number;
|
|
44
|
+
threshold: number;
|
|
45
|
+
target: number;
|
|
46
|
+
settings: WorkingSetSettings;
|
|
47
|
+
seed: number;
|
|
48
|
+
/** Absent: summaries are counted as `turnsToFirstSummary` but never applied. */
|
|
49
|
+
summaries?: ReplaySummaryModel;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface ReplayEvictionEvent {
|
|
53
|
+
turnIndex: number;
|
|
54
|
+
items: ReadonlyArray<EvictedItem>;
|
|
55
|
+
tokensBefore: number;
|
|
56
|
+
tokensAfter: number;
|
|
57
|
+
/** True when this event exhausted the policy's usable candidate pool. */
|
|
58
|
+
saturated: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* Tokens of the projected working set from the earliest evicted position to
|
|
61
|
+
* the end, after the event: what an exact-prefix cache (Anthropic, OpenAI,
|
|
62
|
+
* vLLM) re-prefills on the next request because the bytes before it moved.
|
|
63
|
+
*/
|
|
64
|
+
coldPrefixTokens: number;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface ReplayTraceResult {
|
|
68
|
+
traceId: string;
|
|
69
|
+
policyId: string;
|
|
70
|
+
budgetTokens: number;
|
|
71
|
+
turnCount: number;
|
|
72
|
+
events: ReadonlyArray<ReplayEvictionEvent>;
|
|
73
|
+
/** Tool-result refs only; thinking-unit evictions are intentionally absent. */
|
|
74
|
+
evictedAtTurn: ReadonlyMap<string, number>;
|
|
75
|
+
turnsToFirstSummary: number | null;
|
|
76
|
+
/** Summary compactions applied under `config.summaries`. */
|
|
77
|
+
summaries: number;
|
|
78
|
+
/** Original entries plus synthetic append-only contextEviction and compactionSummary records. */
|
|
79
|
+
entries: ReadonlyArray<SessionEntry>;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function sumTokens(entries: ReadonlyArray<SessionEntry>): number {
|
|
83
|
+
let tokens = 0;
|
|
84
|
+
for (const entry of entries) tokens += estimateTokens(entry);
|
|
85
|
+
return tokens;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function hasCandidatePool(policy: WorkingSetPolicy): policy is ReplayCandidatePoolPolicy {
|
|
89
|
+
return "replayCandidateCount" in policy && typeof policy.replayCandidateCount === "function";
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function latestCompactionIndex(entries: ReadonlyArray<SessionEntry>): number {
|
|
93
|
+
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
94
|
+
if (entries[index]?.kind === "compactionSummary") return index;
|
|
95
|
+
}
|
|
96
|
+
return -1;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function eventSaturated(
|
|
100
|
+
policy: WorkingSetPolicy,
|
|
101
|
+
input: PolicyInput,
|
|
102
|
+
plan: { items: ReadonlyArray<EvictedItem>; tokensAfter: number },
|
|
103
|
+
): boolean {
|
|
104
|
+
if (policy.id === "age-horizon") return true;
|
|
105
|
+
const targetTokens = input.pressure.target * input.pressure.contextWindow;
|
|
106
|
+
if (policy.id === "structural-v1") {
|
|
107
|
+
const thresholdTokens = input.pressure.threshold * input.pressure.contextWindow;
|
|
108
|
+
const usedAgeRung = plan.items.some((item) => item.reason === "age_horizon");
|
|
109
|
+
// Rungs 1-5 can legitimately stop between target and threshold. Saturation
|
|
110
|
+
// means rung 6 actually ran and exhausted its pool before reaching target.
|
|
111
|
+
return plan.tokensAfter > targetTokens && (plan.tokensAfter > thresholdTokens || usedAgeRung);
|
|
112
|
+
}
|
|
113
|
+
if (hasCandidatePool(policy)) return plan.items.length === policy.replayCandidateCount(input);
|
|
114
|
+
return plan.tokensAfter > targetTokens;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
interface IncrementalProjection {
|
|
118
|
+
raw: SessionEntry[];
|
|
119
|
+
projected: SessionEntry[];
|
|
120
|
+
indexByTurnId: Map<string, number>;
|
|
121
|
+
tokens: number;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function rebuildProjection(
|
|
125
|
+
soFar: ReadonlyArray<SessionEntry>,
|
|
126
|
+
leaf: string | null,
|
|
127
|
+
view: WorkingSetView,
|
|
128
|
+
): IncrementalProjection {
|
|
129
|
+
const raw = selectVisibleEntries(soFar, leaf ?? undefined);
|
|
130
|
+
const projected = projectWorkingSet(raw, view);
|
|
131
|
+
return {
|
|
132
|
+
raw,
|
|
133
|
+
projected,
|
|
134
|
+
indexByTurnId: new Map(raw.map((entry, index) => [entry.turnId, index])),
|
|
135
|
+
tokens: sumTokens(projected),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function projectAppendedEntry(entry: SessionEntry, state: IncrementalProjection, view: WorkingSetView): SessionEntry {
|
|
140
|
+
if (view.evictionEvents === 0) return entry;
|
|
141
|
+
const lastEventId = view.lastEvictionTurnId;
|
|
142
|
+
if (lastEventId !== null && state.indexByTurnId.has(lastEventId)) {
|
|
143
|
+
// The new entry follows the visible cutoff event, so it is unchanged.
|
|
144
|
+
return entry;
|
|
145
|
+
}
|
|
146
|
+
// The latest event is behind a compaction cut. projectWorkingSet deliberately
|
|
147
|
+
// treats an absent event as later than this slice, so new assistants inherit
|
|
148
|
+
// the same usage-invalidation stamp as the rest of the visible slice.
|
|
149
|
+
return projectWorkingSet([entry], view)[0] ?? entry;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function appendVisibleEntry(entry: SessionEntry, state: IncrementalProjection, view: WorkingSetView): void {
|
|
153
|
+
const projected = projectAppendedEntry(entry, state, view);
|
|
154
|
+
state.indexByTurnId.set(entry.turnId, state.raw.length);
|
|
155
|
+
state.raw.push(entry);
|
|
156
|
+
state.projected.push(projected);
|
|
157
|
+
state.tokens += estimateTokens(projected);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function applyEvictionProjection(
|
|
161
|
+
state: IncrementalProjection,
|
|
162
|
+
synthetic: ContextEvictionEntry,
|
|
163
|
+
view: WorkingSetView,
|
|
164
|
+
items: ReadonlyArray<EvictedItem>,
|
|
165
|
+
): void {
|
|
166
|
+
const affected = new Set<number>();
|
|
167
|
+
for (const item of items) {
|
|
168
|
+
const index = state.indexByTurnId.get(item.ref.entry);
|
|
169
|
+
if (index !== undefined) affected.add(index);
|
|
170
|
+
}
|
|
171
|
+
// The new cutoff invalidates usage on every assistant before it. Scanning
|
|
172
|
+
// assistants once per applied event is linear in events, never in turns.
|
|
173
|
+
for (let index = 0; index < state.raw.length - 1; index += 1) {
|
|
174
|
+
const entry = state.raw[index];
|
|
175
|
+
if (entry?.kind === "message" && entry.role === "assistant") affected.add(index);
|
|
176
|
+
}
|
|
177
|
+
for (const index of affected) {
|
|
178
|
+
const source = state.raw[index];
|
|
179
|
+
const before = state.projected[index];
|
|
180
|
+
if (source === undefined || before === undefined) continue;
|
|
181
|
+
const after = projectWorkingSet([source, synthetic], view)[0] ?? source;
|
|
182
|
+
state.projected[index] = after;
|
|
183
|
+
state.tokens += estimateTokens(after) - estimateTokens(before);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function coldPrefixTokens(state: IncrementalProjection, items: ReadonlyArray<EvictedItem>): number {
|
|
188
|
+
let cut = state.projected.length;
|
|
189
|
+
for (const item of items) {
|
|
190
|
+
const index = state.indexByTurnId.get(item.ref.entry);
|
|
191
|
+
if (index !== undefined && index < cut) cut = index;
|
|
192
|
+
}
|
|
193
|
+
let tokens = 0;
|
|
194
|
+
for (let index = cut; index < state.projected.length; index += 1) {
|
|
195
|
+
const entry = state.projected[index];
|
|
196
|
+
if (entry !== undefined) tokens += estimateTokens(entry);
|
|
197
|
+
}
|
|
198
|
+
return tokens;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** Live plan/fold/project code driven at deterministic ledger turn boundaries. */
|
|
202
|
+
export function replayTrace(trace: Trace, policy: WorkingSetPolicy, config: ReplayConfig): ReplayTraceResult {
|
|
203
|
+
const soFar: SessionEntry[] = [];
|
|
204
|
+
const events: ReplayEvictionEvent[] = [];
|
|
205
|
+
const evictedAtTurn = new Map<string, number>();
|
|
206
|
+
const toolResults = new Set(
|
|
207
|
+
trace.entries
|
|
208
|
+
.filter((entry) => entry.kind === "message" && entry.role === "tool_result")
|
|
209
|
+
.map((entry) => entry.turnId),
|
|
210
|
+
);
|
|
211
|
+
let evictionSequence = 0;
|
|
212
|
+
let summaries = 0;
|
|
213
|
+
let turnIndex = 0;
|
|
214
|
+
let turnsToFirstSummary: number | null = null;
|
|
215
|
+
let lastMessageTurnId: string | null = null;
|
|
216
|
+
let view: WorkingSetView = EMPTY_WORKING_SET_VIEW;
|
|
217
|
+
let visible: IncrementalProjection = { raw: [], projected: [], indexByTurnId: new Map(), tokens: 0 };
|
|
218
|
+
const pressureLimit = config.threshold * config.budgetTokens;
|
|
219
|
+
|
|
220
|
+
for (const entry of trace.entries) {
|
|
221
|
+
if (isTurnStart(entry)) {
|
|
222
|
+
turnIndex += 1;
|
|
223
|
+
const leaf = lastMessageTurnId;
|
|
224
|
+
const tokens = visible.tokens;
|
|
225
|
+
if (tokens > pressureLimit) {
|
|
226
|
+
const input: PolicyInput = {
|
|
227
|
+
entries: visible.raw,
|
|
228
|
+
view,
|
|
229
|
+
cwd: trace.cwd,
|
|
230
|
+
settings: config.settings,
|
|
231
|
+
pressure: {
|
|
232
|
+
tokens,
|
|
233
|
+
contextWindow: config.budgetTokens,
|
|
234
|
+
threshold: config.threshold,
|
|
235
|
+
target: config.target,
|
|
236
|
+
},
|
|
237
|
+
estimateTokens,
|
|
238
|
+
};
|
|
239
|
+
const plan = planEviction(policy, input);
|
|
240
|
+
if (plan !== null) {
|
|
241
|
+
const saturated = eventSaturated(policy, input, plan);
|
|
242
|
+
evictionSequence += 1;
|
|
243
|
+
const previous = soFar[soFar.length - 1];
|
|
244
|
+
const synthetic: ContextEvictionEntry = {
|
|
245
|
+
...buildEvictionFields(plan, {
|
|
246
|
+
trigger: "pressure",
|
|
247
|
+
pressureBefore: tokens / config.budgetTokens,
|
|
248
|
+
snapshotIdBefore: null,
|
|
249
|
+
}),
|
|
250
|
+
turnId: `replay-evict-${evictionSequence}`,
|
|
251
|
+
parentTurnId: leaf,
|
|
252
|
+
timestamp: previous?.timestamp ?? entry.timestamp,
|
|
253
|
+
};
|
|
254
|
+
soFar.push(synthetic);
|
|
255
|
+
appendVisibleEntry(synthetic, visible, view);
|
|
256
|
+
view = foldWorkingSet(soFar, leaf ?? undefined);
|
|
257
|
+
applyEvictionProjection(visible, synthetic, view, plan.items);
|
|
258
|
+
events.push({
|
|
259
|
+
turnIndex,
|
|
260
|
+
items: plan.items,
|
|
261
|
+
tokensBefore: plan.tokensBefore,
|
|
262
|
+
tokensAfter: plan.tokensAfter,
|
|
263
|
+
saturated,
|
|
264
|
+
coldPrefixTokens: coldPrefixTokens(visible, plan.items),
|
|
265
|
+
});
|
|
266
|
+
for (const item of plan.items) {
|
|
267
|
+
if (toolResults.has(item.ref.entry) && !evictedAtTurn.has(item.ref.entry)) {
|
|
268
|
+
evictedAtTurn.set(item.ref.entry, turnIndex);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (visible.tokens > pressureLimit) {
|
|
273
|
+
if (turnsToFirstSummary === null) turnsToFirstSummary = turnIndex;
|
|
274
|
+
const previousCompactionIndex = latestCompactionIndex(soFar);
|
|
275
|
+
const boundaryStart = previousCompactionIndex + 1;
|
|
276
|
+
const cut =
|
|
277
|
+
config.summaries === undefined
|
|
278
|
+
? boundaryStart
|
|
279
|
+
: findCutPoint(soFar, config.summaries.keepRecentTokens, { startIndex: boundaryStart }).firstKeptEntryIndex;
|
|
280
|
+
// A cut at the iterative boundary has no new history or turn prefix,
|
|
281
|
+
// which is the live "nothing to compact" case.
|
|
282
|
+
if (config.summaries !== undefined && cut > boundaryStart) {
|
|
283
|
+
summaries += 1;
|
|
284
|
+
const visibleBefore = new Set(visible.raw.map((candidate) => candidate.turnId));
|
|
285
|
+
const previous = soFar[soFar.length - 1];
|
|
286
|
+
const firstKeptTurnId = soFar[cut]?.turnId ?? "";
|
|
287
|
+
const synthetic: CompactionSummaryEntry = {
|
|
288
|
+
kind: "compactionSummary",
|
|
289
|
+
turnId: `replay-summary-${summaries}`,
|
|
290
|
+
parentTurnId: firstKeptTurnId || null,
|
|
291
|
+
timestamp: previous?.timestamp ?? entry.timestamp,
|
|
292
|
+
summary: "#".repeat(config.summaries.summaryTokens * 4),
|
|
293
|
+
tokensBefore: visible.tokens,
|
|
294
|
+
firstKeptTurnId,
|
|
295
|
+
};
|
|
296
|
+
soFar.push(synthetic);
|
|
297
|
+
visible = rebuildProjection(soFar, lastMessageTurnId, view);
|
|
298
|
+
const visibleAfter = new Set(visible.raw.map((candidate) => candidate.turnId));
|
|
299
|
+
for (const removed of visibleBefore) {
|
|
300
|
+
if (toolResults.has(removed) && !visibleAfter.has(removed) && !evictedAtTurn.has(removed)) {
|
|
301
|
+
evictedAtTurn.set(removed, turnIndex);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
soFar.push(entry);
|
|
309
|
+
if (entry.kind === "compactionSummary") visible = rebuildProjection(soFar, lastMessageTurnId, view);
|
|
310
|
+
else appendVisibleEntry(entry, visible, view);
|
|
311
|
+
if (entry.kind === "message") lastMessageTurnId = entry.turnId;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
traceId: trace.id,
|
|
316
|
+
policyId: config.policyId,
|
|
317
|
+
budgetTokens: config.budgetTokens,
|
|
318
|
+
turnCount: trace.turnCount,
|
|
319
|
+
events,
|
|
320
|
+
evictedAtTurn,
|
|
321
|
+
turnsToFirstSummary,
|
|
322
|
+
summaries,
|
|
323
|
+
entries: soFar,
|
|
324
|
+
};
|
|
325
|
+
}
|