pi-fold 0.0.1 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +135 -11
- package/extensions/active-context.ts +2157 -0
- package/extensions/evidence.js +673 -0
- package/extensions/index.js +32 -0
- package/extensions/json.ts +228 -0
- package/extensions/lib/advisory.ts +238 -0
- package/extensions/lib/canonical.ts +171 -0
- package/extensions/lib/folding.ts +1091 -0
- package/extensions/lib/measurement.ts +429 -0
- package/extensions/lib/persistence.ts +814 -0
- package/extensions/lib/policy.ts +296 -0
- package/extensions/lib/selection.ts +501 -0
- package/extensions/lib/surfacing.ts +332 -0
- package/extensions/lib/tool-surface.ts +69 -0
- package/extensions/lib/transcript.ts +479 -0
- package/extensions/summarizer.js +95 -0
- package/package.json +48 -11
- package/index.js +0 -4
|
@@ -0,0 +1,1091 @@
|
|
|
1
|
+
import type { EvidenceRef } from "../json.ts";
|
|
2
|
+
import {
|
|
3
|
+
denseOwnArrayValues,
|
|
4
|
+
evidenceRef,
|
|
5
|
+
evidenceSha256,
|
|
6
|
+
objectRefKey,
|
|
7
|
+
sha256Text,
|
|
8
|
+
sha256Value,
|
|
9
|
+
stableStringify,
|
|
10
|
+
} from "../json.ts";
|
|
11
|
+
import {
|
|
12
|
+
boundedUtf8,
|
|
13
|
+
bytes,
|
|
14
|
+
clone,
|
|
15
|
+
messageRole,
|
|
16
|
+
ownValue,
|
|
17
|
+
sessionEntryMessages,
|
|
18
|
+
usefulBrief,
|
|
19
|
+
} from "./canonical.ts";
|
|
20
|
+
import {
|
|
21
|
+
branchSha256,
|
|
22
|
+
exactMapped,
|
|
23
|
+
explicitProtectedKeys,
|
|
24
|
+
foldInterval,
|
|
25
|
+
hardFenceRatio,
|
|
26
|
+
orderedRoots,
|
|
27
|
+
refsInOrder,
|
|
28
|
+
refsProtected,
|
|
29
|
+
toolFoldCadence,
|
|
30
|
+
toolRefsProtected,
|
|
31
|
+
visibleCollapsedRoots,
|
|
32
|
+
} from "./measurement.ts";
|
|
33
|
+
import {
|
|
34
|
+
childFoldIds,
|
|
35
|
+
clearPrepared,
|
|
36
|
+
flattenFoldRefs,
|
|
37
|
+
foldIdFor,
|
|
38
|
+
foldMap,
|
|
39
|
+
normalizedPart,
|
|
40
|
+
protectionSha256,
|
|
41
|
+
topologySha256,
|
|
42
|
+
validateFoldForest,
|
|
43
|
+
} from "./persistence.ts";
|
|
44
|
+
import {
|
|
45
|
+
ACTIVE_CONTEXT_POLICY,
|
|
46
|
+
activeContextBrand,
|
|
47
|
+
activeContextSource,
|
|
48
|
+
CONSOLIDATION_WIDTH_THRESHOLD,
|
|
49
|
+
EXPAND_LEASE_GENERATIONS,
|
|
50
|
+
MAX_EXPAND_LEASES,
|
|
51
|
+
} from "./policy.ts";
|
|
52
|
+
import type {
|
|
53
|
+
ActiveContextSnapshot,
|
|
54
|
+
ActiveContextState,
|
|
55
|
+
ActiveFold,
|
|
56
|
+
BriefProvenance,
|
|
57
|
+
FoldCandidate,
|
|
58
|
+
FoldKind,
|
|
59
|
+
FoldPart,
|
|
60
|
+
MappedMessage,
|
|
61
|
+
PreparedFold,
|
|
62
|
+
} from "./policy.ts";
|
|
63
|
+
import {
|
|
64
|
+
automaticToolBrief,
|
|
65
|
+
candidateSourceRefs,
|
|
66
|
+
chapterUnits,
|
|
67
|
+
deterministicChapterCandidateBrief,
|
|
68
|
+
partsForRange,
|
|
69
|
+
resultCall,
|
|
70
|
+
selectAutomaticConsolidation,
|
|
71
|
+
selectAutomaticRefold,
|
|
72
|
+
selectAutomaticToolForRung,
|
|
73
|
+
} from "./selection.ts";
|
|
74
|
+
|
|
75
|
+
export function selectAutomaticChapter(
|
|
76
|
+
snapshot: ActiveContextSnapshot,
|
|
77
|
+
state: ActiveContextState,
|
|
78
|
+
maximumSourceRefs: number = snapshot.policy.maxFoldSourceRefs,
|
|
79
|
+
): FoldCandidate | null {
|
|
80
|
+
const units = chapterUnits(snapshot);
|
|
81
|
+
const allowedChildren = new Set<FoldKind>(["tool-result"]);
|
|
82
|
+
for (let unitIndex = 0; unitIndex < units.length; unitIndex += 1) {
|
|
83
|
+
const first = units[unitIndex];
|
|
84
|
+
let best: FoldCandidate | null = null;
|
|
85
|
+
const turnStarts = new Set<number>();
|
|
86
|
+
for (let endIndex = unitIndex; endIndex < units.length; endIndex += 1) {
|
|
87
|
+
const unit = units[endIndex];
|
|
88
|
+
if (endIndex > unitIndex && unit.start !== units[endIndex - 1].end) break;
|
|
89
|
+
turnStarts.add(unit.turnStart);
|
|
90
|
+
if (turnStarts.size > snapshot.policy.maxChapterTurns) break;
|
|
91
|
+
const coherentSegment = endIndex > unitIndex || first.end - first.start > 1;
|
|
92
|
+
if (!coherentSegment) continue;
|
|
93
|
+
const parts = partsForRange(snapshot, state, first.start, unit.end - 1, allowedChildren);
|
|
94
|
+
if (!parts || parts.some((part) => part.kind === "fold" && state.expanded.includes(part.foldId))) continue;
|
|
95
|
+
const refs = candidateSourceRefs(parts, state);
|
|
96
|
+
if (refs.length > maximumSourceRefs) break;
|
|
97
|
+
if (refsProtected(refs, state, snapshot)) continue;
|
|
98
|
+
const size = bytes(encodedFoldSource(snapshot, state, parts, "chapter"));
|
|
99
|
+
if (size > snapshot.policy.maxChapterChars) break;
|
|
100
|
+
if (size >= snapshot.policy.minChapterChars) best = { kind: "chapter", parts, sourceRefs: refs };
|
|
101
|
+
}
|
|
102
|
+
if (best) return best;
|
|
103
|
+
}
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export type AutomaticRungSelection =
|
|
108
|
+
| { kind: "prepared-chapter"; candidate: FoldCandidate }
|
|
109
|
+
| { kind: "tool"; candidate: FoldCandidate }
|
|
110
|
+
| { kind: "refold"; foldId: string }
|
|
111
|
+
| { kind: "consolidation"; candidate: FoldCandidate }
|
|
112
|
+
| { kind: "chapter"; candidate: FoldCandidate }
|
|
113
|
+
| { kind: "chapter-prepare"; candidate: FoldCandidate };
|
|
114
|
+
|
|
115
|
+
export interface AutomaticRungSelectionOptions {
|
|
116
|
+
waiveToolCadence?: boolean;
|
|
117
|
+
toolOnly?: boolean;
|
|
118
|
+
summarizerAvailable?: boolean;
|
|
119
|
+
failedPreparationIds?: ReadonlySet<string>;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function automaticPreparationId(candidate: FoldCandidate, state: ActiveContextState): string {
|
|
123
|
+
return sha256Value({
|
|
124
|
+
kind: candidate.kind,
|
|
125
|
+
refs: candidate.sourceRefs,
|
|
126
|
+
topology: topologySha256(state),
|
|
127
|
+
protection: protectionSha256(state),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function selectAutomaticRung(
|
|
132
|
+
snapshot: ActiveContextSnapshot,
|
|
133
|
+
state: ActiveContextState,
|
|
134
|
+
ratio: number,
|
|
135
|
+
options: AutomaticRungSelectionOptions = {},
|
|
136
|
+
): AutomaticRungSelection | null {
|
|
137
|
+
if (!options.toolOnly && state.prepared) {
|
|
138
|
+
if (!Number.isFinite(ratio) || ratio < hardFenceRatio(snapshot)) return null;
|
|
139
|
+
const candidate = selectAutomaticChapter(snapshot, state);
|
|
140
|
+
return candidate ? { kind: "prepared-chapter", candidate } : null;
|
|
141
|
+
}
|
|
142
|
+
const tool = selectAutomaticToolForRung(snapshot, state, ratio, options.waiveToolCadence);
|
|
143
|
+
if (tool) return { kind: "tool", candidate: tool };
|
|
144
|
+
if (options.toolOnly || !Number.isFinite(ratio)) return null;
|
|
145
|
+
const refold = selectAutomaticRefold(snapshot, state, ratio);
|
|
146
|
+
if (refold) return { kind: "refold", foldId: refold };
|
|
147
|
+
const consolidation = selectAutomaticConsolidation(snapshot, state, ratio);
|
|
148
|
+
if (consolidation) return { kind: "consolidation", candidate: consolidation };
|
|
149
|
+
const chapter = selectAutomaticChapter(snapshot, state);
|
|
150
|
+
if (!chapter) return null;
|
|
151
|
+
const preparationFailed = options.failedPreparationIds?.has(
|
|
152
|
+
automaticPreparationId(chapter, state),
|
|
153
|
+
) ?? false;
|
|
154
|
+
if (ratio >= hardFenceRatio(snapshot)) {
|
|
155
|
+
return !options.summarizerAvailable || preparationFailed
|
|
156
|
+
? { kind: "chapter", candidate: chapter }
|
|
157
|
+
: { kind: "chapter-prepare", candidate: chapter };
|
|
158
|
+
}
|
|
159
|
+
if ((options.summarizerAvailable && ratio >= snapshot.policy.warmRatio) ||
|
|
160
|
+
((!options.summarizerAvailable || preparationFailed) && ratio >= snapshot.policy.prepareRatio)) {
|
|
161
|
+
return { kind: "chapter-prepare", candidate: chapter };
|
|
162
|
+
}
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function foldCandidatesDetail(
|
|
167
|
+
snapshot: ActiveContextSnapshot,
|
|
168
|
+
state: ActiveContextState,
|
|
169
|
+
ratio: number | null,
|
|
170
|
+
options: {
|
|
171
|
+
summarizerAvailable?: boolean;
|
|
172
|
+
generation?: number;
|
|
173
|
+
measurementFresh?: boolean;
|
|
174
|
+
automaticFailure?: boolean;
|
|
175
|
+
preparing?: boolean;
|
|
176
|
+
failedPreparationIds?: ReadonlySet<string>;
|
|
177
|
+
} = {},
|
|
178
|
+
): Record<string, unknown> {
|
|
179
|
+
const measuredRatio = ratio !== null && Number.isFinite(ratio) ? ratio : Number.NaN;
|
|
180
|
+
const tool = selectAutomaticToolForRung(snapshot, state, measuredRatio);
|
|
181
|
+
const refold = selectAutomaticRefold(snapshot, state, measuredRatio);
|
|
182
|
+
const consolidation = selectAutomaticConsolidation(snapshot, state, measuredRatio);
|
|
183
|
+
const chapter = selectAutomaticChapter(snapshot, state);
|
|
184
|
+
const selection = selectAutomaticRung(snapshot, state, measuredRatio, {
|
|
185
|
+
summarizerAvailable: options.summarizerAvailable,
|
|
186
|
+
failedPreparationIds: options.failedPreparationIds,
|
|
187
|
+
});
|
|
188
|
+
let wouldFireNow: string | null = null;
|
|
189
|
+
let blockedBy: string | null = null;
|
|
190
|
+
if (options.measurementFresh === false) blockedBy = "measurement-stale";
|
|
191
|
+
else if (options.automaticFailure) blockedBy = "automatic-failure";
|
|
192
|
+
else if (options.preparing) blockedBy = "preparing";
|
|
193
|
+
else if (selection?.kind === "prepared-chapter" && state.prepared && preparedFoldError({
|
|
194
|
+
prepared: state.prepared,
|
|
195
|
+
snapshot,
|
|
196
|
+
state,
|
|
197
|
+
generation: options.generation ?? state.prepared.generation,
|
|
198
|
+
ratio: measuredRatio,
|
|
199
|
+
}) !== null) blockedBy = "prepared-drift";
|
|
200
|
+
else wouldFireNow = selection?.kind ?? null;
|
|
201
|
+
return {
|
|
202
|
+
tool: tool ? {
|
|
203
|
+
startId: tool.sourceRefs[0].entryId,
|
|
204
|
+
endId: tool.sourceRefs.at(-1)!.entryId,
|
|
205
|
+
} : null,
|
|
206
|
+
refold,
|
|
207
|
+
consolidation: consolidation
|
|
208
|
+
? consolidation.parts.map((part) => part.kind === "fold" ? part.foldId : "")
|
|
209
|
+
: null,
|
|
210
|
+
chapter: chapter ? {
|
|
211
|
+
ids: chapter.sourceRefs.map((ref) => ref.entryId),
|
|
212
|
+
estimatedChars: encodedFoldSource(snapshot, state, chapter.parts, chapter.kind).length,
|
|
213
|
+
} : null,
|
|
214
|
+
wouldFireNow,
|
|
215
|
+
blockedBy,
|
|
216
|
+
cadence: {
|
|
217
|
+
tokensSinceToolFold: state.tokensSinceToolFold,
|
|
218
|
+
cadenceNeed: toolFoldCadence(snapshot.contextWindow),
|
|
219
|
+
},
|
|
220
|
+
width: {
|
|
221
|
+
visibleRoots: visibleCollapsedRoots(state, snapshot).length,
|
|
222
|
+
threshold: CONSOLIDATION_WIDTH_THRESHOLD,
|
|
223
|
+
},
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function selectAutomaticCandidate(
|
|
228
|
+
snapshot: ActiveContextSnapshot,
|
|
229
|
+
state: ActiveContextState,
|
|
230
|
+
ratio: number,
|
|
231
|
+
): FoldCandidate | null {
|
|
232
|
+
const selection = selectAutomaticRung(snapshot, state, ratio);
|
|
233
|
+
return selection && "candidate" in selection ? selection.candidate : null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
export function encodedRefs(snapshot: ActiveContextSnapshot, refs: EvidenceRef[]): string {
|
|
237
|
+
return stableStringify(refs.map((ref) => {
|
|
238
|
+
const item = exactMapped(snapshot, ref);
|
|
239
|
+
if (!item) throw new Error(`Exact Pi evidence drift for ${ref.entryId}`);
|
|
240
|
+
return { ref, message: item.message };
|
|
241
|
+
}));
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function encodedFoldSource(
|
|
245
|
+
snapshot: ActiveContextSnapshot,
|
|
246
|
+
state: ActiveContextState,
|
|
247
|
+
parts: FoldPart[],
|
|
248
|
+
kind: FoldKind,
|
|
249
|
+
): string {
|
|
250
|
+
if (kind !== "chapter") {
|
|
251
|
+
const messages = renderFoldParts(parts, state, snapshot);
|
|
252
|
+
if (!messages) throw new Error("Fold source projection drifted");
|
|
253
|
+
return stableStringify({ parts: parts.map(normalizedPart), messages });
|
|
254
|
+
}
|
|
255
|
+
const refs = candidateSourceRefs(parts, state);
|
|
256
|
+
return stableStringify(refs.map((ref) => {
|
|
257
|
+
const item = exactMapped(snapshot, ref);
|
|
258
|
+
if (!item) throw new Error(`Exact Pi evidence drift for ${ref.entryId}`);
|
|
259
|
+
const call = ref.role === "toolResult" ? resultCall(snapshot, item.index, true) : null;
|
|
260
|
+
if (!call) return { ref, message: clone(item.message) };
|
|
261
|
+
const candidate: FoldCandidate = {
|
|
262
|
+
kind: "tool-result",
|
|
263
|
+
parts: [{ kind: "raw", ref }],
|
|
264
|
+
sourceRefs: [ref],
|
|
265
|
+
};
|
|
266
|
+
return {
|
|
267
|
+
ref,
|
|
268
|
+
message: {
|
|
269
|
+
role: "toolResult",
|
|
270
|
+
toolCallId: ownValue(item.message, "toolCallId"),
|
|
271
|
+
toolName: ownValue(item.message, "toolName"),
|
|
272
|
+
content: [{ type: "text", text: automaticToolBrief(snapshot, candidate) }],
|
|
273
|
+
isError: false,
|
|
274
|
+
details: {
|
|
275
|
+
projection: "deterministic-read-only-tool-brief",
|
|
276
|
+
sourceSha256: ref.sha256,
|
|
277
|
+
sourceBytes: bytes(item.message),
|
|
278
|
+
},
|
|
279
|
+
timestamp: ownValue(item.message, "timestamp"),
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
}));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export function boundedOrientation(
|
|
286
|
+
snapshot: ActiveContextSnapshot,
|
|
287
|
+
sourceRefs: EvidenceRef[],
|
|
288
|
+
): { beforeRefs: EvidenceRef[]; beforeText: string; afterRefs: EvidenceRef[]; afterText: string } {
|
|
289
|
+
const indices = refsInOrder(snapshot, sourceRefs);
|
|
290
|
+
if (!indices) throw new Error("Fold source is not an exact contiguous active range");
|
|
291
|
+
const sourceKeys = new Set(sourceRefs.map(objectRefKey));
|
|
292
|
+
const collect = (candidates: MappedMessage[]): { refs: EvidenceRef[]; text: string } => {
|
|
293
|
+
const refs: EvidenceRef[] = [];
|
|
294
|
+
let text = "[]";
|
|
295
|
+
for (const item of candidates) {
|
|
296
|
+
if (!item.ref || sourceKeys.has(objectRefKey(item.ref))) continue;
|
|
297
|
+
const trial = [...refs, item.ref];
|
|
298
|
+
const encoded = encodedRefs(snapshot, trial);
|
|
299
|
+
if (encoded.length > snapshot.policy.maxOrientationChars) break;
|
|
300
|
+
refs.push(item.ref);
|
|
301
|
+
text = encoded;
|
|
302
|
+
if (refs.length >= snapshot.policy.orientationMessages) break;
|
|
303
|
+
}
|
|
304
|
+
return { refs, text };
|
|
305
|
+
};
|
|
306
|
+
const before = collect(snapshot.mapped.slice(0, indices[0]).reverse()).refs.reverse();
|
|
307
|
+
const beforeText = before.length ? encodedRefs(snapshot, before) : "[]";
|
|
308
|
+
const after = collect(snapshot.mapped.slice(indices.at(-1)! + 1));
|
|
309
|
+
return { beforeRefs: before, beforeText, afterRefs: after.refs, afterText: after.text };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export async function prepareFold(input: {
|
|
313
|
+
candidate: FoldCandidate;
|
|
314
|
+
snapshot: ActiveContextSnapshot;
|
|
315
|
+
state: ActiveContextState;
|
|
316
|
+
generation: number;
|
|
317
|
+
brief?: string;
|
|
318
|
+
briefProvenance?: "supplied" | "deterministic";
|
|
319
|
+
summarize?: (request: Record<string, unknown>, ctx?: unknown) => Promise<Record<string, unknown>>;
|
|
320
|
+
onSummarizerFailure?: (error: unknown) => void;
|
|
321
|
+
ctx?: unknown;
|
|
322
|
+
signal?: AbortSignal;
|
|
323
|
+
now?: () => number;
|
|
324
|
+
}): Promise<PreparedFold> {
|
|
325
|
+
const { candidate, snapshot, state } = input;
|
|
326
|
+
const protectedSource = candidate.kind === "tool-result"
|
|
327
|
+
? toolRefsProtected(candidate.sourceRefs, state, snapshot)
|
|
328
|
+
: refsProtected(candidate.sourceRefs, state, snapshot);
|
|
329
|
+
if (!candidate.sourceRefs.length || candidate.sourceRefs.length > snapshot.policy.maxFoldSourceRefs ||
|
|
330
|
+
refsInOrder(snapshot, candidate.sourceRefs) === null || protectedSource) {
|
|
331
|
+
throw new Error("Fold source is not exact, stale, and unprotected");
|
|
332
|
+
}
|
|
333
|
+
const sourceText = encodedFoldSource(snapshot, state, candidate.parts, candidate.kind);
|
|
334
|
+
const orientation = boundedOrientation(snapshot, candidate.sourceRefs);
|
|
335
|
+
const sourceSha256 = sha256Text(sourceText);
|
|
336
|
+
const beforeSha256 = sha256Text(orientation.beforeText);
|
|
337
|
+
const afterSha256 = sha256Text(orientation.afterText);
|
|
338
|
+
const candidateId = sha256Value({
|
|
339
|
+
kind: candidate.kind,
|
|
340
|
+
parts: candidate.parts.map(normalizedPart),
|
|
341
|
+
sourceSha256,
|
|
342
|
+
beforeSha256,
|
|
343
|
+
afterSha256,
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
let brief: string;
|
|
347
|
+
let provenance: BriefProvenance;
|
|
348
|
+
if (input.brief !== undefined) {
|
|
349
|
+
if (!usefulBrief(input.brief, snapshot.policy.maxBriefChars, snapshot.toolName)) {
|
|
350
|
+
throw new Error(`Supplied brief must be non-structural and at most ${snapshot.policy.maxBriefChars} characters`);
|
|
351
|
+
}
|
|
352
|
+
brief = input.brief.trim();
|
|
353
|
+
provenance = { kind: input.briefProvenance ?? "supplied" };
|
|
354
|
+
} else {
|
|
355
|
+
let modelBrief: { brief: string; provenance: BriefProvenance } | null = null;
|
|
356
|
+
if (input.summarize) {
|
|
357
|
+
const controller = new AbortController();
|
|
358
|
+
const relayAbort = (): void => controller.abort();
|
|
359
|
+
input.signal?.addEventListener("abort", relayAbort, { once: true });
|
|
360
|
+
let timeout: ReturnType<typeof setTimeout> | undefined;
|
|
361
|
+
try {
|
|
362
|
+
if (bytes(sourceText) > snapshot.policy.maxSourceChars) {
|
|
363
|
+
throw new Error("Fold source exceeds the bounded model-summary request");
|
|
364
|
+
}
|
|
365
|
+
const timed = new Promise<never>((_resolve, reject) => {
|
|
366
|
+
timeout = setTimeout(() => {
|
|
367
|
+
controller.abort();
|
|
368
|
+
reject(new Error(`Context brief exceeded ${snapshot.policy.briefTimeoutMs}ms`));
|
|
369
|
+
}, snapshot.policy.briefTimeoutMs);
|
|
370
|
+
});
|
|
371
|
+
const result = await Promise.race([input.summarize({
|
|
372
|
+
candidateId,
|
|
373
|
+
sourceRefs: clone(candidate.sourceRefs),
|
|
374
|
+
sourceText,
|
|
375
|
+
sourceSha256,
|
|
376
|
+
beforeRefs: clone(orientation.beforeRefs),
|
|
377
|
+
beforeText: orientation.beforeText,
|
|
378
|
+
beforeSha256,
|
|
379
|
+
afterRefs: clone(orientation.afterRefs),
|
|
380
|
+
afterText: orientation.afterText,
|
|
381
|
+
afterSha256,
|
|
382
|
+
maxBriefChars: snapshot.policy.maxBriefChars,
|
|
383
|
+
signal: controller.signal,
|
|
384
|
+
}, input.ctx), timed]);
|
|
385
|
+
const generated = typeof result?.brief === "string" ? result.brief.trim() : "";
|
|
386
|
+
const digest = result?.launchContractDigest;
|
|
387
|
+
if (!usefulBrief(generated, snapshot.policy.maxBriefChars, snapshot.toolName) ||
|
|
388
|
+
typeof result?.provider !== "string" || !result.provider ||
|
|
389
|
+
typeof result?.model !== "string" || !result.model ||
|
|
390
|
+
typeof result?.effort !== "string" || !result.effort || result.toolCalls !== 0 ||
|
|
391
|
+
(digest !== undefined && (typeof digest !== "string" || !/^[a-f0-9]{64}$/.test(digest)))) {
|
|
392
|
+
throw new Error("Model context brief attribution, zero-tool, digest, or usefulness contract drift");
|
|
393
|
+
}
|
|
394
|
+
modelBrief = {
|
|
395
|
+
brief: generated,
|
|
396
|
+
provenance: {
|
|
397
|
+
kind: "model",
|
|
398
|
+
provider: result.provider,
|
|
399
|
+
model: result.model,
|
|
400
|
+
effort: result.effort,
|
|
401
|
+
...(typeof digest === "string" ? { launchContractDigest: digest } : {}),
|
|
402
|
+
},
|
|
403
|
+
};
|
|
404
|
+
} catch (error) {
|
|
405
|
+
if (input.signal?.aborted) throw error;
|
|
406
|
+
input.onSummarizerFailure?.(error);
|
|
407
|
+
} finally {
|
|
408
|
+
clearTimeout(timeout);
|
|
409
|
+
input.signal?.removeEventListener("abort", relayAbort);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
if (modelBrief) {
|
|
413
|
+
brief = modelBrief.brief;
|
|
414
|
+
provenance = modelBrief.provenance;
|
|
415
|
+
} else {
|
|
416
|
+
brief = deterministicChapterCandidateBrief(snapshot, candidate);
|
|
417
|
+
provenance = { kind: "deterministic" };
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const id = foldIdFor(candidate.kind, candidate.parts);
|
|
422
|
+
const provisional: ActiveFold = {
|
|
423
|
+
id,
|
|
424
|
+
kind: candidate.kind,
|
|
425
|
+
parentId: null,
|
|
426
|
+
parts: clone(candidate.parts),
|
|
427
|
+
brief,
|
|
428
|
+
provenance,
|
|
429
|
+
sourceSha256: sha256Value(candidate.sourceRefs),
|
|
430
|
+
sourceChars: 1,
|
|
431
|
+
placeholderChars: 1,
|
|
432
|
+
createdAt: (input.now ?? Date.now)(),
|
|
433
|
+
};
|
|
434
|
+
const sourceMessages = renderFoldParts(candidate.parts, state, snapshot);
|
|
435
|
+
const provisionalState = stateWithNestedFold(state, provisional);
|
|
436
|
+
const replacementFold = provisionalState.folds.find((item) => item.id === id)!;
|
|
437
|
+
const replacementMessages = renderFold(replacementFold, provisionalState, snapshot);
|
|
438
|
+
if (!sourceMessages || !replacementMessages) throw new Error("Fold rendering drifted before measurement");
|
|
439
|
+
const sourceBytes = bytes(sourceMessages);
|
|
440
|
+
const replacementBytes = bytes(replacementMessages);
|
|
441
|
+
if (candidate.kind === "consolidation" && replacementBytes >= sourceBytes) {
|
|
442
|
+
throw new Error("Consolidation brief would not materially reduce its rendered child placeholders");
|
|
443
|
+
}
|
|
444
|
+
const fold: ActiveFold = {
|
|
445
|
+
...provisional,
|
|
446
|
+
sourceChars: sourceBytes,
|
|
447
|
+
placeholderChars: replacementBytes,
|
|
448
|
+
};
|
|
449
|
+
return {
|
|
450
|
+
id,
|
|
451
|
+
sessionId: snapshot.sessionId,
|
|
452
|
+
generation: input.generation,
|
|
453
|
+
branchSha256: branchSha256(snapshot, [
|
|
454
|
+
...orientation.beforeRefs,
|
|
455
|
+
...candidate.sourceRefs,
|
|
456
|
+
...orientation.afterRefs,
|
|
457
|
+
]),
|
|
458
|
+
topologySha256: topologySha256(state),
|
|
459
|
+
protectionSha256: protectionSha256(state),
|
|
460
|
+
sourceRefs: clone(candidate.sourceRefs),
|
|
461
|
+
sourceSha256,
|
|
462
|
+
beforeRefs: clone(orientation.beforeRefs),
|
|
463
|
+
beforeSha256,
|
|
464
|
+
afterRefs: clone(orientation.afterRefs),
|
|
465
|
+
afterSha256,
|
|
466
|
+
fold,
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
export function renderFoldParts(
|
|
471
|
+
parts: FoldPart[],
|
|
472
|
+
state: ActiveContextState,
|
|
473
|
+
snapshot: ActiveContextSnapshot,
|
|
474
|
+
): unknown[] | null {
|
|
475
|
+
const byId = foldMap(state);
|
|
476
|
+
const output: unknown[] = [];
|
|
477
|
+
for (const part of parts) {
|
|
478
|
+
if (part.kind === "raw") {
|
|
479
|
+
const item = exactMapped(snapshot, part.ref);
|
|
480
|
+
if (!item) return null;
|
|
481
|
+
output.push(clone(item.message));
|
|
482
|
+
} else {
|
|
483
|
+
const child = byId.get(part.foldId);
|
|
484
|
+
if (!child) return null;
|
|
485
|
+
const rendered = renderFold(child, state, snapshot);
|
|
486
|
+
if (!rendered) return null;
|
|
487
|
+
output.push(...rendered);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return output;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
export function stateWithNestedFold(state: ActiveContextState, fold: ActiveFold): ActiveContextState {
|
|
494
|
+
const childIds = new Set(childFoldIds(fold));
|
|
495
|
+
if (state.folds.some((item) => item.id === fold.id)) throw new Error("Prepared fold already exists");
|
|
496
|
+
const folds = state.folds.map((item) => childIds.has(item.id) ? { ...item, parentId: fold.id } : item);
|
|
497
|
+
folds.push(clone(fold));
|
|
498
|
+
const collapse = new Set<string>(childIds);
|
|
499
|
+
for (const childId of childIds) for (const id of descendantIds(state, childId)) collapse.add(id);
|
|
500
|
+
return {
|
|
501
|
+
...state,
|
|
502
|
+
folds: validateFoldForest(folds),
|
|
503
|
+
expanded: state.expanded.filter((id) => !collapse.has(id)),
|
|
504
|
+
tokensSinceToolFold: fold.kind === "tool-result" ? 0 : state.tokensSinceToolFold,
|
|
505
|
+
leases: Object.fromEntries(Object.entries(state.leases)
|
|
506
|
+
.filter(([id]) => !collapse.has(id))),
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
export function preparedPartsStillExact(prepared: PreparedFold, state: ActiveContextState): boolean {
|
|
511
|
+
const byId = foldMap(state);
|
|
512
|
+
const refs: EvidenceRef[] = [];
|
|
513
|
+
for (const part of prepared.fold.parts) {
|
|
514
|
+
if (part.kind === "raw") refs.push(part.ref);
|
|
515
|
+
else {
|
|
516
|
+
const child = byId.get(part.foldId);
|
|
517
|
+
if (!child || child.parentId !== null) return false;
|
|
518
|
+
refs.push(...flattenFoldRefs(child, state));
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return stableStringify(refs) === stableStringify(prepared.sourceRefs) &&
|
|
522
|
+
prepared.fold.id === foldIdFor(prepared.fold.kind, prepared.fold.parts);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
export function preparedMatchesCandidate(prepared: PreparedFold, candidate: FoldCandidate | null): boolean {
|
|
526
|
+
return Boolean(candidate) && stableStringify({
|
|
527
|
+
kind: prepared.fold.kind,
|
|
528
|
+
parts: prepared.fold.parts.map(normalizedPart),
|
|
529
|
+
sourceRefs: prepared.sourceRefs,
|
|
530
|
+
}) === stableStringify({
|
|
531
|
+
kind: candidate!.kind,
|
|
532
|
+
parts: candidate!.parts.map(normalizedPart),
|
|
533
|
+
sourceRefs: candidate!.sourceRefs,
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
export function preparedFoldError(input: {
|
|
538
|
+
prepared: PreparedFold;
|
|
539
|
+
snapshot: ActiveContextSnapshot;
|
|
540
|
+
state: ActiveContextState;
|
|
541
|
+
generation: number;
|
|
542
|
+
ratio?: number | null;
|
|
543
|
+
}): string | null {
|
|
544
|
+
const { prepared, snapshot, state } = input;
|
|
545
|
+
if (prepared.sessionId !== snapshot.sessionId) return "session drift";
|
|
546
|
+
if (prepared.generation !== input.generation) return "generation drift";
|
|
547
|
+
if (prepared.topologySha256 !== topologySha256(state)) return "topology drift";
|
|
548
|
+
if (prepared.protectionSha256 !== protectionSha256(state)) return "protection drift";
|
|
549
|
+
if (!preparedPartsStillExact(prepared, state)) return "prepared parts drift";
|
|
550
|
+
const indices = refsInOrder(snapshot, prepared.sourceRefs);
|
|
551
|
+
if (!indices) return "source mapping drift";
|
|
552
|
+
let sourceText: string;
|
|
553
|
+
try { sourceText = encodedFoldSource(snapshot, state, prepared.fold.parts, prepared.fold.kind); }
|
|
554
|
+
catch { return "source hash drift"; }
|
|
555
|
+
if (sha256Text(sourceText) !== prepared.sourceSha256) return "source hash drift";
|
|
556
|
+
let orientation;
|
|
557
|
+
try { orientation = boundedOrientation(snapshot, prepared.sourceRefs); }
|
|
558
|
+
catch { return "orientation drift"; }
|
|
559
|
+
if (stableStringify(orientation.beforeRefs) !== stableStringify(prepared.beforeRefs) ||
|
|
560
|
+
stableStringify(orientation.afterRefs) !== stableStringify(prepared.afterRefs) ||
|
|
561
|
+
sha256Text(orientation.beforeText) !== prepared.beforeSha256 ||
|
|
562
|
+
sha256Text(orientation.afterText) !== prepared.afterSha256) return "orientation drift";
|
|
563
|
+
if (prepared.branchSha256 !== branchSha256(snapshot, [
|
|
564
|
+
...prepared.beforeRefs, ...prepared.sourceRefs, ...prepared.afterRefs,
|
|
565
|
+
])) return "branch drift";
|
|
566
|
+
const toolSource = prepared.fold.kind === "tool-result";
|
|
567
|
+
if (indices.some((index) => toolSource
|
|
568
|
+
? snapshot.toolProtectedIndices.has(index)
|
|
569
|
+
: snapshot.protectedIndices.has(index))) return "fresh-tail drift";
|
|
570
|
+
if (toolSource
|
|
571
|
+
? toolRefsProtected(prepared.sourceRefs, state, snapshot)
|
|
572
|
+
: refsProtected(prepared.sourceRefs, state, snapshot)) return "source became protected";
|
|
573
|
+
if (Object.prototype.hasOwnProperty.call(input, "ratio")) {
|
|
574
|
+
if (input.ratio === null || typeof input.ratio !== "number" || !Number.isFinite(input.ratio)) {
|
|
575
|
+
return "current pressure unavailable";
|
|
576
|
+
}
|
|
577
|
+
if (!preparedMatchesCandidate(prepared, selectAutomaticCandidate(snapshot, state, input.ratio))) {
|
|
578
|
+
return "automatic candidate drift";
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
return null;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
export function descendantIds(state: ActiveContextState, id: string): Set<string> {
|
|
585
|
+
const byId = foldMap(state);
|
|
586
|
+
const descendants = new Set<string>();
|
|
587
|
+
const visit = (foldId: string): void => {
|
|
588
|
+
for (const child of childFoldIds(byId.get(foldId)!)) {
|
|
589
|
+
descendants.add(child);
|
|
590
|
+
visit(child);
|
|
591
|
+
}
|
|
592
|
+
};
|
|
593
|
+
if (byId.has(id)) visit(id);
|
|
594
|
+
return descendants;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
export function commitPreparedFold(input: {
|
|
598
|
+
prepared: PreparedFold;
|
|
599
|
+
snapshot: ActiveContextSnapshot;
|
|
600
|
+
state: ActiveContextState;
|
|
601
|
+
generation: number;
|
|
602
|
+
}): ActiveContextState {
|
|
603
|
+
const error = preparedFoldError(input);
|
|
604
|
+
if (error) throw new Error(`Prepared active-context fold discarded: ${error}`);
|
|
605
|
+
const nested = stateWithNestedFold(input.state, input.prepared.fold);
|
|
606
|
+
const next = clearPrepared({ ...nested, revision: input.state.revision + 1 });
|
|
607
|
+
// A fold is not committed unless its provider projection preserves every
|
|
608
|
+
// existing tool-call/output pair atomically. This keeps malformed provider
|
|
609
|
+
// context non-constructible even if a future selector regresses.
|
|
610
|
+
projectActiveContext(input.snapshot, next);
|
|
611
|
+
return next;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
export function setFoldProjectionState(
|
|
615
|
+
state: ActiveContextState,
|
|
616
|
+
id: string,
|
|
617
|
+
projection: "folded" | "expanded",
|
|
618
|
+
): ActiveContextState {
|
|
619
|
+
const fold = state.folds.find((item) => item.id === id);
|
|
620
|
+
if (!fold) throw new Error(`Unknown active-context fold ${id}`);
|
|
621
|
+
const expanded = new Set(state.expanded);
|
|
622
|
+
if (projection === "expanded") {
|
|
623
|
+
if (fold.parentId && !expanded.has(fold.parentId)) {
|
|
624
|
+
throw new Error(`Expand parent ${fold.parentId} before child ${id}`);
|
|
625
|
+
}
|
|
626
|
+
expanded.add(id);
|
|
627
|
+
for (const descendant of descendantIds(state, id)) expanded.delete(descendant);
|
|
628
|
+
} else {
|
|
629
|
+
expanded.delete(id);
|
|
630
|
+
for (const descendant of descendantIds(state, id)) expanded.delete(descendant);
|
|
631
|
+
}
|
|
632
|
+
return clearPrepared({ ...state, revision: state.revision + 1, expanded: [...expanded] });
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
export function withExpandLease(state: ActiveContextState, id: string): ActiveContextState {
|
|
636
|
+
const leases = { ...state.leases, [id]: EXPAND_LEASE_GENERATIONS };
|
|
637
|
+
const entries = Object.entries(leases);
|
|
638
|
+
if (entries.length > MAX_EXPAND_LEASES) {
|
|
639
|
+
entries.sort(([leftId, left], [rightId, right]) => left - right || leftId.localeCompare(rightId));
|
|
640
|
+
delete leases[entries[0][0]];
|
|
641
|
+
}
|
|
642
|
+
return { ...state, leases };
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
export function requireActiveFold(
|
|
646
|
+
snapshot: ActiveContextSnapshot,
|
|
647
|
+
state: ActiveContextState,
|
|
648
|
+
id: string,
|
|
649
|
+
): ActiveFold {
|
|
650
|
+
const fold = state.folds.find((item) => item.id === id);
|
|
651
|
+
if (!fold || !foldInterval(fold, state, snapshot)) {
|
|
652
|
+
throw new Error(`Active-context fold ${id} is not present in the current Pi context event`);
|
|
653
|
+
}
|
|
654
|
+
return fold;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
export function protectEvidence(
|
|
658
|
+
snapshot: ActiveContextSnapshot,
|
|
659
|
+
state: ActiveContextState,
|
|
660
|
+
ids: string[],
|
|
661
|
+
protect: boolean,
|
|
662
|
+
): ActiveContextState {
|
|
663
|
+
const refs = ids.flatMap((id) => {
|
|
664
|
+
const fold = state.folds.find((item) => item.id === id);
|
|
665
|
+
if (fold) return flattenFoldRefs(requireActiveFold(snapshot, state, id), state);
|
|
666
|
+
const item = snapshot.mapped.find((candidate) => candidate.ref?.entryId === id);
|
|
667
|
+
if (!item?.ref) throw new Error(`Unknown active-context source ${id}`);
|
|
668
|
+
return [item.ref];
|
|
669
|
+
});
|
|
670
|
+
const byKey = new Map(state.protected.map((ref) => [objectRefKey(ref), ref]));
|
|
671
|
+
for (const ref of refs) protect ? byKey.set(objectRefKey(ref), ref) : byKey.delete(objectRefKey(ref));
|
|
672
|
+
return clearPrepared({
|
|
673
|
+
...state,
|
|
674
|
+
revision: state.revision + 1,
|
|
675
|
+
protected: [...byKey.values()],
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
export function siblingIds(fold: ActiveFold, state: ActiveContextState, snapshot: ActiveContextSnapshot): string[] {
|
|
680
|
+
if (fold.parentId) {
|
|
681
|
+
const parent = state.folds.find((item) => item.id === fold.parentId);
|
|
682
|
+
return parent ? childFoldIds(parent) : [];
|
|
683
|
+
}
|
|
684
|
+
return orderedRoots(state, snapshot).map((item) => item.fold.id);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
export function foldNavigation(fold: ActiveFold, state: ActiveContextState, snapshot: ActiveContextSnapshot): {
|
|
688
|
+
previous: string | null;
|
|
689
|
+
next: string | null;
|
|
690
|
+
} {
|
|
691
|
+
const siblings = siblingIds(fold, state, snapshot);
|
|
692
|
+
const index = siblings.indexOf(fold.id);
|
|
693
|
+
return {
|
|
694
|
+
previous: index > 0 ? siblings[index - 1] : null,
|
|
695
|
+
next: index >= 0 && index + 1 < siblings.length ? siblings[index + 1] : null,
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
export function foldPlaceholder(fold: ActiveFold, state: ActiveContextState, snapshot: ActiveContextSnapshot): string {
|
|
700
|
+
const navigation = foldNavigation(fold, state, snapshot);
|
|
701
|
+
const parent = fold.parentId ?? "root";
|
|
702
|
+
return [
|
|
703
|
+
`[${activeContextBrand(snapshot.brandNoun)} fold ${fold.id}]`,
|
|
704
|
+
fold.brief,
|
|
705
|
+
`Topology: kind=${fold.kind}; parent=${parent}; children=${childFoldIds(fold).length}; ` +
|
|
706
|
+
`previous=${navigation.previous ?? "none"}; next=${navigation.next ?? "none"}.`,
|
|
707
|
+
`Expand exactly: ${snapshot.toolName} {"action":"expand","id":"${fold.id}"}`,
|
|
708
|
+
`List/page exactly: ${snapshot.toolName} {"action":"status"}`,
|
|
709
|
+
].join("\n");
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
export function renderFold(
|
|
713
|
+
fold: ActiveFold,
|
|
714
|
+
state: ActiveContextState,
|
|
715
|
+
snapshot: ActiveContextSnapshot,
|
|
716
|
+
): unknown[] | null {
|
|
717
|
+
const refs = flattenFoldRefs(fold, state);
|
|
718
|
+
const indices = refsInOrder(snapshot, refs);
|
|
719
|
+
if (!indices) return null;
|
|
720
|
+
const mustReveal = state.expanded.includes(fold.id) || (fold.kind === "tool-result"
|
|
721
|
+
? toolRefsProtected(refs, state, snapshot)
|
|
722
|
+
: refsProtected(refs, state, snapshot));
|
|
723
|
+
if (!mustReveal) {
|
|
724
|
+
const first = snapshot.messages[indices[0]] as Record<string, unknown>;
|
|
725
|
+
const text = foldPlaceholder(fold, state, snapshot);
|
|
726
|
+
if (fold.kind === "tool-result") return indices.map((index) => ({
|
|
727
|
+
...clone(snapshot.messages[index] as Record<string, unknown>),
|
|
728
|
+
content: [{ type: "text", text }],
|
|
729
|
+
}));
|
|
730
|
+
return [{
|
|
731
|
+
role: "custom",
|
|
732
|
+
customType: `${snapshot.entryTypePrefix}-fold`,
|
|
733
|
+
content: text,
|
|
734
|
+
display: false,
|
|
735
|
+
details: { source: activeContextSource(snapshot.entryTypePrefix), foldId: fold.id },
|
|
736
|
+
timestamp: typeof first?.timestamp === "number" ? first.timestamp : 0,
|
|
737
|
+
}];
|
|
738
|
+
}
|
|
739
|
+
return renderFoldParts(fold.parts, state, snapshot);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
export interface ToolLinkageCount {
|
|
743
|
+
calls: number;
|
|
744
|
+
results: number;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
export function toolLinkageCounts(messages: unknown[]): Map<string, ToolLinkageCount> {
|
|
748
|
+
const counts = new Map<string, ToolLinkageCount>();
|
|
749
|
+
const increment = (id: string, field: keyof ToolLinkageCount): void => {
|
|
750
|
+
const current = counts.get(id) ?? { calls: 0, results: 0 };
|
|
751
|
+
current[field] += 1;
|
|
752
|
+
counts.set(id, current);
|
|
753
|
+
};
|
|
754
|
+
for (let index = 0; index < messages.length; index += 1) {
|
|
755
|
+
const message = messages[index];
|
|
756
|
+
const role = messageRole(message);
|
|
757
|
+
if (role === "assistant") {
|
|
758
|
+
const content = denseOwnArrayValues(ownValue(message, "content"));
|
|
759
|
+
if (!content) continue;
|
|
760
|
+
for (const part of content) {
|
|
761
|
+
if (ownValue(part, "type") !== "toolCall") continue;
|
|
762
|
+
const id = ownValue(part, "id");
|
|
763
|
+
if (typeof id === "string" && id) increment(id, "calls");
|
|
764
|
+
}
|
|
765
|
+
} else if (role === "toolResult") {
|
|
766
|
+
const id = ownValue(message, "toolCallId");
|
|
767
|
+
if (typeof id === "string" && id) increment(id, "results");
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
return counts;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
export function assertProjectionPreservesToolLinkage(source: unknown[], projected: unknown[]): void {
|
|
774
|
+
const sourceLinkage = toolLinkageCounts(source);
|
|
775
|
+
const projectedLinkage = toolLinkageCounts(projected);
|
|
776
|
+
const ids = new Set([...sourceLinkage.keys(), ...projectedLinkage.keys()]);
|
|
777
|
+
for (const id of ids) {
|
|
778
|
+
const original = sourceLinkage.get(id) ?? { calls: 0, results: 0 };
|
|
779
|
+
const visible = projectedLinkage.get(id) ?? { calls: 0, results: 0 };
|
|
780
|
+
const uniquelyClosed = original.calls === 1 && original.results === 1;
|
|
781
|
+
const valid = uniquelyClosed
|
|
782
|
+
? (visible.calls === 1 && visible.results === 1) ||
|
|
783
|
+
(visible.calls === 0 && visible.results === 0)
|
|
784
|
+
: visible.calls === original.calls && visible.results === original.results;
|
|
785
|
+
if (!valid) {
|
|
786
|
+
throw new Error(
|
|
787
|
+
`Active-context projection split tool call/result linkage for ${id.slice(0, 120)}: ` +
|
|
788
|
+
`${original.calls}/${original.results} became ${visible.calls}/${visible.results}`,
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
export function projectActiveContext(
|
|
795
|
+
snapshot: ActiveContextSnapshot,
|
|
796
|
+
state: ActiveContextState,
|
|
797
|
+
): unknown[] {
|
|
798
|
+
validateFoldForest(state.folds);
|
|
799
|
+
const replacements = new Map<number, { end: number; messages: unknown[] }>();
|
|
800
|
+
for (const root of orderedRoots(state, snapshot)) {
|
|
801
|
+
const rendered = renderFold(root.fold, state, snapshot);
|
|
802
|
+
if (rendered) replacements.set(root.start, { end: root.end, messages: rendered });
|
|
803
|
+
}
|
|
804
|
+
const output: unknown[] = [];
|
|
805
|
+
for (let index = 0; index < snapshot.messages.length;) {
|
|
806
|
+
const replacement = replacements.get(index);
|
|
807
|
+
if (replacement) {
|
|
808
|
+
output.push(...replacement.messages);
|
|
809
|
+
index = replacement.end + 1;
|
|
810
|
+
} else {
|
|
811
|
+
output.push(clone(snapshot.messages[index]));
|
|
812
|
+
index += 1;
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
assertProjectionPreservesToolLinkage(snapshot.messages, output);
|
|
816
|
+
return output;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
export function foldStatusRow(fold: ActiveFold, state: ActiveContextState, snapshot: ActiveContextSnapshot): Record<string, unknown> {
|
|
820
|
+
const navigation = foldNavigation(fold, state, snapshot);
|
|
821
|
+
const interval = foldInterval(fold, state, snapshot);
|
|
822
|
+
const refs = flattenFoldRefs(fold, state);
|
|
823
|
+
const allSourceIds = refs.map((ref) => ref.entryId);
|
|
824
|
+
const sourceIds = allSourceIds.slice(0, 64);
|
|
825
|
+
const blocked = fold.kind === "tool-result"
|
|
826
|
+
? toolRefsProtected(refs, state, snapshot)
|
|
827
|
+
: refsProtected(refs, state, snapshot);
|
|
828
|
+
const projection = state.expanded.includes(fold.id) ? "expanded" : "folded";
|
|
829
|
+
return {
|
|
830
|
+
id: fold.id,
|
|
831
|
+
kind: fold.kind,
|
|
832
|
+
parent: fold.parentId,
|
|
833
|
+
children: childFoldIds(fold),
|
|
834
|
+
previous: navigation.previous,
|
|
835
|
+
next: navigation.next,
|
|
836
|
+
state: projection,
|
|
837
|
+
active: Boolean(interval),
|
|
838
|
+
protected: blocked,
|
|
839
|
+
sourceIds,
|
|
840
|
+
sourceCount: allSourceIds.length,
|
|
841
|
+
sourceIdsTruncated: sourceIds.length < allSourceIds.length,
|
|
842
|
+
sourceSha256: fold.sourceSha256,
|
|
843
|
+
actions: {
|
|
844
|
+
primary: projection === "folded"
|
|
845
|
+
? { action: "expand", id: fold.id }
|
|
846
|
+
: { action: "refold", id: fold.id },
|
|
847
|
+
expand: { action: "expand", id: fold.id },
|
|
848
|
+
refold: { action: "refold", id: fold.id },
|
|
849
|
+
protect: { action: "protect", ids: [fold.id] },
|
|
850
|
+
},
|
|
851
|
+
};
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
/** Every fold, nested ones included, in transcript order with each parent before its children. */
|
|
855
|
+
export function orderedFoldTree(state: ActiveContextState, snapshot: ActiveContextSnapshot): ActiveFold[] {
|
|
856
|
+
const byId = foldMap(state);
|
|
857
|
+
const ordered: ActiveFold[] = [];
|
|
858
|
+
const seen = new Set<string>();
|
|
859
|
+
const visit = (id: string): void => {
|
|
860
|
+
const fold = byId.get(id);
|
|
861
|
+
if (!fold || seen.has(id)) return;
|
|
862
|
+
seen.add(id);
|
|
863
|
+
ordered.push(fold);
|
|
864
|
+
for (const child of childFoldIds(fold)) visit(child);
|
|
865
|
+
};
|
|
866
|
+
for (const root of orderedRoots(state, snapshot)) visit(root.fold.id);
|
|
867
|
+
return ordered;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
export function foldDepth(state: ActiveContextState, fold: ActiveFold): number {
|
|
871
|
+
const byId = foldMap(state);
|
|
872
|
+
let depth = 0;
|
|
873
|
+
for (let parentId = fold.parentId; parentId; parentId = byId.get(parentId)?.parentId ?? null) depth += 1;
|
|
874
|
+
return depth;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
export function foldTreeDetail(
|
|
878
|
+
snapshot: ActiveContextSnapshot,
|
|
879
|
+
state: ActiveContextState,
|
|
880
|
+
): Array<Record<string, unknown>> {
|
|
881
|
+
return orderedFoldTree(state, snapshot).map((fold) => ({
|
|
882
|
+
id: fold.id,
|
|
883
|
+
kind: fold.kind,
|
|
884
|
+
depth: foldDepth(state, fold),
|
|
885
|
+
parentId: fold.parentId,
|
|
886
|
+
brief: fold.brief,
|
|
887
|
+
sourceCount: flattenFoldRefs(fold, state).length,
|
|
888
|
+
state: state.expanded.includes(fold.id) ? "expanded" : "folded",
|
|
889
|
+
peekable: true,
|
|
890
|
+
}));
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
export function activeContextStatus(
|
|
894
|
+
snapshot: ActiveContextSnapshot,
|
|
895
|
+
state: ActiveContextState,
|
|
896
|
+
offset = 0,
|
|
897
|
+
limit = 40,
|
|
898
|
+
maximumChapterSourceRefs = Number.MAX_SAFE_INTEGER,
|
|
899
|
+
): Record<string, unknown> {
|
|
900
|
+
const roots = orderedRoots(state, snapshot).map((item) => item.fold.id);
|
|
901
|
+
const ordered = orderedFoldTree(state, snapshot);
|
|
902
|
+
const selected = ordered.slice(offset, offset + limit);
|
|
903
|
+
const protectedKeys = explicitProtectedKeys(state);
|
|
904
|
+
const objects = snapshot.mapped.flatMap((item) => item.ref ? [{
|
|
905
|
+
id: item.ref.entryId,
|
|
906
|
+
role: item.ref.role,
|
|
907
|
+
sha256: item.ref.sha256,
|
|
908
|
+
position: item.index,
|
|
909
|
+
stale: !snapshot.protectedIndices.has(item.index),
|
|
910
|
+
protected: snapshot.protectedIndices.has(item.index) || protectedKeys.has(objectRefKey(item.ref)),
|
|
911
|
+
staleToolResult: item.ref.role === "toolResult" &&
|
|
912
|
+
!snapshot.toolProtectedIndices.has(item.index) && !protectedKeys.has(objectRefKey(item.ref)) &&
|
|
913
|
+
resultCall(snapshot, item.index, true) !== null,
|
|
914
|
+
}] : []);
|
|
915
|
+
const selectedObjects = objects.slice(offset, offset + limit);
|
|
916
|
+
const eligibleChapter = selectAutomaticChapter(snapshot, state, maximumChapterSourceRefs);
|
|
917
|
+
const eligibleSourceIds = eligibleChapter?.sourceRefs.map((ref) => ref.entryId) ?? [];
|
|
918
|
+
const eligibleEndpoints = eligibleSourceIds.length
|
|
919
|
+
? [...new Set([eligibleSourceIds[0], eligibleSourceIds.at(-1)!])]
|
|
920
|
+
: [];
|
|
921
|
+
return {
|
|
922
|
+
version: 1,
|
|
923
|
+
service: "active-context-folding",
|
|
924
|
+
roots,
|
|
925
|
+
folds: selected.map((fold) => foldStatusRow(fold, state, snapshot)),
|
|
926
|
+
offset,
|
|
927
|
+
nextOffset: offset + selected.length < ordered.length ? offset + selected.length : null,
|
|
928
|
+
totalFolds: ordered.length,
|
|
929
|
+
protectedSourceIds: state.protected.flatMap((ref) => exactMapped(snapshot, ref) ? [ref.entryId] : []),
|
|
930
|
+
objects: selectedObjects,
|
|
931
|
+
totalObjects: objects.length,
|
|
932
|
+
nextObjectOffset: offset + selectedObjects.length < objects.length ? offset + selectedObjects.length : null,
|
|
933
|
+
eligibleChapter: eligibleChapter ? {
|
|
934
|
+
kind: "chapter",
|
|
935
|
+
sourceCount: eligibleSourceIds.length,
|
|
936
|
+
sourceIds: eligibleSourceIds.slice(0, 64),
|
|
937
|
+
sourceIdsTruncated: eligibleSourceIds.length > 64,
|
|
938
|
+
startId: eligibleSourceIds[0],
|
|
939
|
+
endId: eligibleSourceIds.at(-1),
|
|
940
|
+
action: { action: "fold", ids: eligibleEndpoints, brief: "<factual brief, at most 1000 characters>" },
|
|
941
|
+
} : null,
|
|
942
|
+
rawTailMinimumBytes: snapshot.policy.freshBytes,
|
|
943
|
+
currentTurnRequiresBoundary: false,
|
|
944
|
+
actions: {
|
|
945
|
+
status: { action: "status", offset, limit },
|
|
946
|
+
fold: { action: "fold", ids: ["<source-or-fold-id>"], brief: "<optional factual brief, at most 1000 characters>" },
|
|
947
|
+
protect: { action: "protect", ids: ["<source-or-fold-id>"] },
|
|
948
|
+
},
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
export function visibleCollapsedFolds(
|
|
953
|
+
state: ActiveContextState,
|
|
954
|
+
snapshot: ActiveContextSnapshot,
|
|
955
|
+
): ActiveFold[] {
|
|
956
|
+
const byId = foldMap(state);
|
|
957
|
+
const output: ActiveFold[] = [];
|
|
958
|
+
const visit = (fold: ActiveFold): void => {
|
|
959
|
+
const refs = flattenFoldRefs(fold, state);
|
|
960
|
+
if (!foldInterval(fold, state, snapshot)) return;
|
|
961
|
+
const protectedSource = fold.kind === "tool-result"
|
|
962
|
+
? toolRefsProtected(refs, state, snapshot)
|
|
963
|
+
: refsProtected(refs, state, snapshot);
|
|
964
|
+
if (!state.expanded.includes(fold.id) && !protectedSource) {
|
|
965
|
+
output.push(fold);
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
for (const childId of childFoldIds(fold)) visit(byId.get(childId)!);
|
|
969
|
+
};
|
|
970
|
+
for (const root of orderedRoots(state, snapshot)) visit(root.fold);
|
|
971
|
+
return output;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
export function projectionSlateCandidates(
|
|
975
|
+
state: ActiveContextState,
|
|
976
|
+
snapshot: ActiveContextSnapshot,
|
|
977
|
+
limit = 16,
|
|
978
|
+
): Array<Record<string, unknown>> {
|
|
979
|
+
return visibleCollapsedFolds(state, snapshot).slice(0, limit).flatMap((fold, rank) => {
|
|
980
|
+
const replacement = renderFold(fold, state, snapshot);
|
|
981
|
+
const expandedState = { ...state, expanded: [...state.expanded, fold.id] };
|
|
982
|
+
const source = renderFold(fold, expandedState, snapshot);
|
|
983
|
+
if (!replacement || !source) return [];
|
|
984
|
+
const replacementBytes = bytes(replacement);
|
|
985
|
+
const sourceBytes = bytes(source);
|
|
986
|
+
const saved = sourceBytes - replacementBytes;
|
|
987
|
+
if (saved <= 0) return [];
|
|
988
|
+
const provenanceKind = (fold.provenance as { kind: string }).kind;
|
|
989
|
+
const generator = provenanceKind === "model" || provenanceKind === "luna"
|
|
990
|
+
? "projection-model"
|
|
991
|
+
: provenanceKind === "deterministic" ? "projection-deterministic" : "projection-supplied";
|
|
992
|
+
return [{
|
|
993
|
+
version: 1,
|
|
994
|
+
key: `projection:${fold.id}`,
|
|
995
|
+
kind: "projection",
|
|
996
|
+
domain: "system",
|
|
997
|
+
horizon: "working",
|
|
998
|
+
source_id: fold.id,
|
|
999
|
+
source_version: fold.sourceSha256,
|
|
1000
|
+
route: { tool: snapshot.toolName, arguments: { action: "expand", id: fold.id } },
|
|
1001
|
+
token_cost: Math.max(1, Math.ceil(replacementBytes / 4)),
|
|
1002
|
+
expansion_cost: Math.max(1, Math.ceil(sourceBytes / 4)),
|
|
1003
|
+
rank,
|
|
1004
|
+
score: Math.min(1, saved / sourceBytes),
|
|
1005
|
+
raw_score: saved,
|
|
1006
|
+
confidence: "exact",
|
|
1007
|
+
freshness: "current",
|
|
1008
|
+
locked_owner: false,
|
|
1009
|
+
collapse_key: `projection:${fold.id}`,
|
|
1010
|
+
generator,
|
|
1011
|
+
generator_version: "memory-slate-generators-v2",
|
|
1012
|
+
recency: null,
|
|
1013
|
+
}];
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
export function recoverFoldMessages(input: {
|
|
1018
|
+
foldId: string;
|
|
1019
|
+
state: ActiveContextState;
|
|
1020
|
+
entries: Array<Record<string, unknown>>;
|
|
1021
|
+
sessionId: string;
|
|
1022
|
+
projectEntry?: (entry: Record<string, unknown>) => unknown[];
|
|
1023
|
+
}): unknown[] {
|
|
1024
|
+
const fold = input.state.folds.find((item) => item.id === input.foldId);
|
|
1025
|
+
if (!fold) throw new Error(`Unknown active-context fold ${input.foldId}`);
|
|
1026
|
+
const projectEntry = input.projectEntry ?? sessionEntryMessages;
|
|
1027
|
+
const exact = new Map<string, unknown>();
|
|
1028
|
+
for (const entry of input.entries) {
|
|
1029
|
+
if (typeof entry?.id !== "string") continue;
|
|
1030
|
+
for (const message of projectEntry(entry)) {
|
|
1031
|
+
const ref = evidenceRef(input.sessionId, entry.id, message);
|
|
1032
|
+
exact.set(`${objectRefKey(ref)}:${ref.sha256}`, message);
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
return flattenFoldRefs(fold, input.state).map((ref) => {
|
|
1036
|
+
if (ref.sessionId !== input.sessionId) throw new Error(`Fold source ${ref.entryId} belongs to another session`);
|
|
1037
|
+
const message = exact.get(`${objectRefKey(ref)}:${ref.sha256}`);
|
|
1038
|
+
if (!message || evidenceSha256(message) !== ref.sha256) {
|
|
1039
|
+
throw new Error(`Exact recovery failed for ${ref.entryId}`);
|
|
1040
|
+
}
|
|
1041
|
+
return clone(message);
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
/**
|
|
1046
|
+
* Ephemeral point read of one fold's exact source at any depth. It recovers the same
|
|
1047
|
+
* SHA-256-verified messages expansion restores, bounded for the reply, and changes no
|
|
1048
|
+
* projection: the fold stays collapsed and the durable state is untouched.
|
|
1049
|
+
*/
|
|
1050
|
+
export function peekFoldSource(input: {
|
|
1051
|
+
foldId: string;
|
|
1052
|
+
state: ActiveContextState;
|
|
1053
|
+
entries: Array<Record<string, unknown>>;
|
|
1054
|
+
sessionId: string;
|
|
1055
|
+
maximumBytes?: number;
|
|
1056
|
+
projectEntry?: (entry: Record<string, unknown>) => unknown[];
|
|
1057
|
+
}): Record<string, unknown> {
|
|
1058
|
+
const fold = input.state.folds.find((item) => item.id === input.foldId);
|
|
1059
|
+
if (!fold) throw new Error(`Unknown active-context fold ${input.foldId}`);
|
|
1060
|
+
const messages = recoverFoldMessages({
|
|
1061
|
+
foldId: input.foldId,
|
|
1062
|
+
state: input.state,
|
|
1063
|
+
entries: input.entries,
|
|
1064
|
+
sessionId: input.sessionId,
|
|
1065
|
+
projectEntry: input.projectEntry,
|
|
1066
|
+
});
|
|
1067
|
+
const source = stableStringify(messages);
|
|
1068
|
+
const sourceBytes = bytes(source);
|
|
1069
|
+
const returned = boundedUtf8(source, input.maximumBytes ?? ACTIVE_CONTEXT_POLICY.maxChapterChars);
|
|
1070
|
+
const returnedBytes = bytes(returned);
|
|
1071
|
+
const truncated = returnedBytes < sourceBytes;
|
|
1072
|
+
return {
|
|
1073
|
+
version: 1,
|
|
1074
|
+
action: "peek",
|
|
1075
|
+
id: fold.id,
|
|
1076
|
+
kind: fold.kind,
|
|
1077
|
+
parentId: fold.parentId,
|
|
1078
|
+
depth: foldDepth(input.state, fold),
|
|
1079
|
+
brief: fold.brief,
|
|
1080
|
+
sourceCount: messages.length,
|
|
1081
|
+
sourceSha256: fold.sourceSha256,
|
|
1082
|
+
sourceBytes,
|
|
1083
|
+
returnedBytes,
|
|
1084
|
+
truncated,
|
|
1085
|
+
note: truncated
|
|
1086
|
+
? `Truncated: this reply carries the first ${returnedBytes} of ${sourceBytes} exact source bytes; ` +
|
|
1087
|
+
`expand ${fold.id} to restore the complete source in place.`
|
|
1088
|
+
: "Complete exact source; the fold stayed collapsed and no projection changed.",
|
|
1089
|
+
source: returned,
|
|
1090
|
+
};
|
|
1091
|
+
}
|