pi-fold 1.0.4 → 3.0.0
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/README.md +75 -28
- package/extensions/active-context.ts +1213 -2756
- package/extensions/evidence.js +0 -10
- package/extensions/index.js +25 -32
- package/extensions/json.ts +3 -29
- package/extensions/lib/canonical.ts +0 -8
- package/extensions/lib/curation.ts +52 -313
- package/extensions/lib/editor-ui.ts +739 -0
- package/extensions/lib/folding.ts +57 -446
- package/extensions/lib/instrumentation.ts +13 -127
- package/extensions/lib/measurement.ts +6 -83
- package/extensions/lib/persistence.ts +272 -229
- package/extensions/lib/policy.ts +165 -612
- package/extensions/lib/rollback.ts +0 -53
- package/extensions/lib/scheduling.ts +352 -450
- package/extensions/lib/selection.ts +212 -390
- package/extensions/lib/tool-surface.ts +29 -46
- package/extensions/lib/transcript.ts +47 -166
- package/extensions/settings.ts +481 -0
- package/package.json +4 -1
- package/extensions/lib/surfacing.ts +0 -604
- package/extensions/summarizer.js +0 -291
|
@@ -28,7 +28,6 @@ import {
|
|
|
28
28
|
orderedRoots,
|
|
29
29
|
refsInOrder,
|
|
30
30
|
refsProtected,
|
|
31
|
-
toolRefsProtected,
|
|
32
31
|
} from "./measurement.ts";
|
|
33
32
|
import {
|
|
34
33
|
childFoldIds,
|
|
@@ -53,8 +52,7 @@ import {
|
|
|
53
52
|
MAX_FOLD_SPAN_CHARS,
|
|
54
53
|
PEEK_DEFAULT_MAX_BYTES,
|
|
55
54
|
PEEK_HEAD_SHARE,
|
|
56
|
-
|
|
57
|
-
zoneBytes,
|
|
55
|
+
STATUS_DIET_INDEX_ROWS,
|
|
58
56
|
} from "./policy.ts";
|
|
59
57
|
import type {
|
|
60
58
|
ActiveContextSnapshot,
|
|
@@ -85,49 +83,29 @@ import {
|
|
|
85
83
|
export function selectAutomaticChapter(
|
|
86
84
|
snapshot: ActiveContextSnapshot,
|
|
87
85
|
state: ActiveContextState,
|
|
88
|
-
maximumSourceRefs: number = snapshot.policy.maxFoldSourceRefs,
|
|
89
86
|
claimed: ReadonlySet<string> = new Set<string>(),
|
|
90
87
|
): FoldCandidate | null {
|
|
91
|
-
// Every structurally closed unit is a candidate. What keeps a chapter off the newest
|
|
92
|
-
// material is `refsProtected` below -- the fresh byte tail and the agent's pins -- and
|
|
93
|
-
// what keeps it off the running excursion's own evidence is the guard adjudicated at
|
|
94
|
-
// the commit. There is no position filter here: a unit is composable or it is not.
|
|
95
88
|
const units = chapterUnits(snapshot);
|
|
96
|
-
// An automatic chapter is raw material, always. Letting a chapter swallow placeholders
|
|
97
|
-
// once the root count reached `consolidateAfter` was the old approximation of
|
|
98
|
-
// consolidation, and the count law replaces it outright: a placeholder gets a parent
|
|
99
|
-
// when the count says it must, under a fold that says what it is, not as a side effect
|
|
100
|
-
// of which chapter happened to be composable that pass (Shane 2026-08-10).
|
|
101
89
|
const allowedChildren = NO_FOLD_KINDS;
|
|
102
90
|
for (let unitIndex = 0; unitIndex < units.length; unitIndex += 1) {
|
|
103
91
|
const first = units[unitIndex];
|
|
104
92
|
let best: FoldCandidate | null = null;
|
|
105
|
-
const turnStarts = new Set<number>();
|
|
106
93
|
for (let endIndex = unitIndex; endIndex < units.length; endIndex += 1) {
|
|
107
94
|
const unit = units[endIndex];
|
|
108
95
|
if (endIndex > unitIndex && unit.start !== units[endIndex - 1].end) break;
|
|
109
|
-
turnStarts.add(unit.turnStart);
|
|
110
|
-
if (turnStarts.size > snapshot.policy.maxChapterTurns) break;
|
|
111
96
|
const coherentSegment = endIndex > unitIndex || first.end - first.start > 1;
|
|
112
97
|
if (!coherentSegment) continue;
|
|
113
98
|
const parts = partsForRange(snapshot, state, first.start, unit.end - 1, allowedChildren);
|
|
114
99
|
if (!parts || parts.some((part) => part.kind === "fold" && state.expanded.includes(part.foldId))) continue;
|
|
115
100
|
const refs = candidateSourceRefs(parts, state);
|
|
116
|
-
if (refs.length > maximumSourceRefs) break;
|
|
117
101
|
if (claimed.size && refs.some((ref) => claimed.has(objectRefKey(ref)))) continue;
|
|
118
102
|
if (refsProtected(refs, state, snapshot)) continue;
|
|
119
103
|
const size = bytes(encodedFoldSource(snapshot, state, parts, "chapter"));
|
|
120
|
-
// The chapter cap bounds what the rung COMPOSES, never what it may reclaim: a
|
|
121
|
-
// single closed unit that alone exceeds it is still accepted (rep 19: six status
|
|
122
|
-
// results, each above the cap, were exactly the mass nothing could fold).
|
|
123
|
-
if (size > snapshot.policy.maxChapterChars && endIndex > unitIndex) break;
|
|
124
|
-
// Bite-sized by construction. A multi-unit chapter never grows past the cap, so
|
|
125
|
-
// the ladder cannot build the 60kB fold whose tail hid the answer (rep 6); a
|
|
126
|
-
// SINGLE unit that alone exceeds it is still accepted, because there is no
|
|
127
|
-
// interior boundary to stop at and refusing would leave the biggest span in the
|
|
128
|
-
// window as the one thing nothing may reclaim.
|
|
129
104
|
const biteSized = size <= MAX_FOLD_SPAN_CHARS || endIndex === unitIndex;
|
|
130
|
-
|
|
105
|
+
// ACCUMULATE UNTIL OVER, never cut to hit the number. The walk grows the span one
|
|
106
|
+
// whole unit at a time and records the first one that carries it past the floor,
|
|
107
|
+
// so a fold is always at least minFoldChars and always ends on a message boundary.
|
|
108
|
+
if (size >= snapshot.thresholds.minFoldChars && biteSized) best = { kind: "chapter", parts, sourceRefs: refs };
|
|
131
109
|
if (size > MAX_FOLD_SPAN_CHARS) break;
|
|
132
110
|
}
|
|
133
111
|
if (best) return best;
|
|
@@ -135,26 +113,12 @@ export function selectAutomaticChapter(
|
|
|
135
113
|
return null;
|
|
136
114
|
}
|
|
137
115
|
|
|
138
|
-
/**
|
|
139
|
-
* ONE automatic selection law for stale material.
|
|
140
|
-
*
|
|
141
|
-
* There is no chapter rung and no consolidation rung any more, each with its own
|
|
142
|
-
* trigger: automation proposes the stalest eligible SPAN, and what the span contains
|
|
143
|
-
* decides the fold's kind. Raw narrative folds as a chapter; a parent the root count
|
|
144
|
-
* requires folds as a consolidation. Nothing here is pressure-scaled: the epoch's own
|
|
145
|
-
* commit trigger decides WHEN, and this decides WHAT.
|
|
146
|
-
*
|
|
147
|
-
* One candidate, because that is what a single inline rung can apply. The count law
|
|
148
|
-
* itself is not rationed this way: the commit epoch takes EVERY group the count requires
|
|
149
|
-
* in one pass, and this is the leftover route by which the stalest of them can also ride
|
|
150
|
-
* an epoch's own rewrite.
|
|
151
|
-
*/
|
|
152
116
|
export function selectAutomaticStaleSpan(
|
|
153
117
|
snapshot: ActiveContextSnapshot,
|
|
154
118
|
state: ActiveContextState,
|
|
155
119
|
claimed: ReadonlySet<string> = new Set<string>(),
|
|
156
120
|
): FoldCandidate | null {
|
|
157
|
-
const chapter = selectAutomaticChapter(snapshot, state,
|
|
121
|
+
const chapter = selectAutomaticChapter(snapshot, state, claimed);
|
|
158
122
|
const folds = selectAutomaticConsolidations(snapshot, state)[0] ?? null;
|
|
159
123
|
if (!chapter || !folds) return chapter ?? folds;
|
|
160
124
|
const at = (candidate: FoldCandidate): number =>
|
|
@@ -162,10 +126,6 @@ export function selectAutomaticStaleSpan(
|
|
|
162
126
|
return at(folds) < at(chapter) ? folds : chapter;
|
|
163
127
|
}
|
|
164
128
|
|
|
165
|
-
/**
|
|
166
|
-
* What the commit epoch proposes: a completed read-only tool batch if one is stale, and
|
|
167
|
-
* otherwise the stalest span the one law composes. This is the whole automatic path.
|
|
168
|
-
*/
|
|
169
129
|
export function selectAutomaticSpan(
|
|
170
130
|
snapshot: ActiveContextSnapshot,
|
|
171
131
|
state: ActiveContextState,
|
|
@@ -185,14 +145,6 @@ export type AutomaticRungSelection =
|
|
|
185
145
|
|
|
186
146
|
export interface AutomaticRungSelectionOptions {
|
|
187
147
|
toolOnly?: boolean;
|
|
188
|
-
summarizerAvailable?: boolean;
|
|
189
|
-
failedPreparationIds?: ReadonlySet<string>;
|
|
190
|
-
/**
|
|
191
|
-
* Evidence and folds already spoken for by something that is not yet a fold, i.e.
|
|
192
|
-
* a pending epoch mark. Excluding them makes each eligible turn select NEW stale
|
|
193
|
-
* content instead of re-selecting the batch a mark already covers, which is the
|
|
194
|
-
* only way marks accumulate below the commit threshold.
|
|
195
|
-
*/
|
|
196
148
|
claimed?: ReadonlySet<string>;
|
|
197
149
|
claimedFoldIds?: ReadonlySet<string>;
|
|
198
150
|
}
|
|
@@ -216,7 +168,7 @@ export function selectAutomaticRung(
|
|
|
216
168
|
const claimedFoldIds = options.claimedFoldIds ?? new Set<string>();
|
|
217
169
|
if (!options.toolOnly && state.prepared) {
|
|
218
170
|
if (!Number.isFinite(ratio) || ratio < hardFenceRatio(snapshot)) return null;
|
|
219
|
-
const candidate = selectAutomaticChapter(snapshot, state,
|
|
171
|
+
const candidate = selectAutomaticChapter(snapshot, state, claimed);
|
|
220
172
|
return candidate ? { kind: "prepared-chapter", candidate } : null;
|
|
221
173
|
}
|
|
222
174
|
const tool = selectAutomaticToolBatch(snapshot, state, claimed)[0] ?? null;
|
|
@@ -224,24 +176,12 @@ export function selectAutomaticRung(
|
|
|
224
176
|
if (options.toolOnly || !Number.isFinite(ratio)) return null;
|
|
225
177
|
const refold = selectAutomaticRefold(snapshot, state, claimedFoldIds);
|
|
226
178
|
if (refold) return { kind: "refold", foldId: refold };
|
|
227
|
-
// One law decides the span; only the CHAPTER kind still has a preparation path in
|
|
228
|
-
// front of it, because only a chapter brief is worth a model call.
|
|
229
179
|
const span = selectAutomaticStaleSpan(snapshot, state, claimed);
|
|
230
180
|
if (span?.kind === "consolidation") return { kind: "consolidation", candidate: span };
|
|
231
181
|
const chapter = span;
|
|
232
182
|
if (!chapter) return null;
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
) ?? false;
|
|
236
|
-
if (ratio >= hardFenceRatio(snapshot)) {
|
|
237
|
-
return !options.summarizerAvailable || preparationFailed
|
|
238
|
-
? { kind: "chapter", candidate: chapter }
|
|
239
|
-
: { kind: "chapter-prepare", candidate: chapter };
|
|
240
|
-
}
|
|
241
|
-
if ((options.summarizerAvailable && ratio >= snapshot.policy.warmRatio) ||
|
|
242
|
-
((!options.summarizerAvailable || preparationFailed) && ratio >= snapshot.policy.prepareRatio)) {
|
|
243
|
-
return { kind: "chapter-prepare", candidate: chapter };
|
|
244
|
-
}
|
|
183
|
+
if (ratio >= hardFenceRatio(snapshot)) return { kind: "chapter", candidate: chapter };
|
|
184
|
+
if (ratio >= snapshot.policy.prepareRatio) return { kind: "chapter-prepare", candidate: chapter };
|
|
245
185
|
return null;
|
|
246
186
|
}
|
|
247
187
|
|
|
@@ -250,12 +190,10 @@ export function foldCandidatesDetail(
|
|
|
250
190
|
state: ActiveContextState,
|
|
251
191
|
ratio: number | null,
|
|
252
192
|
options: {
|
|
253
|
-
summarizerAvailable?: boolean;
|
|
254
193
|
generation?: number;
|
|
255
194
|
measurementFresh?: boolean;
|
|
256
195
|
automaticFailure?: boolean;
|
|
257
196
|
preparing?: boolean;
|
|
258
|
-
failedPreparationIds?: ReadonlySet<string>;
|
|
259
197
|
} = {},
|
|
260
198
|
): Record<string, unknown> {
|
|
261
199
|
const measuredRatio = ratio !== null && Number.isFinite(ratio) ? ratio : Number.NaN;
|
|
@@ -264,10 +202,7 @@ export function foldCandidatesDetail(
|
|
|
264
202
|
const staleSpan = selectAutomaticStaleSpan(snapshot, state);
|
|
265
203
|
const consolidation = staleSpan?.kind === "consolidation" ? staleSpan : null;
|
|
266
204
|
const chapter = staleSpan?.kind === "chapter" ? staleSpan : null;
|
|
267
|
-
const selection = selectAutomaticRung(snapshot, state, measuredRatio
|
|
268
|
-
summarizerAvailable: options.summarizerAvailable,
|
|
269
|
-
failedPreparationIds: options.failedPreparationIds,
|
|
270
|
-
});
|
|
205
|
+
const selection = selectAutomaticRung(snapshot, state, measuredRatio);
|
|
271
206
|
let wouldFireNow: string | null = null;
|
|
272
207
|
let blockedBy: string | null = null;
|
|
273
208
|
if (options.measurementFresh === false) blockedBy = "measurement-stale";
|
|
@@ -296,9 +231,6 @@ export function foldCandidatesDetail(
|
|
|
296
231
|
} : null,
|
|
297
232
|
wouldFireNow,
|
|
298
233
|
blockedBy,
|
|
299
|
-
// The one position that still cuts anything, plus the count law read as the
|
|
300
|
-
// arithmetic it is: the roots it counts, the width it divides by, and how many
|
|
301
|
-
// parents that count owes right now.
|
|
302
234
|
zones: {
|
|
303
235
|
freshBoundary: snapshot.freshBoundary,
|
|
304
236
|
budgetTokens: snapshot.budgetTokens,
|
|
@@ -369,25 +301,6 @@ export function encodedFoldSource(
|
|
|
369
301
|
}));
|
|
370
302
|
}
|
|
371
303
|
|
|
372
|
-
/**
|
|
373
|
-
* What a generator reads to brief a PARENT: its children at depth one.
|
|
374
|
-
*
|
|
375
|
-
* `encodedFoldSource` renders a parent's children through `renderFold`, which yields their
|
|
376
|
-
* placeholders, so a parent's source was the children's briefs. Briefing that produces a
|
|
377
|
-
* summary of summaries, and by the second rung the material itself is three descriptions
|
|
378
|
-
* away. So each child is opened here instead: its own brief for orientation, then its
|
|
379
|
-
* contents, with ITS children left as placeholders. Depth one is the whole rule. It bounds
|
|
380
|
-
* the read at one level per rung whatever the ladder's height, and it is the same view an
|
|
381
|
-
* agent gets from expanding the parent, so a brief written from it describes what expanding
|
|
382
|
-
* would actually return.
|
|
383
|
-
*
|
|
384
|
-
* When the opened children do not fit, the LARGEST collapses back to brief-only first, and
|
|
385
|
-
* again until the rest fit. Size is what decides, not position: dropping in span order
|
|
386
|
-
* would spend the whole budget on an early giant and close every child behind it, while
|
|
387
|
-
* taking the giant out first buys back the most room for the fewest subjects lost. A
|
|
388
|
-
* collapsed child still appears, in span order, carrying its brief and saying it is
|
|
389
|
-
* collapsed, so the generator can tell a child it read from a child it only heard about.
|
|
390
|
-
*/
|
|
391
304
|
export function consolidationSourceText(
|
|
392
305
|
snapshot: ActiveContextSnapshot,
|
|
393
306
|
state: ActiveContextState,
|
|
@@ -417,8 +330,6 @@ export function consolidationSourceText(
|
|
|
417
330
|
? { collapsed: "brief only: this child did not fit expanded" }
|
|
418
331
|
: { contents: entry.messages }),
|
|
419
332
|
})));
|
|
420
|
-
// Largest first, and only as many as the budget forces: each collapse is a subject the
|
|
421
|
-
// brief must describe from one sentence instead of from its contents.
|
|
422
333
|
const bySize = entries
|
|
423
334
|
.filter((entry) => entry.foldId !== null)
|
|
424
335
|
.map((entry) => ({ index: entry.index, size: bytes(entry.messages) }))
|
|
@@ -459,17 +370,6 @@ export function boundedOrientation(
|
|
|
459
370
|
return { beforeRefs: before, beforeText, afterRefs: after.refs, afterText: after.text };
|
|
460
371
|
}
|
|
461
372
|
|
|
462
|
-
/**
|
|
463
|
-
* What is wrong with a generated brief, said in words its author can act on, or null.
|
|
464
|
-
*
|
|
465
|
-
* Length is a criterion we set, so a brief that misses it is answered rather than cut.
|
|
466
|
-
* Truncating a long brief silently loses whatever the generator put last, which on a group
|
|
467
|
-
* is the last children it covered; the cure is to hand the complaint back and let the
|
|
468
|
-
* generator spend the budget itself. Coverage cannot be checked the same way: no test tells
|
|
469
|
-
* whether a sentence stands for a child, so coverage is instructed in the request and
|
|
470
|
-
* defended by the deterministic index underneath, and what is checked here is the pair of
|
|
471
|
-
* failures that were silent before.
|
|
472
|
-
*/
|
|
473
373
|
export function briefContractComplaint(
|
|
474
374
|
brief: string,
|
|
475
375
|
maxBriefChars: number,
|
|
@@ -488,240 +388,44 @@ export function briefContractComplaint(
|
|
|
488
388
|
return null;
|
|
489
389
|
}
|
|
490
390
|
|
|
491
|
-
/**
|
|
492
|
-
* One brief from the generator, with one chance to cure a criterion it missed.
|
|
493
|
-
*
|
|
494
|
-
* The no-retry rule this lane was built with holds for what it was written about: a
|
|
495
|
-
* generator that fails on a span fails on it again, and looping would spend the session's
|
|
496
|
-
* calls proving it. A contract complaint is the other case. The request stated a limit, the
|
|
497
|
-
* answer missed it, and the complaint names exactly what to change, so the second ask is
|
|
498
|
-
* new information rather than the same ask twice. It is bounded at one, and only a
|
|
499
|
-
* complaint earns it: transport faults, timeouts and attribution drift still fail on the
|
|
500
|
-
* first answer. Both attempts sit inside the caller's timeout, so a brief costs no more
|
|
501
|
-
* wall clock than it did.
|
|
502
|
-
*/
|
|
503
|
-
export async function generatedBrief(input: {
|
|
504
|
-
summarize: (request: Record<string, unknown>, ctx?: unknown) => Promise<Record<string, unknown>>;
|
|
505
|
-
request: Record<string, unknown>;
|
|
506
|
-
ctx?: unknown;
|
|
507
|
-
maxBriefChars: number;
|
|
508
|
-
toolName: string;
|
|
509
|
-
}): Promise<{ brief: string; provenance: BriefProvenance; cured: boolean }> {
|
|
510
|
-
let complaint: string | null = null;
|
|
511
|
-
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
512
|
-
const result = await input.summarize(
|
|
513
|
-
complaint === null ? input.request : { ...input.request, cure: complaint },
|
|
514
|
-
input.ctx,
|
|
515
|
-
);
|
|
516
|
-
const brief = typeof result?.brief === "string" ? result.brief.trim() : "";
|
|
517
|
-
const provenance = generatorAttribution(result);
|
|
518
|
-
complaint = briefContractComplaint(brief, input.maxBriefChars, input.toolName);
|
|
519
|
-
if (complaint === null) return { brief, provenance, cured: attempt > 0 };
|
|
520
|
-
}
|
|
521
|
-
throw new Error(`Model context brief usefulness contract drift after one cure: ${complaint}`);
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
/**
|
|
525
|
-
* Attribution is the harness's own wiring, not the generator's judgment, so it is never
|
|
526
|
-
* handed back to be cured: a summarizer that cannot say which model wrote a brief is broken
|
|
527
|
-
* in a way a second ask cannot mend. Shared by the single and batched paths so the two
|
|
528
|
-
* cannot drift.
|
|
529
|
-
*/
|
|
530
|
-
function generatorAttribution(result: Record<string, unknown> | null | undefined): BriefProvenance {
|
|
531
|
-
const digest = result?.launchContractDigest;
|
|
532
|
-
if (typeof result?.provider !== "string" || !result.provider ||
|
|
533
|
-
typeof result?.model !== "string" || !result.model ||
|
|
534
|
-
typeof result?.effort !== "string" || !result.effort || result.toolCalls !== 0 ||
|
|
535
|
-
(digest !== undefined && (typeof digest !== "string" || !/^[a-f0-9]{64}$/.test(digest)))) {
|
|
536
|
-
throw new Error("Model context brief attribution, zero-tool, or digest contract drift");
|
|
537
|
-
}
|
|
538
|
-
return {
|
|
539
|
-
kind: "model",
|
|
540
|
-
provider: result.provider,
|
|
541
|
-
model: result.model,
|
|
542
|
-
effort: result.effort,
|
|
543
|
-
...(typeof digest === "string" ? { launchContractDigest: digest } : {}),
|
|
544
|
-
};
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
/**
|
|
548
|
-
* Many briefs from one call, with one chance to cure the ones that missed.
|
|
549
|
-
*
|
|
550
|
-
* The lane used to send one request per fold, two at a time, which meant most folds never
|
|
551
|
-
* reached a generator at all: the 2026-08-11 rep committed 87 folds and applied 14 model
|
|
552
|
-
* briefs, the rest keeping the deterministic sentence because the queue was full. A commit
|
|
553
|
-
* folds its spans together, so they are briefed together, and coverage stops competing with
|
|
554
|
-
* throughput.
|
|
555
|
-
*
|
|
556
|
-
* The cure is per-span and the retry carries only the spans that failed, so one bad brief
|
|
557
|
-
* does not make the model rewrite eleven good ones. A span still failing after its cure
|
|
558
|
-
* keeps its deterministic brief and is named in the error, which is the same outcome a
|
|
559
|
-
* single-span failure has always had; the difference is that its neighbours still land.
|
|
560
|
-
*/
|
|
561
|
-
export async function generatedBriefs(input: {
|
|
562
|
-
summarize: (request: Record<string, unknown>, ctx?: unknown) => Promise<Record<string, unknown>>;
|
|
563
|
-
request: Record<string, unknown>;
|
|
564
|
-
spans: Array<Record<string, unknown>>;
|
|
565
|
-
ctx?: unknown;
|
|
566
|
-
maxBriefChars: number;
|
|
567
|
-
toolName: string;
|
|
568
|
-
}): Promise<{
|
|
569
|
-
briefs: Array<{ brief: string; provenance: BriefProvenance } | null>;
|
|
570
|
-
cured: number;
|
|
571
|
-
complaints: string[];
|
|
572
|
-
}> {
|
|
573
|
-
const settled = new Array<{ brief: string; provenance: BriefProvenance } | null>(input.spans.length)
|
|
574
|
-
.fill(null);
|
|
575
|
-
let pending = input.spans.map((span, index) => ({ span, index }));
|
|
576
|
-
let cured = 0;
|
|
577
|
-
let complaints: string[] = [];
|
|
578
|
-
for (let attempt = 0; attempt < 2 && pending.length; attempt += 1) {
|
|
579
|
-
const asked = pending;
|
|
580
|
-
const result = await input.summarize({
|
|
581
|
-
...input.request,
|
|
582
|
-
spans: asked.map((item) => item.span),
|
|
583
|
-
...(complaints.length
|
|
584
|
-
? {
|
|
585
|
-
cure: asked.map((item, at) =>
|
|
586
|
-
`Brief ${at + 1}: ${complaints[at] ?? "did not meet the stated criteria."}`).join(" "),
|
|
587
|
-
}
|
|
588
|
-
: {}),
|
|
589
|
-
}, input.ctx);
|
|
590
|
-
const provenance = generatorAttribution(result);
|
|
591
|
-
const written = Array.isArray(result?.briefs) ? result.briefs : [];
|
|
592
|
-
const failed: typeof pending = [];
|
|
593
|
-
const next: string[] = [];
|
|
594
|
-
for (let at = 0; at < asked.length; at += 1) {
|
|
595
|
-
const brief = typeof written[at] === "string" ? (written[at] as string).trim() : "";
|
|
596
|
-
const complaint = briefContractComplaint(brief, input.maxBriefChars, input.toolName);
|
|
597
|
-
if (complaint === null) {
|
|
598
|
-
settled[asked[at].index] = { brief, provenance };
|
|
599
|
-
if (attempt > 0) cured += 1;
|
|
600
|
-
continue;
|
|
601
|
-
}
|
|
602
|
-
failed.push(asked[at]);
|
|
603
|
-
next.push(complaint);
|
|
604
|
-
}
|
|
605
|
-
pending = failed;
|
|
606
|
-
complaints = next;
|
|
607
|
-
}
|
|
608
|
-
return { briefs: settled, cured, complaints };
|
|
609
|
-
}
|
|
610
|
-
|
|
611
391
|
export async function prepareFold(input: {
|
|
612
392
|
candidate: FoldCandidate;
|
|
613
393
|
snapshot: ActiveContextSnapshot;
|
|
614
394
|
state: ActiveContextState;
|
|
615
395
|
generation: number;
|
|
616
396
|
brief?: string;
|
|
617
|
-
/**
|
|
618
|
-
* Provenance for a supplied brief. A full record round-trips a brief whose
|
|
619
|
-
* generator already ran (a pending mark carrying a model brief); the string
|
|
620
|
-
* forms stay the shorthand for the two model-free kinds.
|
|
621
|
-
*/
|
|
622
397
|
briefProvenance?: "supplied" | "deterministic" | BriefProvenance;
|
|
623
|
-
summarize?: (request: Record<string, unknown>, ctx?: unknown) => Promise<Record<string, unknown>>;
|
|
624
|
-
onSummarizerFailure?: (error: unknown) => void;
|
|
625
398
|
ctx?: unknown;
|
|
626
399
|
signal?: AbortSignal;
|
|
627
400
|
now?: () => number;
|
|
628
401
|
}): Promise<PreparedFold> {
|
|
629
402
|
const { candidate, snapshot, state } = input;
|
|
630
|
-
const protectedSource = candidate.
|
|
631
|
-
|
|
632
|
-
: refsProtected(candidate.sourceRefs, state, snapshot);
|
|
633
|
-
if (!candidate.sourceRefs.length || candidate.sourceRefs.length > snapshot.policy.maxFoldSourceRefs ||
|
|
403
|
+
const protectedSource = refsProtected(candidate.sourceRefs, state, snapshot);
|
|
404
|
+
if (!candidate.sourceRefs.length ||
|
|
634
405
|
refsInOrder(snapshot, candidate.sourceRefs) === null || protectedSource) {
|
|
635
406
|
throw new Error("Fold source is not exact, stale, and unprotected");
|
|
636
407
|
}
|
|
637
408
|
const sourceText = encodedFoldSource(snapshot, state, candidate.parts, candidate.kind);
|
|
638
|
-
const groupChildren = candidate.parts.filter((part) => part.kind === "fold").length;
|
|
639
|
-
const readableSource = groupChildren
|
|
640
|
-
? consolidationSourceText(snapshot, state, candidate.parts)
|
|
641
|
-
: sourceText;
|
|
642
409
|
const orientation = boundedOrientation(snapshot, candidate.sourceRefs);
|
|
643
410
|
const sourceSha256 = sha256Text(sourceText);
|
|
644
411
|
const beforeSha256 = sha256Text(orientation.beforeText);
|
|
645
412
|
const afterSha256 = sha256Text(orientation.afterText);
|
|
646
|
-
const candidateId = sha256Value({
|
|
647
|
-
kind: candidate.kind,
|
|
648
|
-
parts: candidate.parts.map(normalizedPart),
|
|
649
|
-
sourceSha256,
|
|
650
|
-
beforeSha256,
|
|
651
|
-
afterSha256,
|
|
652
|
-
});
|
|
653
413
|
|
|
654
414
|
let brief: string;
|
|
655
415
|
let provenance: BriefProvenance;
|
|
656
416
|
if (input.brief !== undefined) {
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
}
|
|
417
|
+
const complaint = briefContractComplaint(
|
|
418
|
+
input.brief.trim(), snapshot.policy.maxBriefChars, snapshot.toolName);
|
|
419
|
+
if (complaint !== null) throw new Error(`Supplied brief rejected. ${complaint}`);
|
|
660
420
|
brief = input.brief.trim();
|
|
661
421
|
provenance = typeof input.briefProvenance === "object"
|
|
662
422
|
? clone(input.briefProvenance)
|
|
663
423
|
: { kind: input.briefProvenance ?? "supplied" };
|
|
664
424
|
} else {
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
input.signal?.addEventListener("abort", relayAbort, { once: true });
|
|
670
|
-
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
671
|
-
try {
|
|
672
|
-
if (bytes(readableSource) > snapshot.policy.maxSourceChars) {
|
|
673
|
-
throw new Error("Fold source exceeds the bounded model-summary request");
|
|
674
|
-
}
|
|
675
|
-
const timed = new Promise<never>((_resolve, reject) => {
|
|
676
|
-
timeout = setTimeout(() => {
|
|
677
|
-
controller.abort();
|
|
678
|
-
reject(new Error(`Context brief exceeded ${snapshot.policy.briefTimeoutMs}ms`));
|
|
679
|
-
}, snapshot.policy.briefTimeoutMs);
|
|
680
|
-
});
|
|
681
|
-
const generated = await Promise.race([generatedBrief({
|
|
682
|
-
summarize: input.summarize,
|
|
683
|
-
request: {
|
|
684
|
-
candidateId,
|
|
685
|
-
sourceRefs: clone(candidate.sourceRefs),
|
|
686
|
-
// What the generator READS, which for a group is its children opened one level
|
|
687
|
-
// rather than the placeholders `sourceText` renders. The fold's own identity
|
|
688
|
-
// stays `sourceSha256` above: what a span IS does not change with how it is read.
|
|
689
|
-
sourceText: readableSource,
|
|
690
|
-
sourceSha256: sha256Text(readableSource),
|
|
691
|
-
children: groupChildren,
|
|
692
|
-
beforeRefs: clone(orientation.beforeRefs),
|
|
693
|
-
beforeText: orientation.beforeText,
|
|
694
|
-
beforeSha256,
|
|
695
|
-
afterRefs: clone(orientation.afterRefs),
|
|
696
|
-
afterText: orientation.afterText,
|
|
697
|
-
afterSha256,
|
|
698
|
-
maxBriefChars: snapshot.policy.maxBriefChars,
|
|
699
|
-
signal: controller.signal,
|
|
700
|
-
},
|
|
701
|
-
ctx: input.ctx,
|
|
702
|
-
maxBriefChars: snapshot.policy.maxBriefChars,
|
|
703
|
-
toolName: snapshot.toolName,
|
|
704
|
-
}), timed]);
|
|
705
|
-
modelBrief = { brief: generated.brief, provenance: generated.provenance };
|
|
706
|
-
} catch (error) {
|
|
707
|
-
if (input.signal?.aborted) throw error;
|
|
708
|
-
input.onSummarizerFailure?.(error);
|
|
709
|
-
} finally {
|
|
710
|
-
clearTimeout(timeout);
|
|
711
|
-
input.signal?.removeEventListener("abort", relayAbort);
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
if (modelBrief) {
|
|
715
|
-
brief = modelBrief.brief;
|
|
716
|
-
provenance = modelBrief.provenance;
|
|
717
|
-
} else {
|
|
718
|
-
// By kind, not by assumption: a group that loses its generator falls back to the
|
|
719
|
-
// index of its children, and only a chapter falls back to a chapter's brief.
|
|
720
|
-
brief = candidate.kind === "consolidation"
|
|
721
|
-
? deterministicConsolidationBrief(candidate, state)
|
|
722
|
-
: deterministicChapterCandidateBrief(snapshot, candidate);
|
|
723
|
-
provenance = { kind: "deterministic" };
|
|
724
|
-
}
|
|
425
|
+
brief = candidate.kind === "consolidation"
|
|
426
|
+
? deterministicConsolidationBrief(candidate, state, snapshot.toolName, snapshot)
|
|
427
|
+
: deterministicChapterCandidateBrief(snapshot, candidate);
|
|
428
|
+
provenance = { kind: "deterministic" };
|
|
725
429
|
}
|
|
726
430
|
|
|
727
431
|
const id = foldIdFor(candidate.kind, candidate.parts);
|
|
@@ -869,13 +573,12 @@ export function preparedFoldError(input: {
|
|
|
869
573
|
if (prepared.branchSha256 !== branchSha256(snapshot, [
|
|
870
574
|
...prepared.beforeRefs, ...prepared.sourceRefs, ...prepared.afterRefs,
|
|
871
575
|
])) return "branch drift";
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
if (
|
|
877
|
-
|
|
878
|
-
: refsProtected(prepared.sourceRefs, state, snapshot)) return "source became protected";
|
|
576
|
+
// One protected set now: an entry is unfoldable because its turn has not completed or
|
|
577
|
+
// because it carries no evidence ref, and both are validity, not recency. The branch on
|
|
578
|
+
// fold kind went with fresh-tail protection: it read `toolRefsProtected` on one side,
|
|
579
|
+
// and once that collapsed onto `refsProtected` both arms were the same call.
|
|
580
|
+
if (indices.some((index) => snapshot.protectedIndices.has(index))) return "protection drift";
|
|
581
|
+
if (refsProtected(prepared.sourceRefs, state, snapshot)) return "source became protected";
|
|
879
582
|
if (Object.prototype.hasOwnProperty.call(input, "ratio")) {
|
|
880
583
|
if (input.ratio === null || typeof input.ratio !== "number" || !Number.isFinite(input.ratio)) {
|
|
881
584
|
return "current pressure unavailable";
|
|
@@ -910,9 +613,6 @@ export function commitPreparedFold(input: {
|
|
|
910
613
|
if (error) throw new Error(`Prepared active-context fold discarded: ${error}`);
|
|
911
614
|
const nested = stateWithNestedFold(input.state, input.prepared.fold);
|
|
912
615
|
const next = clearPrepared({ ...nested, revision: input.state.revision + 1 });
|
|
913
|
-
// A fold is not committed unless its provider projection preserves every
|
|
914
|
-
// existing tool-call/output pair atomically. This keeps malformed provider
|
|
915
|
-
// context non-constructible even if a future selector regresses.
|
|
916
616
|
projectActiveContext(input.snapshot, next);
|
|
917
617
|
return next;
|
|
918
618
|
}
|
|
@@ -975,12 +675,10 @@ export function protectEvidence(
|
|
|
975
675
|
});
|
|
976
676
|
const byKey = new Map(state.protected.map((ref) => [objectRefKey(ref), ref]));
|
|
977
677
|
for (const ref of refs) protect ? byKey.set(objectRefKey(ref), ref) : byKey.delete(objectRefKey(ref));
|
|
978
|
-
// Atomic: the whole resolved set pins or nothing does. A partial pin that reports
|
|
979
|
-
// success would leave part of the requested span silently reclaimable.
|
|
980
678
|
if (byKey.size > MAX_ACTIVE_PROTECTED) {
|
|
981
679
|
throw new Error(
|
|
982
680
|
`Protecting ${ids.join(", ")} would hold ${byKey.size} refs, over the ${MAX_ACTIVE_PROTECTED}-ref cap; ` +
|
|
983
|
-
"
|
|
681
|
+
"unpin something first",
|
|
984
682
|
);
|
|
985
683
|
}
|
|
986
684
|
return clearPrepared({
|
|
@@ -1031,9 +729,7 @@ export function renderFold(
|
|
|
1031
729
|
const refs = flattenFoldRefs(fold, state);
|
|
1032
730
|
const indices = refsInOrder(snapshot, refs);
|
|
1033
731
|
if (!indices) return null;
|
|
1034
|
-
const mustReveal = state.expanded.includes(fold.id) || (
|
|
1035
|
-
? toolRefsProtected(refs, state, snapshot)
|
|
1036
|
-
: refsProtected(refs, state, snapshot));
|
|
732
|
+
const mustReveal = state.expanded.includes(fold.id) || (refsProtected(refs, state, snapshot));
|
|
1037
733
|
if (!mustReveal) {
|
|
1038
734
|
const first = snapshot.messages[indices[0]] as Record<string, unknown>;
|
|
1039
735
|
const text = foldPlaceholder(fold, state, snapshot);
|
|
@@ -1105,13 +801,6 @@ export function assertProjectionPreservesToolLinkage(source: unknown[], projecte
|
|
|
1105
801
|
}
|
|
1106
802
|
}
|
|
1107
803
|
|
|
1108
|
-
/**
|
|
1109
|
-
* Peek mass the ladder cannot take: results whose evidence the agent protected
|
|
1110
|
-
* outright. This is the number that made rep 7's starvation invisible. The mass sat
|
|
1111
|
-
* raw in the window, the ROI trigger's eligibility arithmetic could never reach its
|
|
1112
|
-
* threshold over it, and no accounting field said so while the window grew to 235k
|
|
1113
|
-
* tokens.
|
|
1114
|
-
*/
|
|
1115
804
|
export function pinnedPeekMass(
|
|
1116
805
|
snapshot: ActiveContextSnapshot,
|
|
1117
806
|
state: ActiveContextState,
|
|
@@ -1170,13 +859,6 @@ export function projectActiveContext(
|
|
|
1170
859
|
index += 1;
|
|
1171
860
|
}
|
|
1172
861
|
}
|
|
1173
|
-
// A peek result is APPEND-ONLY. It used to be rewritten in place once a model call
|
|
1174
|
-
// had seen it, on the theory that the edit was tail-local; rep 22 disproved that.
|
|
1175
|
-
// The result is only tail-local when it ARRIVES, and reclamation deliberately waits
|
|
1176
|
-
// until a later assistant message exists, by which point the window has grown over
|
|
1177
|
-
// it and the edit lands mid-prefix. Two peeks cost 100k fresh tokens that way. The
|
|
1178
|
-
// duplicate bytes are still reclaimed, by the tool-fold rung at the next commit,
|
|
1179
|
-
// which is the one moment a rewrite is already being paid for.
|
|
1180
862
|
assertProjectionPreservesToolLinkage(snapshot.messages, output);
|
|
1181
863
|
return output;
|
|
1182
864
|
}
|
|
@@ -1187,9 +869,7 @@ export function foldStatusRow(fold: ActiveFold, state: ActiveContextState, snaps
|
|
|
1187
869
|
const refs = flattenFoldRefs(fold, state);
|
|
1188
870
|
const allSourceIds = refs.map((ref) => ref.entryId);
|
|
1189
871
|
const sourceIds = allSourceIds.slice(0, 64);
|
|
1190
|
-
const blocked =
|
|
1191
|
-
? toolRefsProtected(refs, state, snapshot)
|
|
1192
|
-
: refsProtected(refs, state, snapshot);
|
|
872
|
+
const blocked = refsProtected(refs, state, snapshot);
|
|
1193
873
|
const projection = state.expanded.includes(fold.id) ? "expanded" : "folded";
|
|
1194
874
|
return {
|
|
1195
875
|
id: fold.id,
|
|
@@ -1205,9 +885,6 @@ export function foldStatusRow(fold: ActiveFold, state: ActiveContextState, snaps
|
|
|
1205
885
|
sourceCount: allSourceIds.length,
|
|
1206
886
|
sourceIdsTruncated: sourceIds.length < allSourceIds.length,
|
|
1207
887
|
sourceSha256: fold.sourceSha256,
|
|
1208
|
-
// A paged row without the brief is a chronological id and nothing else: the
|
|
1209
|
-
// agent that paged the tree to find WHICH fold holds something got the one
|
|
1210
|
-
// field that answers it withheld, while the tree detail carried it all along.
|
|
1211
888
|
brief: foldBrief(fold, state),
|
|
1212
889
|
sourceChars: fold.sourceChars,
|
|
1213
890
|
actions: {
|
|
@@ -1216,12 +893,11 @@ export function foldStatusRow(fold: ActiveFold, state: ActiveContextState, snaps
|
|
|
1216
893
|
: { action: "refold", id: fold.id },
|
|
1217
894
|
expand: { action: "expand", id: fold.id },
|
|
1218
895
|
refold: { action: "refold", id: fold.id },
|
|
1219
|
-
|
|
896
|
+
pin: { action: "pin", ids: [fold.id] },
|
|
1220
897
|
},
|
|
1221
898
|
};
|
|
1222
899
|
}
|
|
1223
900
|
|
|
1224
|
-
/** Every fold, nested ones included, in transcript order with each parent before its children. */
|
|
1225
901
|
export function orderedFoldTree(state: ActiveContextState, snapshot: ActiveContextSnapshot): ActiveFold[] {
|
|
1226
902
|
const byId = foldMap(state);
|
|
1227
903
|
const ordered: ActiveFold[] = [];
|
|
@@ -1260,12 +936,6 @@ export function foldTreeDetail(
|
|
|
1260
936
|
}));
|
|
1261
937
|
}
|
|
1262
938
|
|
|
1263
|
-
/**
|
|
1264
|
-
* One compact row per collapsed fold: what is behind it, where its span starts and
|
|
1265
|
-
* ends, and the brief that carries whatever key material the fold captured. This is
|
|
1266
|
-
* the answer to "which fold has X" that agents were paying 56-83k chars per peek to
|
|
1267
|
-
* reconstruct, and it costs a line.
|
|
1268
|
-
*/
|
|
1269
939
|
export function foldIndexRow(
|
|
1270
940
|
fold: ActiveFold,
|
|
1271
941
|
state: ActiveContextState,
|
|
@@ -1294,7 +964,6 @@ export function foldIndexRow(
|
|
|
1294
964
|
};
|
|
1295
965
|
}
|
|
1296
966
|
|
|
1297
|
-
/** Every mapped source id to the fold that now holds it, so a lookup never needs a read. */
|
|
1298
967
|
export function foldSourceMap(
|
|
1299
968
|
state: ActiveContextState,
|
|
1300
969
|
snapshot: ActiveContextSnapshot,
|
|
@@ -1304,7 +973,6 @@ export function foldSourceMap(
|
|
|
1304
973
|
for (const fold of orderedFoldTree(state, snapshot)) {
|
|
1305
974
|
for (const ref of flattenFoldRefs(fold, state)) entries.push([ref.entryId, fold.id]);
|
|
1306
975
|
}
|
|
1307
|
-
// Deepest wins: the innermost fold is the one that actually holds the source.
|
|
1308
976
|
const byEntry = new Map(entries);
|
|
1309
977
|
const all = [...byEntry.entries()];
|
|
1310
978
|
return { entries: all.slice(0, limit), total: all.length, truncated: all.length > limit };
|
|
@@ -1315,8 +983,7 @@ export function activeContextStatus(
|
|
|
1315
983
|
state: ActiveContextState,
|
|
1316
984
|
offset = 0,
|
|
1317
985
|
limit = 40,
|
|
1318
|
-
|
|
1319
|
-
statusOptions: { diet?: boolean; suggestions?: number } = {},
|
|
986
|
+
statusOptions: { diet?: boolean; indexRows?: number } = {},
|
|
1320
987
|
): Record<string, unknown> {
|
|
1321
988
|
const roots = orderedRoots(state, snapshot).map((item) => item.fold.id);
|
|
1322
989
|
const ordered = orderedFoldTree(state, snapshot);
|
|
@@ -1330,26 +997,22 @@ export function activeContextStatus(
|
|
|
1330
997
|
stale: !snapshot.protectedIndices.has(item.index),
|
|
1331
998
|
protected: snapshot.protectedIndices.has(item.index) || protectedKeys.has(objectRefKey(item.ref)),
|
|
1332
999
|
staleToolResult: item.ref.role === "toolResult" &&
|
|
1333
|
-
!snapshot.
|
|
1000
|
+
!snapshot.protectedIndices.has(item.index) && !protectedKeys.has(objectRefKey(item.ref)) &&
|
|
1334
1001
|
resultCall(snapshot, item.index, true) !== null,
|
|
1335
1002
|
}] : []);
|
|
1336
1003
|
const selectedObjects = objects.slice(offset, offset + limit);
|
|
1337
|
-
const eligibleChapter = selectAutomaticChapter(snapshot, state
|
|
1004
|
+
const eligibleChapter = selectAutomaticChapter(snapshot, state);
|
|
1338
1005
|
const eligibleSourceIds = eligibleChapter?.sourceRefs.map((ref) => ref.entryId) ?? [];
|
|
1339
1006
|
const eligibleEndpoints = eligibleSourceIds.length
|
|
1340
1007
|
? [...new Set([eligibleSourceIds[0], eligibleSourceIds.at(-1)!])]
|
|
1341
1008
|
: [];
|
|
1342
|
-
// The index that used to ride every status call grew from 12.6k to 86.5k chars in
|
|
1343
|
-
// one measured session, injected on every request that asked for state. On the diet
|
|
1344
|
-
// the default payload carries counts, the source map and the folds worth acting on;
|
|
1345
|
-
// the full tree and the object list move behind an explicit paged query.
|
|
1346
1009
|
const index = statusOptions.diet
|
|
1347
1010
|
? visibleCollapsedFolds(state, snapshot)
|
|
1348
1011
|
.map((fold) => foldIndexRow(fold, state, snapshot))
|
|
1349
1012
|
.sort((left, right) =>
|
|
1350
1013
|
Number(right.reclaimableBytes) - Number(left.reclaimableBytes) ||
|
|
1351
1014
|
String(left.id).localeCompare(String(right.id)))
|
|
1352
|
-
.slice(0, statusOptions.
|
|
1015
|
+
.slice(0, statusOptions.indexRows ?? STATUS_DIET_INDEX_ROWS)
|
|
1353
1016
|
: null;
|
|
1354
1017
|
const sourceMap = statusOptions.diet ? foldSourceMap(state, snapshot) : null;
|
|
1355
1018
|
return {
|
|
@@ -1391,30 +1054,17 @@ export function activeContextStatus(
|
|
|
1391
1054
|
endId: eligibleSourceIds.at(-1),
|
|
1392
1055
|
action: { action: "fold", ids: eligibleEndpoints, brief: "<factual brief, at most 1000 characters>" },
|
|
1393
1056
|
} : null,
|
|
1394
|
-
rawTailMinimumBytes: zoneBytes(snapshot.thresholds.freshTail, snapshot.budgetTokens),
|
|
1395
1057
|
currentTurnRequiresBoundary: false,
|
|
1396
1058
|
actions: {
|
|
1397
1059
|
status: { action: "status", offset, limit },
|
|
1398
1060
|
fold: { action: "fold", ids: ["<source-or-fold-id>"], brief: "<optional factual brief, at most 1000 characters>" },
|
|
1399
|
-
|
|
1061
|
+
pin: { action: "pin", ids: ["<source-or-fold-id>"] },
|
|
1400
1062
|
},
|
|
1401
1063
|
};
|
|
1402
1064
|
}
|
|
1403
1065
|
|
|
1404
|
-
/** Room reserved for the one-line continuation marker a truncated page carries. */
|
|
1405
1066
|
const STATUS_CONTINUATION_RESERVE_BYTES = 400;
|
|
1406
1067
|
|
|
1407
|
-
/**
|
|
1408
|
-
* Enforce the hard status page cap on one fully assembled status payload.
|
|
1409
|
-
*
|
|
1410
|
-
* Measured 2026-08-07 (rep 19): one status answer serialized to 526KB, 6.2x the
|
|
1411
|
-
* remaining headroom, and aborted the request. The paging affordance existed;
|
|
1412
|
-
* nothing forced it. This function is the force: every listing in the payload is
|
|
1413
|
-
* truncated at a unit boundary (whole rows, never mid-unit) until the serialized
|
|
1414
|
-
* page fits, and a one-line continuation marker names the next offset. Trim order
|
|
1415
|
-
* spends the diagnostics first and the listing the caller asked for last, and the
|
|
1416
|
-
* requested listing always keeps at least one unit so paging always progresses.
|
|
1417
|
-
*/
|
|
1418
1068
|
export function boundStatusPayload(
|
|
1419
1069
|
payload: Record<string, unknown>,
|
|
1420
1070
|
detail: string | null,
|
|
@@ -1426,9 +1076,6 @@ export function boundStatusPayload(
|
|
|
1426
1076
|
const target = cap - STATUS_CONTINUATION_RESERVE_BYTES;
|
|
1427
1077
|
const fits = (): boolean => size() <= target;
|
|
1428
1078
|
const omitted: string[] = [];
|
|
1429
|
-
// keepEnd keeps the NEWEST units of a time-ordered ledger; a paged listing keeps
|
|
1430
|
-
// its head so the continuation offset is the next unread unit. Binary search for
|
|
1431
|
-
// the largest keep that fits; a stage that cannot fit alone trims to its floor.
|
|
1432
1079
|
const shrink = (owner: unknown, key: string, label: string, keepEnd = false, minKeep = 0): number => {
|
|
1433
1080
|
if (!isPlainRecord(owner) || fits()) return 0;
|
|
1434
1081
|
const original = ownValue(owner, key);
|
|
@@ -1452,16 +1099,22 @@ export function boundStatusPayload(
|
|
|
1452
1099
|
};
|
|
1453
1100
|
const automatic = ownValue(payload, "automatic");
|
|
1454
1101
|
const instrumentation = isPlainRecord(automatic) ? ownValue(automatic, "instrumentation") : undefined;
|
|
1455
|
-
const
|
|
1102
|
+
const lastAction = isPlainRecord(automatic) ? ownValue(automatic, "lastAutomaticAction") : undefined;
|
|
1103
|
+
const lastEpoch = isPlainRecord(lastAction) ? ownValue(lastAction, "epoch") : undefined;
|
|
1456
1104
|
shrink(instrumentation, "projectionRecords", "projection record(s)", true);
|
|
1457
1105
|
shrink(instrumentation, "cacheObservations", "cache observation(s)", true);
|
|
1458
1106
|
shrink(instrumentation, "events", "context event(s)", true);
|
|
1459
|
-
shrink(surfacing, "log", "surfacing log row(s)", true);
|
|
1460
1107
|
if (shrink(payload, "sourceMap", "source map row(s)") > 0) payload.sourceMapTruncated = true;
|
|
1461
1108
|
shrink(payload, "topFolds", "top fold row(s)");
|
|
1109
|
+
// The last epoch's receipt lists grow with commit depth (a deep epoch applies dozens
|
|
1110
|
+
// of marks). They are the commit's own receipt, so they outlast
|
|
1111
|
+
// every other diagnostic, but they still page away before any listing row the
|
|
1112
|
+
// caller actually asked for: on the detail pages, where none of the diagnostics
|
|
1113
|
+
// above exist, they are the mass that made one fold row miss the cap.
|
|
1114
|
+
const receiptRowsDropped =
|
|
1115
|
+
shrink(lastEpoch, "applied", "applied receipt row(s)", true) +
|
|
1116
|
+
shrink(lastEpoch, "absorbed", "absorbed receipt row(s)", true) > 0;
|
|
1462
1117
|
const rowLabel = (key: string): string => key === "folds" ? "fold row(s)" : key === "objects" ? "object row(s)" : "tree row(s)";
|
|
1463
|
-
// The listing the caller asked for goes last and never below one unit. Its
|
|
1464
|
-
// machine-readable next offset moves back to the first unit the page dropped.
|
|
1465
1118
|
let continueAt: number | null = null;
|
|
1466
1119
|
if (detail === "folds" || detail === "objects") {
|
|
1467
1120
|
const rideKey = detail === "folds" ? "objects" : "folds";
|
|
@@ -1478,9 +1131,17 @@ export function boundStatusPayload(
|
|
|
1478
1131
|
else payload.nextOffset = continueAt;
|
|
1479
1132
|
}
|
|
1480
1133
|
if (!omitted.length) return payload;
|
|
1481
|
-
|
|
1482
|
-
|
|
1134
|
+
// A dropped receipt row has no page: the epoch receipt is a record of the last
|
|
1135
|
+
// action, not a listing, and claiming it "stays reachable" pointed the agent at
|
|
1136
|
+
// a page that does not exist (2026-08-14 external review). The span-to-fold
|
|
1137
|
+
// mapping it summarizes stays complete in the folds and tree details, so the
|
|
1138
|
+
// continuation says exactly that instead.
|
|
1139
|
+
const pageable = continueAt === null
|
|
1140
|
+
? "the fold, object and tree lists stay reachable through the paged status details"
|
|
1483
1141
|
: `continue at ${JSON.stringify({ action: "status", detail, offset: continueAt })}`;
|
|
1142
|
+
const resume = receiptRowsDropped
|
|
1143
|
+
? `${pageable}; the epoch receipt keeps only its newest rows and the trimmed rows have no page`
|
|
1144
|
+
: (continueAt === null ? "the full lists stay reachable through the paged status details" : pageable);
|
|
1484
1145
|
payload.continuation =
|
|
1485
1146
|
`Status pages are bounded to ${cap} bytes; omitted ${omitted.join(", ")}; ${resume}.`;
|
|
1486
1147
|
return payload;
|
|
@@ -1495,9 +1156,7 @@ export function visibleCollapsedFolds(
|
|
|
1495
1156
|
const visit = (fold: ActiveFold): void => {
|
|
1496
1157
|
const refs = flattenFoldRefs(fold, state);
|
|
1497
1158
|
if (!foldInterval(fold, state, snapshot)) return;
|
|
1498
|
-
const protectedSource =
|
|
1499
|
-
? toolRefsProtected(refs, state, snapshot)
|
|
1500
|
-
: refsProtected(refs, state, snapshot);
|
|
1159
|
+
const protectedSource = refsProtected(refs, state, snapshot);
|
|
1501
1160
|
if (!state.expanded.includes(fold.id) && !protectedSource) {
|
|
1502
1161
|
output.push(fold);
|
|
1503
1162
|
return;
|
|
@@ -1559,7 +1218,6 @@ export interface FoldSourceRequest {
|
|
|
1559
1218
|
projectEntry?: (entry: Record<string, unknown>) => unknown[];
|
|
1560
1219
|
}
|
|
1561
1220
|
|
|
1562
|
-
/** One exact message per ref identity and digest, read off the immutable session entries. */
|
|
1563
1221
|
function exactSessionMessages(input: FoldSourceRequest): Map<string, unknown> {
|
|
1564
1222
|
const projectEntry = input.projectEntry ?? sessionEntryMessages;
|
|
1565
1223
|
const exact = new Map<string, unknown>();
|
|
@@ -1586,13 +1244,6 @@ function exactMessageFor(
|
|
|
1586
1244
|
return clone(message);
|
|
1587
1245
|
}
|
|
1588
1246
|
|
|
1589
|
-
/**
|
|
1590
|
-
* One fold's ENTIRE original source, at every depth.
|
|
1591
|
-
*
|
|
1592
|
-
* This is the rescue read: restoration and the recovery audit both need the bytes as the
|
|
1593
|
-
* session first wrote them, with nothing standing in for anything. Retrieval inside a
|
|
1594
|
-
* session is the other function, and it serves one level.
|
|
1595
|
-
*/
|
|
1596
1247
|
export function recoverFoldMessages(input: FoldSourceRequest): unknown[] {
|
|
1597
1248
|
const fold = input.state.folds.find((item) => item.id === input.foldId);
|
|
1598
1249
|
if (!fold) throw new Error(`Unknown active-context fold ${input.foldId}`);
|
|
@@ -1600,7 +1251,6 @@ export function recoverFoldMessages(input: FoldSourceRequest): unknown[] {
|
|
|
1600
1251
|
return flattenFoldRefs(fold, input.state).map((ref) => exactMessageFor(exact, ref, input.sessionId));
|
|
1601
1252
|
}
|
|
1602
1253
|
|
|
1603
|
-
/** A child fold as its parent's stored span holds it: the placeholder, not the bytes under it. */
|
|
1604
1254
|
export function storedChildPlaceholder(child: ActiveFold, state: ActiveContextState): Record<string, unknown> {
|
|
1605
1255
|
return {
|
|
1606
1256
|
placeholder: "fold",
|
|
@@ -1614,18 +1264,6 @@ export function storedChildPlaceholder(child: ActiveFold, state: ActiveContextSt
|
|
|
1614
1264
|
};
|
|
1615
1265
|
}
|
|
1616
1266
|
|
|
1617
|
-
/**
|
|
1618
|
-
* One fold's stored span, AS STORED: one level.
|
|
1619
|
-
*
|
|
1620
|
-
* A fold's span is what it took in, and what it took in was already whatever the window
|
|
1621
|
-
* held: raw entries, and placeholders for folds that were there first. So the honest read
|
|
1622
|
-
* of a parent is that same span, children still placeheld. Reading it as the original
|
|
1623
|
-
* bytes at every depth would hand back a whole session's transcript for one call and
|
|
1624
|
-
* undo the nesting the fold performed.
|
|
1625
|
-
*
|
|
1626
|
-
* The verbatim floor is untouched by this. A leaf's stored span IS its original bytes, so
|
|
1627
|
-
* the bytes are always exactly one peek away from any id the read hands back.
|
|
1628
|
-
*/
|
|
1629
1267
|
export function foldStoredSpan(input: FoldSourceRequest): unknown[] {
|
|
1630
1268
|
const fold = input.state.folds.find((item) => item.id === input.foldId);
|
|
1631
1269
|
if (!fold) throw new Error(`Unknown active-context fold ${input.foldId}`);
|
|
@@ -1639,21 +1277,12 @@ export function foldStoredSpan(input: FoldSourceRequest): unknown[] {
|
|
|
1639
1277
|
});
|
|
1640
1278
|
}
|
|
1641
1279
|
|
|
1642
|
-
/**
|
|
1643
|
-
* Ephemeral point read of one fold's stored span. It returns the same SHA-256-verified
|
|
1644
|
-
* span expansion restores, ONE level: raw entries verbatim, child folds still placeheld.
|
|
1645
|
-
* It is bounded for one model call and changes no projection: the fold stays collapsed and
|
|
1646
|
-
* the durable state is untouched. Any id in the result peeks in one hop, so the verbatim
|
|
1647
|
-
* floor is a hop away at every depth rather than a payload nobody asked for.
|
|
1648
|
-
*/
|
|
1649
1280
|
export function peekFoldSource(input: {
|
|
1650
1281
|
foldId: string;
|
|
1651
1282
|
state: ActiveContextState;
|
|
1652
1283
|
entries: Array<Record<string, unknown>>;
|
|
1653
1284
|
sessionId: string;
|
|
1654
|
-
/** Explicit widening. Absent, a peek returns the bounded index view. */
|
|
1655
1285
|
maximumBytes?: number;
|
|
1656
|
-
/** Byte offset into the exact stored source; the narrowing half of admission control. */
|
|
1657
1286
|
offset?: number;
|
|
1658
1287
|
toolName?: string;
|
|
1659
1288
|
projectEntry?: (entry: Record<string, unknown>) => unknown[];
|
|
@@ -1672,9 +1301,6 @@ export function peekFoldSource(input: {
|
|
|
1672
1301
|
const offset = Math.min(Math.max(0, input.offset ?? 0), sourceBytes);
|
|
1673
1302
|
const budget = input.maximumBytes ?? PEEK_DEFAULT_MAX_BYTES;
|
|
1674
1303
|
const window = offset > 0 ? utf8Slice(source, offset) : source;
|
|
1675
|
-
// Offset zero is the INDEX read and takes head AND tail, because chain keys and
|
|
1676
|
-
// conclusions live at the end of a source and a head-only slice silently omits
|
|
1677
|
-
// exactly that region. An explicit offset is paging and stays contiguous.
|
|
1678
1304
|
const view = offset > 0
|
|
1679
1305
|
? { text: boundedUtf8(window, budget), omittedBytes: 0, contiguous: true }
|
|
1680
1306
|
: boundedHeadTail(window, budget);
|
|
@@ -1685,8 +1311,6 @@ export function peekFoldSource(input: {
|
|
|
1685
1311
|
: null;
|
|
1686
1312
|
const truncated = view.omittedBytes > 0 || nextOffset !== null;
|
|
1687
1313
|
const children = childFoldIds(fold);
|
|
1688
|
-
// The index view: every nested fold's brief in FULL. A brief is the navigable unit,
|
|
1689
|
-
// so truncating it is the one economy that costs the read its purpose.
|
|
1690
1314
|
const index = descendantIndexRows(fold, input.state);
|
|
1691
1315
|
return {
|
|
1692
1316
|
version: 1,
|
|
@@ -1706,15 +1330,13 @@ export function peekFoldSource(input: {
|
|
|
1706
1330
|
truncated,
|
|
1707
1331
|
view: offset > 0 ? "slice" : (view.omittedBytes > 0 ? "index" : "complete"),
|
|
1708
1332
|
...(index.length ? { index } : {}),
|
|
1709
|
-
// Narrower and WIDER reads that are always constructible, so a refusal or a
|
|
1710
|
-
// truncation can name one.
|
|
1711
1333
|
children,
|
|
1712
1334
|
narrower: {
|
|
1713
1335
|
...(nextOffset === null ? {} : { slice: { action: "peek", id: fold.id, offset: nextOffset, bytes: returnedBytes } }),
|
|
1714
1336
|
...(children.length ? { child: { action: "peek", id: children[0] } } : {}),
|
|
1715
1337
|
},
|
|
1716
1338
|
...(truncated
|
|
1717
|
-
? { wider: { action: "peek", id: fold.id, bytes: Math.min(sourceBytes, ACTIVE_CONTEXT_POLICY.
|
|
1339
|
+
? { wider: { action: "peek", id: fold.id, bytes: Math.min(sourceBytes, ACTIVE_CONTEXT_POLICY.maxSourceChars) } }
|
|
1718
1340
|
: {}),
|
|
1719
1341
|
lifetime: "these bytes stay in the window exactly as returned, like any other tool result, and " +
|
|
1720
1342
|
`nothing rewrites this result in place. It is a COPY of fold ${fold.id}'s stored source, so the ` +
|
|
@@ -1728,17 +1350,13 @@ export function peekFoldSource(input: {
|
|
|
1728
1350
|
"placeheld. Peek a child id to read its own span; the fold stayed collapsed and no projection changed."
|
|
1729
1351
|
: "Complete exact source; the fold stayed collapsed and no projection changed.",
|
|
1730
1352
|
source: returned,
|
|
1731
|
-
// Serialized AFTER the source on purpose: a reader that just consumed a bounded slice
|
|
1732
|
-
// decides its next action at the end of the payload, and a notice buried before ten
|
|
1733
|
-
// thousand source bytes is a notice unread (measured: a run concluded a staged chain
|
|
1734
|
-
// was finished because the chain key lived in the unshown tail of a truncated peek).
|
|
1735
1353
|
...(truncated
|
|
1736
1354
|
? {
|
|
1737
1355
|
truncationReminder: `STOP: ${view.omittedBytes || sourceBytes - returnedBytes} of ${sourceBytes} ` +
|
|
1738
1356
|
"bytes were NOT shown" +
|
|
1739
1357
|
(view.omittedBytes ? " (the middle; the head and tail are both above)" : " (everything after this slice)") +
|
|
1740
1358
|
`. Widen with {"action":"peek","id":"${fold.id}","bytes":${
|
|
1741
|
-
Math.min(sourceBytes, ACTIVE_CONTEXT_POLICY.
|
|
1359
|
+
Math.min(sourceBytes, ACTIVE_CONTEXT_POLICY.maxSourceChars)}}` +
|
|
1742
1360
|
(nextOffset === null ? "" : ` or read on with {"action":"peek","id":"${fold.id}","offset":${nextOffset}}`) +
|
|
1743
1361
|
".",
|
|
1744
1362
|
}
|
|
@@ -1746,16 +1364,10 @@ export function peekFoldSource(input: {
|
|
|
1746
1364
|
};
|
|
1747
1365
|
}
|
|
1748
1366
|
|
|
1749
|
-
/** The marker a head+tail view puts where the omitted middle was. */
|
|
1750
1367
|
export function headTailMarker(omittedBytes: number): string {
|
|
1751
1368
|
return omittedBytes > 0 ? `\n... [${omittedBytes} exact source bytes omitted] ...\n` : "";
|
|
1752
1369
|
}
|
|
1753
1370
|
|
|
1754
|
-
/**
|
|
1755
|
-
* A bounded view that keeps the head AND the tail. A head-only bound drops exactly the
|
|
1756
|
-
* region a staged chain keeps its key in; the omitted size is stated where the reading
|
|
1757
|
-
* ends so "there is more" is never an inference.
|
|
1758
|
-
*/
|
|
1759
1371
|
export function boundedHeadTail(
|
|
1760
1372
|
source: string,
|
|
1761
1373
|
budget: number,
|
|
@@ -1776,7 +1388,6 @@ export function boundedHeadTail(
|
|
|
1776
1388
|
};
|
|
1777
1389
|
}
|
|
1778
1390
|
|
|
1779
|
-
/** Every descendant fold, in transcript order, with its brief in full. */
|
|
1780
1391
|
export function descendantIndexRows(
|
|
1781
1392
|
fold: ActiveFold,
|
|
1782
1393
|
state: ActiveContextState,
|