pi-fold 1.0.3 → 2.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 +34 -8
- package/extensions/active-context.ts +255 -2331
- package/extensions/evidence.js +0 -10
- package/extensions/index.js +10 -30
- package/extensions/json.ts +3 -29
- package/extensions/lib/canonical.ts +0 -8
- package/extensions/lib/curation.ts +1 -258
- package/extensions/lib/folding.ts +33 -406
- package/extensions/lib/instrumentation.ts +3 -124
- package/extensions/lib/measurement.ts +0 -71
- package/extensions/lib/persistence.ts +216 -197
- package/extensions/lib/policy.ts +29 -558
- package/extensions/lib/rollback.ts +0 -53
- package/extensions/lib/scheduling.ts +36 -367
- package/extensions/lib/selection.ts +206 -312
- package/extensions/lib/tool-surface.ts +5 -18
- package/extensions/lib/transcript.ts +32 -108
- package/package.json +1 -1
- package/extensions/lib/surfacing.ts +0 -604
- package/extensions/summarizer.js +0 -291
|
@@ -53,7 +53,7 @@ import {
|
|
|
53
53
|
MAX_FOLD_SPAN_CHARS,
|
|
54
54
|
PEEK_DEFAULT_MAX_BYTES,
|
|
55
55
|
PEEK_HEAD_SHARE,
|
|
56
|
-
|
|
56
|
+
STATUS_DIET_INDEX_ROWS,
|
|
57
57
|
zoneBytes,
|
|
58
58
|
} from "./policy.ts";
|
|
59
59
|
import type {
|
|
@@ -88,16 +88,7 @@ export function selectAutomaticChapter(
|
|
|
88
88
|
maximumSourceRefs: number = snapshot.policy.maxFoldSourceRefs,
|
|
89
89
|
claimed: ReadonlySet<string> = new Set<string>(),
|
|
90
90
|
): 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
91
|
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
92
|
const allowedChildren = NO_FOLD_KINDS;
|
|
102
93
|
for (let unitIndex = 0; unitIndex < units.length; unitIndex += 1) {
|
|
103
94
|
const first = units[unitIndex];
|
|
@@ -117,15 +108,7 @@ export function selectAutomaticChapter(
|
|
|
117
108
|
if (claimed.size && refs.some((ref) => claimed.has(objectRefKey(ref)))) continue;
|
|
118
109
|
if (refsProtected(refs, state, snapshot)) continue;
|
|
119
110
|
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
111
|
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
112
|
const biteSized = size <= MAX_FOLD_SPAN_CHARS || endIndex === unitIndex;
|
|
130
113
|
if (size >= snapshot.policy.minChapterChars && biteSized) best = { kind: "chapter", parts, sourceRefs: refs };
|
|
131
114
|
if (size > MAX_FOLD_SPAN_CHARS) break;
|
|
@@ -135,20 +118,6 @@ export function selectAutomaticChapter(
|
|
|
135
118
|
return null;
|
|
136
119
|
}
|
|
137
120
|
|
|
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
121
|
export function selectAutomaticStaleSpan(
|
|
153
122
|
snapshot: ActiveContextSnapshot,
|
|
154
123
|
state: ActiveContextState,
|
|
@@ -162,10 +131,6 @@ export function selectAutomaticStaleSpan(
|
|
|
162
131
|
return at(folds) < at(chapter) ? folds : chapter;
|
|
163
132
|
}
|
|
164
133
|
|
|
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
134
|
export function selectAutomaticSpan(
|
|
170
135
|
snapshot: ActiveContextSnapshot,
|
|
171
136
|
state: ActiveContextState,
|
|
@@ -185,14 +150,6 @@ export type AutomaticRungSelection =
|
|
|
185
150
|
|
|
186
151
|
export interface AutomaticRungSelectionOptions {
|
|
187
152
|
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
153
|
claimed?: ReadonlySet<string>;
|
|
197
154
|
claimedFoldIds?: ReadonlySet<string>;
|
|
198
155
|
}
|
|
@@ -224,24 +181,12 @@ export function selectAutomaticRung(
|
|
|
224
181
|
if (options.toolOnly || !Number.isFinite(ratio)) return null;
|
|
225
182
|
const refold = selectAutomaticRefold(snapshot, state, claimedFoldIds);
|
|
226
183
|
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
184
|
const span = selectAutomaticStaleSpan(snapshot, state, claimed);
|
|
230
185
|
if (span?.kind === "consolidation") return { kind: "consolidation", candidate: span };
|
|
231
186
|
const chapter = span;
|
|
232
187
|
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
|
-
}
|
|
188
|
+
if (ratio >= hardFenceRatio(snapshot)) return { kind: "chapter", candidate: chapter };
|
|
189
|
+
if (ratio >= snapshot.policy.prepareRatio) return { kind: "chapter-prepare", candidate: chapter };
|
|
245
190
|
return null;
|
|
246
191
|
}
|
|
247
192
|
|
|
@@ -250,12 +195,10 @@ export function foldCandidatesDetail(
|
|
|
250
195
|
state: ActiveContextState,
|
|
251
196
|
ratio: number | null,
|
|
252
197
|
options: {
|
|
253
|
-
summarizerAvailable?: boolean;
|
|
254
198
|
generation?: number;
|
|
255
199
|
measurementFresh?: boolean;
|
|
256
200
|
automaticFailure?: boolean;
|
|
257
201
|
preparing?: boolean;
|
|
258
|
-
failedPreparationIds?: ReadonlySet<string>;
|
|
259
202
|
} = {},
|
|
260
203
|
): Record<string, unknown> {
|
|
261
204
|
const measuredRatio = ratio !== null && Number.isFinite(ratio) ? ratio : Number.NaN;
|
|
@@ -264,10 +207,7 @@ export function foldCandidatesDetail(
|
|
|
264
207
|
const staleSpan = selectAutomaticStaleSpan(snapshot, state);
|
|
265
208
|
const consolidation = staleSpan?.kind === "consolidation" ? staleSpan : null;
|
|
266
209
|
const chapter = staleSpan?.kind === "chapter" ? staleSpan : null;
|
|
267
|
-
const selection = selectAutomaticRung(snapshot, state, measuredRatio
|
|
268
|
-
summarizerAvailable: options.summarizerAvailable,
|
|
269
|
-
failedPreparationIds: options.failedPreparationIds,
|
|
270
|
-
});
|
|
210
|
+
const selection = selectAutomaticRung(snapshot, state, measuredRatio);
|
|
271
211
|
let wouldFireNow: string | null = null;
|
|
272
212
|
let blockedBy: string | null = null;
|
|
273
213
|
if (options.measurementFresh === false) blockedBy = "measurement-stale";
|
|
@@ -296,9 +236,6 @@ export function foldCandidatesDetail(
|
|
|
296
236
|
} : null,
|
|
297
237
|
wouldFireNow,
|
|
298
238
|
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
239
|
zones: {
|
|
303
240
|
freshBoundary: snapshot.freshBoundary,
|
|
304
241
|
budgetTokens: snapshot.budgetTokens,
|
|
@@ -369,25 +306,6 @@ export function encodedFoldSource(
|
|
|
369
306
|
}));
|
|
370
307
|
}
|
|
371
308
|
|
|
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
309
|
export function consolidationSourceText(
|
|
392
310
|
snapshot: ActiveContextSnapshot,
|
|
393
311
|
state: ActiveContextState,
|
|
@@ -417,8 +335,6 @@ export function consolidationSourceText(
|
|
|
417
335
|
? { collapsed: "brief only: this child did not fit expanded" }
|
|
418
336
|
: { contents: entry.messages }),
|
|
419
337
|
})));
|
|
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
338
|
const bySize = entries
|
|
423
339
|
.filter((entry) => entry.foldId !== null)
|
|
424
340
|
.map((entry) => ({ index: entry.index, size: bytes(entry.messages) }))
|
|
@@ -459,17 +375,6 @@ export function boundedOrientation(
|
|
|
459
375
|
return { beforeRefs: before, beforeText, afterRefs: after.refs, afterText: after.text };
|
|
460
376
|
}
|
|
461
377
|
|
|
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
378
|
export function briefContractComplaint(
|
|
474
379
|
brief: string,
|
|
475
380
|
maxBriefChars: number,
|
|
@@ -488,140 +393,13 @@ export function briefContractComplaint(
|
|
|
488
393
|
return null;
|
|
489
394
|
}
|
|
490
395
|
|
|
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
396
|
export async function prepareFold(input: {
|
|
612
397
|
candidate: FoldCandidate;
|
|
613
398
|
snapshot: ActiveContextSnapshot;
|
|
614
399
|
state: ActiveContextState;
|
|
615
400
|
generation: number;
|
|
616
401
|
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
402
|
briefProvenance?: "supplied" | "deterministic" | BriefProvenance;
|
|
623
|
-
summarize?: (request: Record<string, unknown>, ctx?: unknown) => Promise<Record<string, unknown>>;
|
|
624
|
-
onSummarizerFailure?: (error: unknown) => void;
|
|
625
403
|
ctx?: unknown;
|
|
626
404
|
signal?: AbortSignal;
|
|
627
405
|
now?: () => number;
|
|
@@ -635,93 +413,26 @@ export async function prepareFold(input: {
|
|
|
635
413
|
throw new Error("Fold source is not exact, stale, and unprotected");
|
|
636
414
|
}
|
|
637
415
|
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
416
|
const orientation = boundedOrientation(snapshot, candidate.sourceRefs);
|
|
643
417
|
const sourceSha256 = sha256Text(sourceText);
|
|
644
418
|
const beforeSha256 = sha256Text(orientation.beforeText);
|
|
645
419
|
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
420
|
|
|
654
421
|
let brief: string;
|
|
655
422
|
let provenance: BriefProvenance;
|
|
656
423
|
if (input.brief !== undefined) {
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
}
|
|
424
|
+
const complaint = briefContractComplaint(
|
|
425
|
+
input.brief.trim(), snapshot.policy.maxBriefChars, snapshot.toolName);
|
|
426
|
+
if (complaint !== null) throw new Error(`Supplied brief rejected. ${complaint}`);
|
|
660
427
|
brief = input.brief.trim();
|
|
661
428
|
provenance = typeof input.briefProvenance === "object"
|
|
662
429
|
? clone(input.briefProvenance)
|
|
663
430
|
: { kind: input.briefProvenance ?? "supplied" };
|
|
664
431
|
} 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
|
-
}
|
|
432
|
+
brief = candidate.kind === "consolidation"
|
|
433
|
+
? deterministicConsolidationBrief(candidate, state, snapshot.toolName, snapshot)
|
|
434
|
+
: deterministicChapterCandidateBrief(snapshot, candidate);
|
|
435
|
+
provenance = { kind: "deterministic" };
|
|
725
436
|
}
|
|
726
437
|
|
|
727
438
|
const id = foldIdFor(candidate.kind, candidate.parts);
|
|
@@ -910,9 +621,6 @@ export function commitPreparedFold(input: {
|
|
|
910
621
|
if (error) throw new Error(`Prepared active-context fold discarded: ${error}`);
|
|
911
622
|
const nested = stateWithNestedFold(input.state, input.prepared.fold);
|
|
912
623
|
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
624
|
projectActiveContext(input.snapshot, next);
|
|
917
625
|
return next;
|
|
918
626
|
}
|
|
@@ -975,8 +683,6 @@ export function protectEvidence(
|
|
|
975
683
|
});
|
|
976
684
|
const byKey = new Map(state.protected.map((ref) => [objectRefKey(ref), ref]));
|
|
977
685
|
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
686
|
if (byKey.size > MAX_ACTIVE_PROTECTED) {
|
|
981
687
|
throw new Error(
|
|
982
688
|
`Protecting ${ids.join(", ")} would hold ${byKey.size} refs, over the ${MAX_ACTIVE_PROTECTED}-ref cap; ` +
|
|
@@ -1105,13 +811,6 @@ export function assertProjectionPreservesToolLinkage(source: unknown[], projecte
|
|
|
1105
811
|
}
|
|
1106
812
|
}
|
|
1107
813
|
|
|
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
814
|
export function pinnedPeekMass(
|
|
1116
815
|
snapshot: ActiveContextSnapshot,
|
|
1117
816
|
state: ActiveContextState,
|
|
@@ -1170,13 +869,6 @@ export function projectActiveContext(
|
|
|
1170
869
|
index += 1;
|
|
1171
870
|
}
|
|
1172
871
|
}
|
|
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
872
|
assertProjectionPreservesToolLinkage(snapshot.messages, output);
|
|
1181
873
|
return output;
|
|
1182
874
|
}
|
|
@@ -1205,9 +897,6 @@ export function foldStatusRow(fold: ActiveFold, state: ActiveContextState, snaps
|
|
|
1205
897
|
sourceCount: allSourceIds.length,
|
|
1206
898
|
sourceIdsTruncated: sourceIds.length < allSourceIds.length,
|
|
1207
899
|
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
900
|
brief: foldBrief(fold, state),
|
|
1212
901
|
sourceChars: fold.sourceChars,
|
|
1213
902
|
actions: {
|
|
@@ -1221,7 +910,6 @@ export function foldStatusRow(fold: ActiveFold, state: ActiveContextState, snaps
|
|
|
1221
910
|
};
|
|
1222
911
|
}
|
|
1223
912
|
|
|
1224
|
-
/** Every fold, nested ones included, in transcript order with each parent before its children. */
|
|
1225
913
|
export function orderedFoldTree(state: ActiveContextState, snapshot: ActiveContextSnapshot): ActiveFold[] {
|
|
1226
914
|
const byId = foldMap(state);
|
|
1227
915
|
const ordered: ActiveFold[] = [];
|
|
@@ -1260,12 +948,6 @@ export function foldTreeDetail(
|
|
|
1260
948
|
}));
|
|
1261
949
|
}
|
|
1262
950
|
|
|
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
951
|
export function foldIndexRow(
|
|
1270
952
|
fold: ActiveFold,
|
|
1271
953
|
state: ActiveContextState,
|
|
@@ -1294,7 +976,6 @@ export function foldIndexRow(
|
|
|
1294
976
|
};
|
|
1295
977
|
}
|
|
1296
978
|
|
|
1297
|
-
/** Every mapped source id to the fold that now holds it, so a lookup never needs a read. */
|
|
1298
979
|
export function foldSourceMap(
|
|
1299
980
|
state: ActiveContextState,
|
|
1300
981
|
snapshot: ActiveContextSnapshot,
|
|
@@ -1304,7 +985,6 @@ export function foldSourceMap(
|
|
|
1304
985
|
for (const fold of orderedFoldTree(state, snapshot)) {
|
|
1305
986
|
for (const ref of flattenFoldRefs(fold, state)) entries.push([ref.entryId, fold.id]);
|
|
1306
987
|
}
|
|
1307
|
-
// Deepest wins: the innermost fold is the one that actually holds the source.
|
|
1308
988
|
const byEntry = new Map(entries);
|
|
1309
989
|
const all = [...byEntry.entries()];
|
|
1310
990
|
return { entries: all.slice(0, limit), total: all.length, truncated: all.length > limit };
|
|
@@ -1316,7 +996,7 @@ export function activeContextStatus(
|
|
|
1316
996
|
offset = 0,
|
|
1317
997
|
limit = 40,
|
|
1318
998
|
maximumChapterSourceRefs = Number.MAX_SAFE_INTEGER,
|
|
1319
|
-
statusOptions: { diet?: boolean;
|
|
999
|
+
statusOptions: { diet?: boolean; indexRows?: number } = {},
|
|
1320
1000
|
): Record<string, unknown> {
|
|
1321
1001
|
const roots = orderedRoots(state, snapshot).map((item) => item.fold.id);
|
|
1322
1002
|
const ordered = orderedFoldTree(state, snapshot);
|
|
@@ -1339,17 +1019,13 @@ export function activeContextStatus(
|
|
|
1339
1019
|
const eligibleEndpoints = eligibleSourceIds.length
|
|
1340
1020
|
? [...new Set([eligibleSourceIds[0], eligibleSourceIds.at(-1)!])]
|
|
1341
1021
|
: [];
|
|
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
1022
|
const index = statusOptions.diet
|
|
1347
1023
|
? visibleCollapsedFolds(state, snapshot)
|
|
1348
1024
|
.map((fold) => foldIndexRow(fold, state, snapshot))
|
|
1349
1025
|
.sort((left, right) =>
|
|
1350
1026
|
Number(right.reclaimableBytes) - Number(left.reclaimableBytes) ||
|
|
1351
1027
|
String(left.id).localeCompare(String(right.id)))
|
|
1352
|
-
.slice(0, statusOptions.
|
|
1028
|
+
.slice(0, statusOptions.indexRows ?? STATUS_DIET_INDEX_ROWS)
|
|
1353
1029
|
: null;
|
|
1354
1030
|
const sourceMap = statusOptions.diet ? foldSourceMap(state, snapshot) : null;
|
|
1355
1031
|
return {
|
|
@@ -1401,20 +1077,8 @@ export function activeContextStatus(
|
|
|
1401
1077
|
};
|
|
1402
1078
|
}
|
|
1403
1079
|
|
|
1404
|
-
/** Room reserved for the one-line continuation marker a truncated page carries. */
|
|
1405
1080
|
const STATUS_CONTINUATION_RESERVE_BYTES = 400;
|
|
1406
1081
|
|
|
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
1082
|
export function boundStatusPayload(
|
|
1419
1083
|
payload: Record<string, unknown>,
|
|
1420
1084
|
detail: string | null,
|
|
@@ -1426,9 +1090,6 @@ export function boundStatusPayload(
|
|
|
1426
1090
|
const target = cap - STATUS_CONTINUATION_RESERVE_BYTES;
|
|
1427
1091
|
const fits = (): boolean => size() <= target;
|
|
1428
1092
|
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
1093
|
const shrink = (owner: unknown, key: string, label: string, keepEnd = false, minKeep = 0): number => {
|
|
1433
1094
|
if (!isPlainRecord(owner) || fits()) return 0;
|
|
1434
1095
|
const original = ownValue(owner, key);
|
|
@@ -1452,16 +1113,22 @@ export function boundStatusPayload(
|
|
|
1452
1113
|
};
|
|
1453
1114
|
const automatic = ownValue(payload, "automatic");
|
|
1454
1115
|
const instrumentation = isPlainRecord(automatic) ? ownValue(automatic, "instrumentation") : undefined;
|
|
1455
|
-
const
|
|
1116
|
+
const lastAction = isPlainRecord(automatic) ? ownValue(automatic, "lastAutomaticAction") : undefined;
|
|
1117
|
+
const lastEpoch = isPlainRecord(lastAction) ? ownValue(lastAction, "epoch") : undefined;
|
|
1456
1118
|
shrink(instrumentation, "projectionRecords", "projection record(s)", true);
|
|
1457
1119
|
shrink(instrumentation, "cacheObservations", "cache observation(s)", true);
|
|
1458
1120
|
shrink(instrumentation, "events", "context event(s)", true);
|
|
1459
|
-
shrink(surfacing, "log", "surfacing log row(s)", true);
|
|
1460
1121
|
if (shrink(payload, "sourceMap", "source map row(s)") > 0) payload.sourceMapTruncated = true;
|
|
1461
1122
|
shrink(payload, "topFolds", "top fold row(s)");
|
|
1123
|
+
// The last epoch's receipt lists grow with commit depth (a 0.20 minTarget epoch
|
|
1124
|
+
// applies dozens of marks). They are the commit's own receipt, so they outlast
|
|
1125
|
+
// every other diagnostic, but they still page away before any listing row the
|
|
1126
|
+
// caller actually asked for: on the detail pages, where none of the diagnostics
|
|
1127
|
+
// above exist, they are the mass that made one fold row miss the cap.
|
|
1128
|
+
const receiptRowsDropped =
|
|
1129
|
+
shrink(lastEpoch, "applied", "applied receipt row(s)", true) +
|
|
1130
|
+
shrink(lastEpoch, "absorbed", "absorbed receipt row(s)", true) > 0;
|
|
1462
1131
|
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
1132
|
let continueAt: number | null = null;
|
|
1466
1133
|
if (detail === "folds" || detail === "objects") {
|
|
1467
1134
|
const rideKey = detail === "folds" ? "objects" : "folds";
|
|
@@ -1478,9 +1145,17 @@ export function boundStatusPayload(
|
|
|
1478
1145
|
else payload.nextOffset = continueAt;
|
|
1479
1146
|
}
|
|
1480
1147
|
if (!omitted.length) return payload;
|
|
1481
|
-
|
|
1482
|
-
|
|
1148
|
+
// A dropped receipt row has no page: the epoch receipt is a record of the last
|
|
1149
|
+
// action, not a listing, and claiming it "stays reachable" pointed the agent at
|
|
1150
|
+
// a page that does not exist (2026-08-14 external review). The span-to-fold
|
|
1151
|
+
// mapping it summarizes stays complete in the folds and tree details, so the
|
|
1152
|
+
// continuation says exactly that instead.
|
|
1153
|
+
const pageable = continueAt === null
|
|
1154
|
+
? "the fold, object and tree lists stay reachable through the paged status details"
|
|
1483
1155
|
: `continue at ${JSON.stringify({ action: "status", detail, offset: continueAt })}`;
|
|
1156
|
+
const resume = receiptRowsDropped
|
|
1157
|
+
? `${pageable}; the epoch receipt keeps only its newest rows and the trimmed rows have no page`
|
|
1158
|
+
: (continueAt === null ? "the full lists stay reachable through the paged status details" : pageable);
|
|
1484
1159
|
payload.continuation =
|
|
1485
1160
|
`Status pages are bounded to ${cap} bytes; omitted ${omitted.join(", ")}; ${resume}.`;
|
|
1486
1161
|
return payload;
|
|
@@ -1559,7 +1234,6 @@ export interface FoldSourceRequest {
|
|
|
1559
1234
|
projectEntry?: (entry: Record<string, unknown>) => unknown[];
|
|
1560
1235
|
}
|
|
1561
1236
|
|
|
1562
|
-
/** One exact message per ref identity and digest, read off the immutable session entries. */
|
|
1563
1237
|
function exactSessionMessages(input: FoldSourceRequest): Map<string, unknown> {
|
|
1564
1238
|
const projectEntry = input.projectEntry ?? sessionEntryMessages;
|
|
1565
1239
|
const exact = new Map<string, unknown>();
|
|
@@ -1586,13 +1260,6 @@ function exactMessageFor(
|
|
|
1586
1260
|
return clone(message);
|
|
1587
1261
|
}
|
|
1588
1262
|
|
|
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
1263
|
export function recoverFoldMessages(input: FoldSourceRequest): unknown[] {
|
|
1597
1264
|
const fold = input.state.folds.find((item) => item.id === input.foldId);
|
|
1598
1265
|
if (!fold) throw new Error(`Unknown active-context fold ${input.foldId}`);
|
|
@@ -1600,7 +1267,6 @@ export function recoverFoldMessages(input: FoldSourceRequest): unknown[] {
|
|
|
1600
1267
|
return flattenFoldRefs(fold, input.state).map((ref) => exactMessageFor(exact, ref, input.sessionId));
|
|
1601
1268
|
}
|
|
1602
1269
|
|
|
1603
|
-
/** A child fold as its parent's stored span holds it: the placeholder, not the bytes under it. */
|
|
1604
1270
|
export function storedChildPlaceholder(child: ActiveFold, state: ActiveContextState): Record<string, unknown> {
|
|
1605
1271
|
return {
|
|
1606
1272
|
placeholder: "fold",
|
|
@@ -1614,18 +1280,6 @@ export function storedChildPlaceholder(child: ActiveFold, state: ActiveContextSt
|
|
|
1614
1280
|
};
|
|
1615
1281
|
}
|
|
1616
1282
|
|
|
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
1283
|
export function foldStoredSpan(input: FoldSourceRequest): unknown[] {
|
|
1630
1284
|
const fold = input.state.folds.find((item) => item.id === input.foldId);
|
|
1631
1285
|
if (!fold) throw new Error(`Unknown active-context fold ${input.foldId}`);
|
|
@@ -1639,21 +1293,12 @@ export function foldStoredSpan(input: FoldSourceRequest): unknown[] {
|
|
|
1639
1293
|
});
|
|
1640
1294
|
}
|
|
1641
1295
|
|
|
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
1296
|
export function peekFoldSource(input: {
|
|
1650
1297
|
foldId: string;
|
|
1651
1298
|
state: ActiveContextState;
|
|
1652
1299
|
entries: Array<Record<string, unknown>>;
|
|
1653
1300
|
sessionId: string;
|
|
1654
|
-
/** Explicit widening. Absent, a peek returns the bounded index view. */
|
|
1655
1301
|
maximumBytes?: number;
|
|
1656
|
-
/** Byte offset into the exact stored source; the narrowing half of admission control. */
|
|
1657
1302
|
offset?: number;
|
|
1658
1303
|
toolName?: string;
|
|
1659
1304
|
projectEntry?: (entry: Record<string, unknown>) => unknown[];
|
|
@@ -1672,9 +1317,6 @@ export function peekFoldSource(input: {
|
|
|
1672
1317
|
const offset = Math.min(Math.max(0, input.offset ?? 0), sourceBytes);
|
|
1673
1318
|
const budget = input.maximumBytes ?? PEEK_DEFAULT_MAX_BYTES;
|
|
1674
1319
|
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
1320
|
const view = offset > 0
|
|
1679
1321
|
? { text: boundedUtf8(window, budget), omittedBytes: 0, contiguous: true }
|
|
1680
1322
|
: boundedHeadTail(window, budget);
|
|
@@ -1685,8 +1327,6 @@ export function peekFoldSource(input: {
|
|
|
1685
1327
|
: null;
|
|
1686
1328
|
const truncated = view.omittedBytes > 0 || nextOffset !== null;
|
|
1687
1329
|
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
1330
|
const index = descendantIndexRows(fold, input.state);
|
|
1691
1331
|
return {
|
|
1692
1332
|
version: 1,
|
|
@@ -1706,8 +1346,6 @@ export function peekFoldSource(input: {
|
|
|
1706
1346
|
truncated,
|
|
1707
1347
|
view: offset > 0 ? "slice" : (view.omittedBytes > 0 ? "index" : "complete"),
|
|
1708
1348
|
...(index.length ? { index } : {}),
|
|
1709
|
-
// Narrower and WIDER reads that are always constructible, so a refusal or a
|
|
1710
|
-
// truncation can name one.
|
|
1711
1349
|
children,
|
|
1712
1350
|
narrower: {
|
|
1713
1351
|
...(nextOffset === null ? {} : { slice: { action: "peek", id: fold.id, offset: nextOffset, bytes: returnedBytes } }),
|
|
@@ -1728,10 +1366,6 @@ export function peekFoldSource(input: {
|
|
|
1728
1366
|
"placeheld. Peek a child id to read its own span; the fold stayed collapsed and no projection changed."
|
|
1729
1367
|
: "Complete exact source; the fold stayed collapsed and no projection changed.",
|
|
1730
1368
|
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
1369
|
...(truncated
|
|
1736
1370
|
? {
|
|
1737
1371
|
truncationReminder: `STOP: ${view.omittedBytes || sourceBytes - returnedBytes} of ${sourceBytes} ` +
|
|
@@ -1746,16 +1380,10 @@ export function peekFoldSource(input: {
|
|
|
1746
1380
|
};
|
|
1747
1381
|
}
|
|
1748
1382
|
|
|
1749
|
-
/** The marker a head+tail view puts where the omitted middle was. */
|
|
1750
1383
|
export function headTailMarker(omittedBytes: number): string {
|
|
1751
1384
|
return omittedBytes > 0 ? `\n... [${omittedBytes} exact source bytes omitted] ...\n` : "";
|
|
1752
1385
|
}
|
|
1753
1386
|
|
|
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
1387
|
export function boundedHeadTail(
|
|
1760
1388
|
source: string,
|
|
1761
1389
|
budget: number,
|
|
@@ -1776,7 +1404,6 @@ export function boundedHeadTail(
|
|
|
1776
1404
|
};
|
|
1777
1405
|
}
|
|
1778
1406
|
|
|
1779
|
-
/** Every descendant fold, in transcript order, with its brief in full. */
|
|
1780
1407
|
export function descendantIndexRows(
|
|
1781
1408
|
fold: ActiveFold,
|
|
1782
1409
|
state: ActiveContextState,
|