@sema-agent/core 5.59.0 → 5.60.1
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 +61 -0
- package/dist/brain/anthropic.js +15 -5
- package/dist/brain/errors.d.ts +18 -1
- package/dist/brain/errors.js +7 -1
- package/dist/brain/input-too-long.d.ts +57 -0
- package/dist/brain/input-too-long.js +35 -0
- package/dist/brain/stream-engine.js +9 -1
- package/dist/core/auto-compaction.js +2 -2
- package/dist/core/checkpoint-store.d.ts +172 -25
- package/dist/core/checkpoint-store.js +15 -8
- package/dist/core/context-edit.d.ts +243 -41
- package/dist/core/context-edit.js +247 -32
- package/dist/core/governance-codes.d.ts +1 -1
- package/dist/core/governance-codes.js +5 -0
- package/dist/core/locked-config.d.ts +36 -4
- package/dist/core/locked-config.js +34 -1
- package/dist/core/mcp.d.ts +4 -5
- package/dist/core/mcp.js +10 -6
- package/dist/core/memory-engine/content-origin.d.ts +24 -2
- package/dist/core/memory-engine/content-origin.js +6 -1
- package/dist/core/memory-engine/engine.d.ts +5 -6
- package/dist/core/memory.d.ts +10 -0
- package/dist/core/park-selfcheck.js +1 -0
- package/dist/core/permission-rule-consent.js +9 -5
- package/dist/core/runner/prepare-config-doors.d.ts +22 -1
- package/dist/core/runner/prepare-config-doors.js +36 -0
- package/dist/core/runner/prepare-task.d.ts +28 -1
- package/dist/core/runner/prepare-task.js +107 -8
- package/dist/core/runner/runtask.js +97 -9
- package/dist/core/store-contracts/checkpoint-store-contract.js +32 -0
- package/dist/core/tool-policy.d.ts +32 -10
- package/dist/core/tool-policy.js +3 -3
- package/dist/core/tools.js +1 -1
- package/dist/core/trace.d.ts +36 -0
- package/dist/core/types.d.ts +127 -2
- package/dist/core/untrusted-text.d.ts +11 -0
- package/dist/core/untrusted-text.js +1 -0
- package/dist/engine/llm/types.d.ts +21 -2
- package/dist/engine/loop/agent-loop.js +7 -1
- package/dist/engine/loop/types.d.ts +4 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/tools/fs/fs-bash.js +1 -2
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +5 -1
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
2
2
|
import { uuidv7 } from "../internal/harness.js";
|
|
3
|
-
import { inlineUntrusted, sanitizeUntrustedText } from "./untrusted-text.js";
|
|
3
|
+
import { PROBE_CAUSE_PATH_MAX, inlineUntrusted, sanitizeUntrustedText } from "./untrusted-text.js";
|
|
4
|
+
import { carriesBidiControls } from "./tool-policy.js";
|
|
5
|
+
import { renderUntrustedCommandText } from "./permission-rule-model.js";
|
|
4
6
|
import { ASK_USER_QUESTION_TOOL_NAME } from "./ask-question.js";
|
|
5
7
|
export function mintCheckpointToken() {
|
|
6
8
|
return randomBytes(16).toString("hex");
|
|
@@ -9,7 +11,6 @@ export function mintCheckpointId() {
|
|
|
9
11
|
return `cp_${randomUUID()}`;
|
|
10
12
|
}
|
|
11
13
|
export const PROBE_REASON_MAX = 200;
|
|
12
|
-
export const PROBE_CAUSE_PATH_MAX = 200;
|
|
13
14
|
const PROBE_CAUSE_MAX_SHOWN = 8;
|
|
14
15
|
export function normalizeProbeCause(value) {
|
|
15
16
|
if (!isPlainRecord(value))
|
|
@@ -62,6 +63,8 @@ const MAX_TOUCHED_PATHS = 8;
|
|
|
62
63
|
const MAX_DIGEST_KEYS = 16;
|
|
63
64
|
const MAX_DIGEST_SCAN_KEYS = 256;
|
|
64
65
|
export const MAX_TOOL_INPUT_PREVIEW_CHARS = 512;
|
|
66
|
+
const BIDI_BACKFILL_MAX_CHARS = 32_000;
|
|
67
|
+
const BIDI_BACKFILL_MAX_NODES = 512;
|
|
65
68
|
export const MAX_PENDING_STEER_CHARS = 16_000;
|
|
66
69
|
export const PENDING_STEER_QUEUE_BYTE_BUDGET_BYTES = 48_000;
|
|
67
70
|
export const MAX_PENDING_STEER_ENTRIES = Math.floor(PENDING_STEER_QUEUE_BYTE_BUDGET_BYTES / MAX_PENDING_STEER_CHARS);
|
|
@@ -121,7 +124,7 @@ export function buildRiskDescriptor(input) {
|
|
|
121
124
|
if (shell || toolName === "Bash") {
|
|
122
125
|
const cmd = isPlainRecord(args) ? safeDataValue(args, "command") : undefined;
|
|
123
126
|
if (typeof cmd === "string" && cmd.length > 0)
|
|
124
|
-
summary =
|
|
127
|
+
summary = renderUntrustedCommandText(cmd, SUMMARY_CMD_MAX);
|
|
125
128
|
const bg = isPlainRecord(args) ? safeDataValue(args, "run_in_background") : undefined;
|
|
126
129
|
if (bg === true)
|
|
127
130
|
summary = `[background persistent process — no per-step recheck] ${summary ?? ""}`.trimEnd();
|
|
@@ -135,20 +138,20 @@ export function buildRiskDescriptor(input) {
|
|
|
135
138
|
const v = safeDataValue(args, key);
|
|
136
139
|
if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
|
|
137
140
|
const encDigest = (s) => s.replace(/%/g, "%25").replace(/=/g, "%3D").replace(/ /g, "%20");
|
|
138
|
-
const k = encDigest(
|
|
139
|
-
const val = encDigest(
|
|
141
|
+
const k = encDigest(renderUntrustedCommandText(key, 40));
|
|
142
|
+
const val = encDigest(renderUntrustedCommandText(String(v), SUMMARY_VALUE_MAX));
|
|
140
143
|
parts.push(`${k}=${val}`);
|
|
141
144
|
}
|
|
142
145
|
}
|
|
143
146
|
if (parts.length > 0)
|
|
144
|
-
summary =
|
|
147
|
+
summary = renderUntrustedCommandText(parts.join(" "), SUMMARY_TOTAL_MAX);
|
|
145
148
|
}
|
|
146
149
|
}
|
|
147
150
|
const touched = [];
|
|
148
151
|
if (!shell && toolName !== "Bash") {
|
|
149
152
|
const p = fsPathArg(args);
|
|
150
153
|
if (p !== undefined)
|
|
151
|
-
touched.push(
|
|
154
|
+
touched.push(renderUntrustedCommandText(p, PATH_MAX));
|
|
152
155
|
}
|
|
153
156
|
const touchedPaths = touched.length > 0 ? touched.slice(0, MAX_TOUCHED_PATHS) : undefined;
|
|
154
157
|
return {
|
|
@@ -156,7 +159,7 @@ export function buildRiskDescriptor(input) {
|
|
|
156
159
|
axes,
|
|
157
160
|
toolName,
|
|
158
161
|
...(input.shellGated && input.shellGateDoctrine !== undefined ? { shellGateDoctrine: input.shellGateDoctrine } : {}),
|
|
159
|
-
...(input.shadowedRule !== undefined ? { shadowedRule:
|
|
162
|
+
...(input.shadowedRule !== undefined ? { shadowedRule: renderUntrustedCommandText(input.shadowedRule, 200) } : {}),
|
|
160
163
|
...(input.probeReason !== undefined ? { probeReason: inlineUntrusted(input.probeReason, PROBE_REASON_MAX) } : {}),
|
|
161
164
|
...(() => {
|
|
162
165
|
if (input.probeCause === undefined)
|
|
@@ -235,6 +238,9 @@ export function summarizeCheckpoint(cp) {
|
|
|
235
238
|
toolInput = undefined;
|
|
236
239
|
}
|
|
237
240
|
}
|
|
241
|
+
const hasBidi = tool !== undefined &&
|
|
242
|
+
(tool.hasBidiControls === true ||
|
|
243
|
+
carriesBidiControls([toolInput, tool.preview], { maxNodes: BIDI_BACKFILL_MAX_NODES, maxChars: BIDI_BACKFILL_MAX_CHARS }));
|
|
238
244
|
return {
|
|
239
245
|
token: cp.token,
|
|
240
246
|
...(cp.checkpointId !== undefined ? { checkpointId: cp.checkpointId } : {}),
|
|
@@ -254,6 +260,7 @@ export function summarizeCheckpoint(cp) {
|
|
|
254
260
|
? { restoreMode: cp.state.workspaceHandle.restoreMode === "park_only" ? "park_only" : "snapshot" }
|
|
255
261
|
: {}),
|
|
256
262
|
...(tool?.preview !== undefined ? { preview: tool.preview } : {}),
|
|
263
|
+
...(hasBidi ? { hasBidiControls: true } : {}),
|
|
257
264
|
};
|
|
258
265
|
}
|
|
259
266
|
export class CheckpointError extends Error {
|
|
@@ -48,6 +48,53 @@ export declare const COMPACTION_TRIGGER_BUFFER_TOKENS = 13000;
|
|
|
48
48
|
* and the fixed deduction strictly wins from W = 110001 up.
|
|
49
49
|
*/
|
|
50
50
|
export declare function contextEditFrontier(window: number): number;
|
|
51
|
+
/**
|
|
52
|
+
* design/374 — which CLEARING MACHINE `clearStaleToolResults` runs.
|
|
53
|
+
*
|
|
54
|
+
* - `"legacy"` (default): the historical sema machine — keep 3, no minimum-savings gate,
|
|
55
|
+
* incremental oldest-first clearing that STOPS once the budget is met, legacy marker bytes.
|
|
56
|
+
* - `"cc"`: the CC 2.1.223 `yId` machine — keep 5, a ≥20000-token minimum-savings gate (below it
|
|
57
|
+
* the pass is byte-identical no-op), ONE deep clear of everything beyond the keep window, the
|
|
58
|
+
* window base counts already-cleared/offloaded occurrences (they hold their keep slots), and
|
|
59
|
+
* CC's plain marker bytes.
|
|
60
|
+
*
|
|
61
|
+
* ⚠️ P-FORM WARNING (design/374 r2-B⑨, constraint C-5): until the slice-3 fallback re-ordering
|
|
62
|
+
* ships (blocking-point machine re-run → in-turn forced compaction → trim demoted to last resort),
|
|
63
|
+
* selecting `"cc"` here puts the 20k gate IN FRONT of the only reduction between the frontier and
|
|
64
|
+
* the guard trim while the OLD guard-trim backstop still drops messages: a pass that now correctly
|
|
65
|
+
* refuses an under-20k clear hands the request straight to the message-dropping trim — WORSE than
|
|
66
|
+
* the legacy default in that band. Opt in before slice 3 only if you accept that trade (e.g. to
|
|
67
|
+
* exercise the machine); the default flip to `"cc"` ships together with the re-ordered backstops.
|
|
68
|
+
*/
|
|
69
|
+
export type ContextEditMachine = "legacy" | "cc";
|
|
70
|
+
/**
|
|
71
|
+
* How many most-recent CANDIDATE tool results keep their content when the stale-result pass runs —
|
|
72
|
+
* per machine (design/374: the two values govern DIFFERENT machines, so they are separate
|
|
73
|
+
* constants, not one adjudicated number):
|
|
74
|
+
*
|
|
75
|
+
* - LEGACY machine: 3. The measured objection to 5 stands FOR THIS MACHINE (probed on a 200k model,
|
|
76
|
+
* 140k usage anchor, five 36k-char Bash results: at 3 the pass clears two and lands under the
|
|
77
|
+
* 177k guard; at 5 nothing is clearable and the guard trim cannot recover the batch either) —
|
|
78
|
+
* on the legacy machine the keep window is the only lever between the frontier and the
|
|
79
|
+
* message-dropping trim, so widening it disables the whole defense in exactly the terminal-batch
|
|
80
|
+
* shape that needs it.
|
|
81
|
+
* - CC machine: 5 (2.1.223 `uAp`; same value on CC 88's `timeBasedMCConfig` — stable across both
|
|
82
|
+
* corpus generations). The #384 adjudication adopted 5 CONDITIONAL on the whole machine coming
|
|
83
|
+
* with it (20k gate + deep clear + re-ordered backstops); the cc machine is that condition's
|
|
84
|
+
* carrier, and the 200k probe above stops being an objection there (§3.5: the gate correctly
|
|
85
|
+
* refuses the no-win clear and the recovery chain owns the request instead).
|
|
86
|
+
*/
|
|
87
|
+
export declare const DEFAULT_KEEP_RECENT_TOOL_RESULTS = 3;
|
|
88
|
+
export declare const CC_DEFAULT_KEEP_RECENT_TOOL_RESULTS = 5;
|
|
89
|
+
/** design/374 §3.1 — CC's minimum-savings gate (`vUs` :397773): the cc machine refuses to clear at
|
|
90
|
+
* all unless the structural estimate of what WOULD be cleared reaches this many tokens. Rationale
|
|
91
|
+
* (independent of "CC does it"): every clear breaks the provider prefix cache once, so a clear that
|
|
92
|
+
* saves less than the re-prefill it causes is a net loss; the gate prices that trade. The LEGACY
|
|
93
|
+
* machine has no such gate (1 token over budget clears) — that difference is machine-defining. */
|
|
94
|
+
export declare const MIN_CLEAR_SAVINGS_TOKENS = 20000;
|
|
95
|
+
/** Minimum kept results — CC `EUs` :397710 `Math.max(1, keepRecent)`. See
|
|
96
|
+
* {@link ContextEditOptions.keepRecentToolResults} for why both degenerate ends are unusable. */
|
|
97
|
+
export declare const MIN_KEEP_RECENT_TOOL_RESULTS = 1;
|
|
51
98
|
/**
|
|
52
99
|
* checklist #54 (CC microCompact.ts:41-50 `COMPACTABLE_TOOLS` parity): the INCLUSION whitelist of
|
|
53
100
|
* tools whose results may be content-cleared by the stale-result pass. CC's set is exactly
|
|
@@ -58,43 +105,43 @@ export declare function contextEditFrontier(window: number): number;
|
|
|
58
105
|
* PowerShell is kept for CC fidelity even though sema does not mount it. Overridable per call via
|
|
59
106
|
* {@link ContextEditOptions.compactableTools} for custom-tool-heavy embedders.
|
|
60
107
|
*/
|
|
61
|
-
/**
|
|
62
|
-
* How many most-recent CANDIDATE tool results keep their content when the stale-result pass runs.
|
|
63
|
-
*
|
|
64
|
-
* 3, UNCHANGED — and the fact that CC's corresponding number is 5 (2.1.223 `uAp`, and the same value
|
|
65
|
-
* on CC 88's `timeBasedMCConfig`, so it is stable across both corpus generations) is deliberately NOT
|
|
66
|
-
* adopted here. Two reasons, in order of weight:
|
|
67
|
-
*
|
|
68
|
-
* 1. MEASURED: widening the window removes this pass's only lever in the shape where a terminal
|
|
69
|
-
* parallel batch holds exactly as many large results as the window keeps. Probed on a 200k model
|
|
70
|
-
* with a 140k usage anchor and five 36k-char Bash results: at 3 the pass clears two and the
|
|
71
|
-
* request lands at ~167k, UNDER the 177k request guard; at 5 nothing is clearable, the request
|
|
72
|
-
* stays at 185k, and the guard cannot recover it either — `trimToBudget` must turn-align back to
|
|
73
|
-
* the emitting assistant, so the whole batch is retained. The window is count-based on both
|
|
74
|
-
* sides (CC's `slice(-keepRecent)` too), so this ceiling exists at every value; 5 simply widens
|
|
75
|
-
* the band that reaches it.
|
|
76
|
-
* 2. SEAT: the two numbers do not govern the same machine. CC's keep-recent clear runs ONLY on the
|
|
77
|
-
* `context_hint` rejection leg — gated off by default — and behind a hard "saves ≥ 20000 tokens
|
|
78
|
-
* or don't bother" test, with request REFUSAL as the real backstop. Ours is on by default, is
|
|
79
|
-
* the only reduction between the frontier and the guard, and its backstop drops messages instead
|
|
80
|
-
* of refusing. Copying a constant across that difference is the "same name, different question"
|
|
81
|
-
* mistake, not parity.
|
|
82
|
-
*
|
|
83
|
-
* So the VALUE is an open adjudication (recorded with the probe above), while the two things CC
|
|
84
|
-
* unambiguously answers — the floor and the candidate-scoped window — are followed exactly.
|
|
85
|
-
*/
|
|
86
|
-
export declare const DEFAULT_KEEP_RECENT_TOOL_RESULTS = 3;
|
|
87
|
-
/** Minimum kept results — CC `EUs` :397710 `Math.max(1, keepRecent)`. See
|
|
88
|
-
* {@link ContextEditOptions.keepRecentToolResults} for why both degenerate ends are unusable. */
|
|
89
|
-
export declare const MIN_KEEP_RECENT_TOOL_RESULTS = 1;
|
|
90
108
|
export declare const COMPACTABLE_TOOLS: ReadonlySet<string>;
|
|
91
109
|
export interface ContextEditOptions {
|
|
92
110
|
/** Start clearing once estimated context tokens exceed this. */
|
|
93
111
|
budgetTokens: number;
|
|
112
|
+
/**
|
|
113
|
+
* design/374 — which clearing machine runs; see {@link ContextEditMachine} (including its P-form
|
|
114
|
+
* warning about enabling `"cc"` before the slice-3 backstop re-ordering). Default `"legacy"`:
|
|
115
|
+
* the pre-374 machine, byte-identical.
|
|
116
|
+
*/
|
|
117
|
+
machine?: ContextEditMachine;
|
|
118
|
+
/**
|
|
119
|
+
* Mixed-configuration protection (design/374 r2-Md⑪ family): make the LEGACY machine recognize
|
|
120
|
+
* the CC marker forms as already-cleared, so a ledger-replayed CC marker is never re-cleared
|
|
121
|
+
* into a legacy marker (destroying its ref/media disclosure). Wired by the pipeline whenever a
|
|
122
|
+
* cc-minting knob is on (machine "cc" — where it is implied — or the rejection-recovery arm over
|
|
123
|
+
* a legacy frontier machine). Default false: in a pure default deployment tool output that
|
|
124
|
+
* merely SPELLS the CC marker is untrusted bytes and stays clearable exactly as before.
|
|
125
|
+
*/
|
|
126
|
+
recognizeCcMarkers?: boolean;
|
|
127
|
+
/**
|
|
128
|
+
* Fired AT MOST ONCE per pass, only when the pass cleared at least one result: the cleared
|
|
129
|
+
* indexes (into the returned array) and the pass's savings estimate (cc machine: the structural
|
|
130
|
+
* minimum-savings estimate it gated on; legacy machine: the accounted structural delta). The
|
|
131
|
+
* request-projection ledger (design/374 §3.2.6) records cleared occurrences through this seat —
|
|
132
|
+
* observation only, a throwing callback is swallowed and never fails the request build.
|
|
133
|
+
*/
|
|
134
|
+
onCleared?: (pass: {
|
|
135
|
+
clears: ReadonlyArray<{
|
|
136
|
+
index: number;
|
|
137
|
+
}>;
|
|
138
|
+
tokensSavedEstimate: number;
|
|
139
|
+
}) => void;
|
|
94
140
|
/**
|
|
95
141
|
* Always keep the content of this many most-recent CANDIDATE tool results (candidates = results
|
|
96
|
-
* from {@link compactableTools}). Default
|
|
97
|
-
*
|
|
142
|
+
* from {@link compactableTools}). Default: the selected machine's own default —
|
|
143
|
+
* {@link DEFAULT_KEEP_RECENT_TOOL_RESULTS} = 3 (legacy) / {@link CC_DEFAULT_KEEP_RECENT_TOOL_RESULTS}
|
|
144
|
+
* = 5 (cc); see those constants for why the two machines carry different numbers.
|
|
98
145
|
*
|
|
99
146
|
* Floored at 1, which IS CC's rule (`EUs` `Math.max(1, keepRecent)`), whose own source note gives
|
|
100
147
|
* the reason: 0 leaves the model with zero working tool context, and in CC's `slice(-0)` spelling
|
|
@@ -139,16 +186,20 @@ export interface ContextEditOptions {
|
|
|
139
186
|
* registry state are compaction's job, not a blanking marker's.
|
|
140
187
|
*/
|
|
141
188
|
compactableTools?: ReadonlySet<string>;
|
|
189
|
+
/**
|
|
190
|
+
* design/374 (RB-212 durable half): the PRE-CAP counterpart array of `messages`, position-aligned
|
|
191
|
+
* (the pipeline's aggregate caps replace in place, never drop). When present, the CC machine
|
|
192
|
+
* composes each clear's marker + offload text from `clearSource[idx]` — the same occurrence
|
|
193
|
+
* bytes the cleared-projection ledger repaints the marker onto at the next session rebuild — so
|
|
194
|
+
* the durable marker discloses the occurrence's REAL media and the page-back ref resolves to its
|
|
195
|
+
* real text rather than to a cap-stripped copy. Ignored by the legacy machine (its clears are
|
|
196
|
+
* request-local and correctly describe the projection they were handed — default bytes do not
|
|
197
|
+
* move). An entry that is not a tool result, or that carries a different `toolCallId` than the
|
|
198
|
+
* target, is ignored per index (composition falls back to the projection copy — a misaligned
|
|
199
|
+
* source must never mis-attribute content).
|
|
200
|
+
*/
|
|
201
|
+
clearSource?: ReadonlyArray<AgentMessage>;
|
|
142
202
|
}
|
|
143
|
-
/**
|
|
144
|
-
* Context-editing (the lightest, safest compaction; Anthropic reports ~29% improvement alone):
|
|
145
|
-
* when context exceeds the budget, replace the CONTENT of older tool-result messages with a short
|
|
146
|
-
* marker, keeping the turn structure (tool_call/tool_result pairing) intact. The most recent
|
|
147
|
-
* `keepRecentToolResults` results are preserved.
|
|
148
|
-
*
|
|
149
|
-
* Request-only and non-destructive: returns a new array; the durable session keeps the full results.
|
|
150
|
-
* Older results are cleared first, stopping as soon as the budget is met.
|
|
151
|
-
*/
|
|
152
203
|
/**
|
|
153
204
|
* audit A-1 (view-level heal for HISTORICAL pollution): drop synthetic EMPTY failure assistants —
|
|
154
205
|
* stopReason aborted/error with no tool calls and no non-whitespace text/thinking. Sessions persisted
|
|
@@ -159,10 +210,161 @@ export interface ContextEditOptions {
|
|
|
159
210
|
* New sessions never contain these (the loop exits cleanly on abort; the harness skips persisting them).
|
|
160
211
|
*/
|
|
161
212
|
export declare function dropEmptyFailureAssistants(messages: AgentMessage[]): AgentMessage[];
|
|
213
|
+
/**
|
|
214
|
+
* Context-editing — the lightest, safest reduction: when context exceeds the budget, replace the
|
|
215
|
+
* CONTENT of older tool-result messages with a short marker, keeping the turn structure
|
|
216
|
+
* (tool_call/tool_result pairing) intact; the most recent `keepRecentToolResults` CANDIDATE
|
|
217
|
+
* results keep their content. Request-only and non-destructive: returns a new array, the durable
|
|
218
|
+
* session keeps the full results (the request-projection ledger, not this function, is what makes
|
|
219
|
+
* a clear DECISION durable across requests — see `replayClearedProjection`).
|
|
220
|
+
*
|
|
221
|
+
* ANCHOR (design/374 片4 re-anchoring): this proactive frontier hook is the client-side form of
|
|
222
|
+
* Anthropic's PLATFORM context-management policy (`clear_tool_uses_20250919` — same
|
|
223
|
+
* trigger/keep/clear-at-least construction), NOT a CC-CLI parity claim: CC 2.1.x's client
|
|
224
|
+
* keep-recent clear lives exclusively on its `context_hint` REJECTION leg (default-gated off), a
|
|
225
|
+
* machine whose sema form is the MC-R arm (`planRejectionClears` + the runner's recovery chain).
|
|
226
|
+
* The per-mechanism CC anchors that DO hold (the COMPACTABLE_TOOLS set, the candidate-scoped keep
|
|
227
|
+
* window, the floor, and the whole `machine: "cc"` clearing form) are cited at their sites.
|
|
228
|
+
* (An earlier note here cited an unverified "~29% improvement" figure — removed: no first-party
|
|
229
|
+
* source in the corpus; the machine's case rests on the measured mechanics, not that number.)
|
|
230
|
+
*
|
|
231
|
+
* Clear ORDER is machine-dependent: legacy clears oldest-first and stops at the budget; the cc
|
|
232
|
+
* machine deep-clears everything beyond the keep window behind the ≥20k gate (see
|
|
233
|
+
* {@link ContextEditMachine}).
|
|
234
|
+
*/
|
|
162
235
|
export declare function clearStaleToolResults(messages: AgentMessage[], opts: ContextEditOptions): AgentMessage[];
|
|
236
|
+
/** design/374 slice 1b (X5) — the resolved trigger-side window and whether the clamp repaired it. */
|
|
237
|
+
export interface TriggerWindowResolution {
|
|
238
|
+
/** The effective trigger-side window: `autoCompactTokens ?? contextTokens ?? contextWindow`,
|
|
239
|
+
* CLAMPED to the physical window when the declared autocompact window exceeds it. */
|
|
240
|
+
window: number;
|
|
241
|
+
/** True = the declared `autoCompactTokens` was ABOVE the physical window and got clamped down —
|
|
242
|
+
* a bad deployment value the caller should announce loudly (坏值响亮度默认律). */
|
|
243
|
+
clamped: boolean;
|
|
244
|
+
/** Present on the clamped arm: the declared (refused) value and the physical window it was
|
|
245
|
+
* clamped to, for the announcement text. */
|
|
246
|
+
declaredAutoCompactTokens?: number;
|
|
247
|
+
physicalWindow?: number;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* design/374 slice 1b (X5, r2-M⑤) — SINGLE SOURCE for the trigger-side window chain
|
|
251
|
+
* (`autoCompactTokens ?? contextTokens ?? contextWindow`), now with the inversion clamp:
|
|
252
|
+
* `autoCompactTokens` is a TRIGGER-side lowering knob (dual-window semantics — the guard trim and
|
|
253
|
+
* physical request budgeting stay on `contextTokens ?? contextWindow`), so a declared value ABOVE
|
|
254
|
+
* the physical window inverts the whole geometry: the frontier (W−33000 of the declared window)
|
|
255
|
+
* lands HIGHER than the guard (physical−23000), every "the frontier machine runs first, the guard
|
|
256
|
+
* only backstops" argument silently reverses (967k declared vs 177k physical is the recorded
|
|
257
|
+
* counterexample), and the misconfiguration announces nothing. Clamp = `min(declared, physical)`,
|
|
258
|
+
* reported via `clamped` so the ONE announce point (prepare) can be loud about it. Every consumer
|
|
259
|
+
* of the chain (editBudget here / maybeCompact's window derivation / the compaction-window-hazard
|
|
260
|
+
* estimate / runtask's prefix-overhead disclosure) reads THIS helper — a fifth copy of the `??`
|
|
261
|
+
* chain is the drift this function exists to end. Non-finite/≤0 declared values keep their historic
|
|
262
|
+
* meaning (each consumer's own unknown-window sentinel), deliberately untouched here.
|
|
263
|
+
*/
|
|
264
|
+
export declare function resolveTriggerWindow(model: Model): TriggerWindowResolution;
|
|
163
265
|
/** The token budget at which context-editing begins for a model (below the harder guard trim).
|
|
164
266
|
* = {@link contextEditFrontier} of the model's AUTOCOMPACT window (CC-parity fixed deduction with
|
|
165
267
|
* a 0.7 small-window floor) — the same point the compaction threshold is clamped to. Dual-window
|
|
166
268
|
* semantics: `autoCompactTokens` (when declared) feeds this trigger-side geometry only; the guard
|
|
167
|
-
* trim (`guardBudget`) stays on the physical window
|
|
269
|
+
* trim (`guardBudget`) stays on the physical window — which is why the window is read through
|
|
270
|
+
* {@link resolveTriggerWindow}: an autocompact window declared ABOVE the physical one would put
|
|
271
|
+
* this frontier above the guard (inverted geometry), so it is clamped there (slice 1b, X5). */
|
|
168
272
|
export declare function editBudget(model: Model): number;
|
|
273
|
+
/** Content fingerprint of an occurrence — a sha-256 digest over the FULL canonical content form
|
|
274
|
+
* (per block: type + complete text bytes, and for non-text blocks the mime type + payload length
|
|
275
|
+
* + a payload head), replay's mis-paint guard (adversarial rounds 1+2): ordinal mapping alone
|
|
276
|
+
* cannot see an equal-size cut-and-append churn inside a same-(id,timestamp) group — the mapped
|
|
277
|
+
* slot may hold a DIFFERENT occurrence, and painting it would clear content that was never
|
|
278
|
+
* cleared. r2 rejected the first cut's {chars, head-64} pair (same-prefix/same-length tails and
|
|
279
|
+
* media-only churn collide); the digest covers whole text bytes and block structure. A non-marker
|
|
280
|
+
* replay target must match or the entry prunes (full text returns — always the safe direction).
|
|
281
|
+
* Content-identical occurrences remain indistinguishable by construction and paint harmlessly. */
|
|
282
|
+
export type OccurrenceFingerprint = string;
|
|
283
|
+
export declare function fingerprintToolResultOccurrence(m: AgentMessage): OccurrenceFingerprint;
|
|
284
|
+
/** One recorded clear: the exact marker bytes to re-apply, the occurrence-group size at record
|
|
285
|
+
* time (the shrink-mapping input — see the lifecycle note above), and the original content's
|
|
286
|
+
* {@link OccurrenceFingerprint} (the mis-paint guard). */
|
|
287
|
+
export interface ClearedOccurrence {
|
|
288
|
+
marker: string;
|
|
289
|
+
groupCount: number;
|
|
290
|
+
fp: OccurrenceFingerprint;
|
|
291
|
+
}
|
|
292
|
+
/** The per-task ledger. Keys are OPAQUE occurrence coordinates minted by
|
|
293
|
+
* {@link replayClearedProjection} / {@link OccurrenceIndex} — derived per view, never persisted. */
|
|
294
|
+
export interface ClearedProjectionLedger {
|
|
295
|
+
entries: Map<string, ClearedOccurrence>;
|
|
296
|
+
}
|
|
297
|
+
export declare function createClearedProjectionLedger(): ClearedProjectionLedger;
|
|
298
|
+
/** One minted occurrence coordinate: the opaque ledger key, the group size at mint, the
|
|
299
|
+
* INDEXED-view fingerprint, and (when the index can hand it back) the indexed-view occurrence
|
|
300
|
+
* MESSAGE itself — the durable-composition seat (RB-212 durable half): a ledger-bound clear
|
|
301
|
+
* composes its marker + offload text from this occurrence, never from a cap-transformed copy, so
|
|
302
|
+
* the recorded marker discloses the occurrence's real media and the page-back ref resolves to its
|
|
303
|
+
* real text (the same bytes the replay repaints the marker onto). */
|
|
304
|
+
export interface OccurrenceKey {
|
|
305
|
+
key: string;
|
|
306
|
+
groupCount: number;
|
|
307
|
+
fp: OccurrenceFingerprint;
|
|
308
|
+
occurrence?: AgentMessage;
|
|
309
|
+
}
|
|
310
|
+
/** Occurrence coordinates for ONE request view (minted fresh per view — see the ledger note). */
|
|
311
|
+
export interface OccurrenceIndex {
|
|
312
|
+
/** Key the tool-result occurrence at this index of the indexed array. `fp` is the occurrence's
|
|
313
|
+
* fingerprint IN THE INDEXED (pre-cap) view — the same bytes the next session rebuild presents,
|
|
314
|
+
* which is what the replay guard compares against (r2: fingerprinting the post-cap projection
|
|
315
|
+
* instead made an MC-R clear of a cap-transformed result prune ITSELF on the retry build — an
|
|
316
|
+
* advertised clear that never reached the wire). */
|
|
317
|
+
keyAt(index: number): OccurrenceKey | undefined;
|
|
318
|
+
/** Key a tool-result by OBJECT identity against the indexed view (the MC-R seat: the rejected
|
|
319
|
+
* projection reuses the view's message objects for everything the caps did not replace); falls
|
|
320
|
+
* back to the unambiguous single-member (id, timestamp) group for cap-replaced copies, and
|
|
321
|
+
* answers undefined — "not clearable via the ledger" — for anything ambiguous. The returned
|
|
322
|
+
* `fp` (and `occurrence`) are ALWAYS of the indexed-view occurrence (never of a cap-replaced
|
|
323
|
+
* copy the caller holds). */
|
|
324
|
+
keyOf(m: AgentMessage): OccurrenceKey | undefined;
|
|
325
|
+
}
|
|
326
|
+
/** The MC-R planner's verdict — either the entries to record (with the pass's gated savings) or a
|
|
327
|
+
* typed decline the caller reports on the `context.mc_null` observation face. */
|
|
328
|
+
export type RejectionClearPlan = {
|
|
329
|
+
cleared: Array<{
|
|
330
|
+
key: string;
|
|
331
|
+
groupCount: number;
|
|
332
|
+
marker: string;
|
|
333
|
+
fp: OccurrenceFingerprint;
|
|
334
|
+
}>;
|
|
335
|
+
tokensSavedEstimate: number;
|
|
336
|
+
} | {
|
|
337
|
+
declined: "no_candidates" | "below_min_savings";
|
|
338
|
+
};
|
|
339
|
+
/**
|
|
340
|
+
* design/374 §3.2 (slice 2) — plan the MC-R rejection clear over the REJECTED projection: the same
|
|
341
|
+
* cc machine (keep 5 default, window base counts cleared occurrences, five-form exclusion, Sby
|
|
342
|
+
* savings, ≥20k gate) with the TRIGGER replaced by the rejection itself — the provider already said
|
|
343
|
+
* the input does not fit, so there is no budget coordinate to compare against (which is also why an
|
|
344
|
+
* enabled MC-R always runs the cc form regardless of the frontier machine knob: the legacy
|
|
345
|
+
* incremental form is defined by "stop when the budget is met" and has no meaning here).
|
|
346
|
+
* `keyOf` threads occurrence identity (the ledger is the ONLY way a rejection-arm clear survives
|
|
347
|
+
* the session rebuild — §1.2's adoption chain discards the in-memory view); a candidate the index
|
|
348
|
+
* cannot key is skipped from BOTH the clear set and the savings (never promise savings the ledger
|
|
349
|
+
* cannot deliver). Marker composition (offload persist included) runs only on the accept path —
|
|
350
|
+
* a declined plan has zero side effects.
|
|
351
|
+
*/
|
|
352
|
+
export declare function planRejectionClears(messages: AgentMessage[], opts: {
|
|
353
|
+
keyOf: OccurrenceIndex["keyOf"];
|
|
354
|
+
offload?: ContextEditOptions["offload"];
|
|
355
|
+
compactableTools?: ReadonlySet<string>;
|
|
356
|
+
keepRecentToolResults?: number;
|
|
357
|
+
}): RejectionClearPlan;
|
|
358
|
+
/**
|
|
359
|
+
* Replay the ledger onto a freshly rebuilt request view (design/374 §3.2.6, r2-M⑩: positioned
|
|
360
|
+
* AFTER the heal, BEFORE the aggregate caps — so every estimator, cap and trigger downstream
|
|
361
|
+
* accounts the REPLAYED view, and a replayed marker never re-enters the cap store or re-emits a
|
|
362
|
+
* cap frame). Also the ledger's intersection point: entries that no longer resolve are pruned, and
|
|
363
|
+
* surviving entries are RE-KEYED to the current view's coordinates. Returns the SAME array instance
|
|
364
|
+
* when nothing changed (reference inequality is the pipeline's lossy-change signal), plus the
|
|
365
|
+
* view's occurrence index for the record/MC-R seats.
|
|
366
|
+
*/
|
|
367
|
+
export declare function replayClearedProjection(messages: AgentMessage[], ledger: ClearedProjectionLedger): {
|
|
368
|
+
messages: AgentMessage[];
|
|
369
|
+
index: OccurrenceIndex;
|
|
370
|
+
};
|