pi-fold 3.1.0 → 4.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 +18 -21
- package/extensions/active-context.ts +138 -282
- package/extensions/index.js +5 -1
- package/extensions/lib/curation.ts +130 -35
- package/extensions/lib/editor-ui.ts +133 -25
- package/extensions/lib/measurement.ts +47 -0
- package/extensions/lib/persistence.ts +5 -36
- package/extensions/lib/policy.ts +17 -39
- package/extensions/lib/tool-surface.ts +2 -31
- package/extensions/settings.ts +114 -20
- package/package.json +1 -1
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
explicitProtectedMass,
|
|
47
47
|
budgetOccupancy,
|
|
48
48
|
hardFenceRatio,
|
|
49
|
+
imageMass,
|
|
49
50
|
latestProviderContextMeasurement,
|
|
50
51
|
orderedRoots,
|
|
51
52
|
parseNativeCompactionCompletion,
|
|
@@ -105,6 +106,7 @@ import {
|
|
|
105
106
|
resolveThresholds,
|
|
106
107
|
servingBudgetTokens,
|
|
107
108
|
ESTIMATED_BYTES_PER_TOKEN,
|
|
109
|
+
IMAGE_ESTIMATED_TOKENS,
|
|
108
110
|
entryTypeNamespace,
|
|
109
111
|
MAX_FOLD_SPAN_CHARS,
|
|
110
112
|
DEFAULT_TOOL_FOLD_THRESHOLD,
|
|
@@ -116,10 +118,6 @@ import {
|
|
|
116
118
|
RETIRED_TOOL_ACTIONS,
|
|
117
119
|
AUTO_FOLD_BLACKLIST_DEFAULT,
|
|
118
120
|
USER_RESCUE_MAX_SOURCE_CHARS,
|
|
119
|
-
MEMORY_BODY_CHARS,
|
|
120
|
-
MEMORY_KEY_CHARS,
|
|
121
|
-
MEMORY_KEYS_MAX,
|
|
122
|
-
WORKING_MEMORY_TOOL_ACTIONS,
|
|
123
121
|
} from "./lib/policy.ts";
|
|
124
122
|
import type {
|
|
125
123
|
ActiveContextSnapshot,
|
|
@@ -140,7 +138,7 @@ import {
|
|
|
140
138
|
import {
|
|
141
139
|
contextReceipt,
|
|
142
140
|
foldNoticeText,
|
|
143
|
-
|
|
141
|
+
foldStatusText,
|
|
144
142
|
receiptBlockText,
|
|
145
143
|
withReceipt,
|
|
146
144
|
} from "./lib/curation.ts";
|
|
@@ -247,13 +245,6 @@ export function registerActiveContext(pi: any, options: {
|
|
|
247
245
|
* agent to improve a brief. Either way the agent verbs stay on the tool, so an agent
|
|
248
246
|
* that finds a fold can still annotate what it reads. */
|
|
249
247
|
postFoldNotice?: boolean;
|
|
250
|
-
/** THE WORKING MEMORY (Shane, 2026-08-26): a session-scoped ordered dictionary the
|
|
251
|
-
* agent maintains beside the fold index. `remember` writes or removes an entry,
|
|
252
|
-
* `recall` reads bodies on demand, and the projection carries one table of contents
|
|
253
|
-
* that refreshes at each commit. Off when absent: the digest channel earned its
|
|
254
|
-
* place in a measured gap (running tallies lost 0/6 against native's 6/6 while the
|
|
255
|
-
* index won everything else), and it must stay separable from the index it is not. */
|
|
256
|
-
workingMemory?: boolean;
|
|
257
248
|
}): {
|
|
258
249
|
projectionCandidates: (ctx: any) => Array<Record<string, unknown>>;
|
|
259
250
|
} {
|
|
@@ -268,11 +259,6 @@ export function registerActiveContext(pi: any, options: {
|
|
|
268
259
|
"carrier inviting the agent to improve it");
|
|
269
260
|
}
|
|
270
261
|
const postFoldNotice = options.postFoldNotice ?? false;
|
|
271
|
-
if (options.workingMemory !== undefined && typeof options.workingMemory !== "boolean") {
|
|
272
|
-
throw new Error("workingMemory must be a boolean: it enables the session-scoped " +
|
|
273
|
-
"working-memory dictionary and its two actions, remember and recall");
|
|
274
|
-
}
|
|
275
|
-
const workingMemory = options.workingMemory === true;
|
|
276
262
|
const toolFoldThreshold = options.toolFoldThreshold ?? DEFAULT_TOOL_FOLD_THRESHOLD;
|
|
277
263
|
if (typeof toolFoldThreshold !== "number" || !Number.isFinite(toolFoldThreshold) ||
|
|
278
264
|
toolFoldThreshold < 0 || toolFoldThreshold >= 1) {
|
|
@@ -345,7 +331,6 @@ export function registerActiveContext(pi: any, options: {
|
|
|
345
331
|
const milestoneProjectionType = `${entryTypePrefix}-milestone`;
|
|
346
332
|
const advisoryProjectionType = `${entryTypePrefix}-advisory`;
|
|
347
333
|
const foldNoticeProjectionType = `${entryTypePrefix}-fold-notice`;
|
|
348
|
-
const memoryTocProjectionType = `${entryTypePrefix}-memory-toc`;
|
|
349
334
|
const receiptProjectionType = `${entryTypePrefix}-receipts`;
|
|
350
335
|
const curationProjectionType = `${entryTypePrefix}-curation`;
|
|
351
336
|
const entryNamespace = entryTypeNamespace(entryTypePrefix);
|
|
@@ -353,9 +338,7 @@ export function registerActiveContext(pi: any, options: {
|
|
|
353
338
|
const nativeReceiptEntryType = `${entryNamespace}-native-compaction-receipt`;
|
|
354
339
|
const contextEventEntryType = `${entryNamespace}-context-event`;
|
|
355
340
|
const nativeDecisionEntryType = `${entryNamespace}-native-compaction-decision`;
|
|
356
|
-
const allowedToolActions: readonly ActiveContextToolAction[] =
|
|
357
|
-
? [...ACTIVE_CONTEXT_TOOL_ACTIONS, ...WORKING_MEMORY_TOOL_ACTIONS]
|
|
358
|
-
: ACTIVE_CONTEXT_TOOL_ACTIONS;
|
|
341
|
+
const allowedToolActions: readonly ActiveContextToolAction[] = ACTIVE_CONTEXT_TOOL_ACTIONS;
|
|
359
342
|
const allowedToolActionSet = new Set<string>(allowedToolActions);
|
|
360
343
|
|
|
361
344
|
type AutomaticFailureState = {
|
|
@@ -401,6 +384,7 @@ export function registerActiveContext(pi: any, options: {
|
|
|
401
384
|
lastProviderMeasurement: null as ProviderContextMeasurement | null,
|
|
402
385
|
descriptorWindow: null as number | null,
|
|
403
386
|
lastProjectedChars: null as number | null,
|
|
387
|
+
lastProjectedImages: 0,
|
|
404
388
|
projectionCalibrations: [] as Array<{ chars: number; tokens: number }>,
|
|
405
389
|
lastProjectedEstimate: null as number | null,
|
|
406
390
|
lastProjectedEstimateCalibrated: false,
|
|
@@ -409,6 +393,10 @@ export function registerActiveContext(pi: any, options: {
|
|
|
409
393
|
projectionAnchor: null as {
|
|
410
394
|
tokens: number;
|
|
411
395
|
chars: number;
|
|
396
|
+
/** `chars` less the base64 of any image, which is what the delta is taken on. */
|
|
397
|
+
textChars: number;
|
|
398
|
+
/** Images inside the anchored prefix, each worth IMAGE_ESTIMATED_TOKENS. */
|
|
399
|
+
images: number;
|
|
412
400
|
head: string;
|
|
413
401
|
messageSha256: string;
|
|
414
402
|
sessionId: string;
|
|
@@ -428,6 +416,10 @@ export function registerActiveContext(pi: any, options: {
|
|
|
428
416
|
ledger: emptyLedger(),
|
|
429
417
|
previousDigests: null as string[] | null,
|
|
430
418
|
previousText: null as string | null,
|
|
419
|
+
// The image mass of `previousText`, kept beside it because the provider anchor is
|
|
420
|
+
// built from that text and has no access to the array it came from.
|
|
421
|
+
previousImages: 0,
|
|
422
|
+
previousImageChars: 0,
|
|
431
423
|
lastChange: "append" as ProjectionChange,
|
|
432
424
|
lastPreservedShare: null as number | null,
|
|
433
425
|
sinceHandoff: [] as Array<{
|
|
@@ -491,9 +483,6 @@ export function registerActiveContext(pi: any, options: {
|
|
|
491
483
|
};
|
|
492
484
|
|
|
493
485
|
const advisory = {
|
|
494
|
-
hardFenceNoticeKey: null as string | null,
|
|
495
|
-
hardFenceReleaseSessionId: null as string | null,
|
|
496
|
-
hardFenceReleasedProjectionKeys: new Set<string>(),
|
|
497
486
|
};
|
|
498
487
|
|
|
499
488
|
// EPHEMERAL PEEK (Build 4b): tool call ids whose peek asked for one-read
|
|
@@ -632,15 +621,43 @@ export function registerActiveContext(pi: any, options: {
|
|
|
632
621
|
lifecycle.generation === expectedGeneration && persistence.state?.sessionId === sessionId &&
|
|
633
622
|
(!ctx || contextSessionMatches(ctx, sessionId));
|
|
634
623
|
|
|
624
|
+
/**
|
|
625
|
+
* THE ONE LINE THAT IS ALWAYS ON SCREEN, so it answers the question a person actually
|
|
626
|
+
* has: how full am I, and is anything about to happen.
|
|
627
|
+
*
|
|
628
|
+
* It used to read `pi_fold_context folds: 3 · provider 187234/272000`. That led with the
|
|
629
|
+
* AGENT TOOL'S NAME, which a person cannot call and does not care about, and then spent
|
|
630
|
+
* its remaining width on two raw counts they would have to divide themselves. The brand
|
|
631
|
+
* leads now, occupancy is a percentage of the serving budget, and the commit point is
|
|
632
|
+
* named only when it is close enough to matter.
|
|
633
|
+
*/
|
|
635
634
|
const updateStatus = (ctx: any): void => {
|
|
636
635
|
try {
|
|
637
636
|
const roots = persistence.state && lifecycle.latestSnapshot ? orderedRoots(persistence.state, lifecycle.latestSnapshot).length : 0;
|
|
638
|
-
const
|
|
639
|
-
const
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
const
|
|
643
|
-
|
|
637
|
+
const staged = persistence.state ? pendingMarks(persistence.state).length : 0;
|
|
638
|
+
const capacity = currentCapacity(ctx);
|
|
639
|
+
const used = measurements.lastProviderMeasurement?.tokens ?? null;
|
|
640
|
+
const share = used !== null && capacity.budgetTokens > 0 ? used / capacity.budgetTokens : null;
|
|
641
|
+
const parts: string[] = [];
|
|
642
|
+
// FAILURE LEADS. This line is cut to whatever width the host gives it, so the last
|
|
643
|
+
// segment is the first one a narrow terminal removes, and folding having stopped is
|
|
644
|
+
// the one state on this line a person has to act on. It was appended last.
|
|
645
|
+
if (ladder.automaticFailure) parts.push("FOLDING STOPPED");
|
|
646
|
+
// Unmeasured is stated, never guessed: an estimate rendered as a fact is the habit
|
|
647
|
+
// that cost a live session, and this line is the most-read surface there is.
|
|
648
|
+
parts.push(share === null ? "not measured" : `${Math.round(share * 100)}% full`);
|
|
649
|
+
const trigger = lifecycle.latestSnapshot?.thresholds.maxTarget ?? null;
|
|
650
|
+
// WHEN, not just how full. "68% full" only answers the second question a reader has
|
|
651
|
+
// if they already remember where the trigger sits; naming it answers both.
|
|
652
|
+
//
|
|
653
|
+
// NOT WHILE STOPPED, though: the line rendered "COMMIT DUE" and "FOLDING STOPPED"
|
|
654
|
+
// in one breath, promising the thing it was announcing would not happen.
|
|
655
|
+
if (share !== null && trigger !== null && !ladder.automaticFailure) {
|
|
656
|
+
parts.push(share >= trigger ? "COMMIT DUE" : `commit at ${Math.round(trigger * 100)}%`);
|
|
657
|
+
}
|
|
658
|
+
if (staged > 0) parts.push(`${staged} staged`);
|
|
659
|
+
if (roots > 0) parts.push(`${roots} fold${roots === 1 ? "" : "s"}`);
|
|
660
|
+
ctx.ui?.setStatus?.(entryTypePrefix, `${brandNoun} ${parts.join(" · ")}`);
|
|
644
661
|
} catch { }
|
|
645
662
|
};
|
|
646
663
|
|
|
@@ -779,6 +796,8 @@ export function registerActiveContext(pi: any, options: {
|
|
|
779
796
|
instrumentation.ledger = emptyLedger();
|
|
780
797
|
instrumentation.previousDigests = null;
|
|
781
798
|
instrumentation.previousText = null;
|
|
799
|
+
instrumentation.previousImages = 0;
|
|
800
|
+
instrumentation.previousImageChars = 0;
|
|
782
801
|
instrumentation.requests = 0;
|
|
783
802
|
instrumentation.lastMutationRequest = 0;
|
|
784
803
|
instrumentation.lastMutationTokens = null;
|
|
@@ -793,16 +812,11 @@ export function registerActiveContext(pi: any, options: {
|
|
|
793
812
|
curation.lastRecovery = null;
|
|
794
813
|
ladder.lastAutomaticAction = null;
|
|
795
814
|
ladder.automaticFailure = null;
|
|
796
|
-
advisory.hardFenceNoticeKey = null;
|
|
797
815
|
measurements.providerMeasurementReceipts.clear();
|
|
798
816
|
measurements.providerMeasurementRevisionByMessageSha.clear();
|
|
799
817
|
measurements.providerMeasurementByMessageSha.clear();
|
|
800
818
|
measurements.providerMeasurementAnchorByMessageSha.clear();
|
|
801
819
|
const sessionId = ctx.sessionManager.getSessionId();
|
|
802
|
-
if (advisory.hardFenceReleaseSessionId !== sessionId) {
|
|
803
|
-
advisory.hardFenceReleaseSessionId = sessionId;
|
|
804
|
-
advisory.hardFenceReleasedProjectionKeys.clear();
|
|
805
|
-
}
|
|
806
820
|
let restored: ActiveContextState | null = null;
|
|
807
821
|
let restoreError: unknown = null;
|
|
808
822
|
let restoredPersistence: MaterializedStatePersistence | null = null;
|
|
@@ -1318,53 +1332,6 @@ export function registerActiveContext(pi: any, options: {
|
|
|
1318
1332
|
safeNotify(ctx, `Automatic context management suspended: ${message}`, "warning");
|
|
1319
1333
|
};
|
|
1320
1334
|
|
|
1321
|
-
const abortUnsafeHardContext = (
|
|
1322
|
-
snapshot: ActiveContextSnapshot | null,
|
|
1323
|
-
ctx: any,
|
|
1324
|
-
allowUnmeasuredRevisionRelease = false,
|
|
1325
|
-
): boolean => {
|
|
1326
|
-
if (measurements.latestRatio === null || measurements.latestRatio < hardFenceRatio(snapshot ?? undefined, ctx) || !persistence.state) return false;
|
|
1327
|
-
const measuredRevision = measurements.lastProviderMeasurement
|
|
1328
|
-
? measurements.providerMeasurementRevisionByMessageSha.get(measurements.lastProviderMeasurement.messageSha256)
|
|
1329
|
-
: undefined;
|
|
1330
|
-
if (allowUnmeasuredRevisionRelease && !ladder.automaticFailure &&
|
|
1331
|
-
measuredRevision !== undefined && measurements.lastProviderMeasurement &&
|
|
1332
|
-
!durableProviderMeasurementMatches(measurements.lastProviderMeasurement)) {
|
|
1333
|
-
const releaseKey = sha256Value({
|
|
1334
|
-
sessionId: persistence.state.sessionId,
|
|
1335
|
-
topologySha256: topologySha256(persistence.state),
|
|
1336
|
-
protectionSha256: protectionSha256(persistence.state),
|
|
1337
|
-
measuredRevision,
|
|
1338
|
-
providerMessageSha256: measurements.lastProviderMeasurement?.messageSha256 ?? null,
|
|
1339
|
-
});
|
|
1340
|
-
if (!advisory.hardFenceReleasedProjectionKeys.has(releaseKey) &&
|
|
1341
|
-
advisory.hardFenceReleasedProjectionKeys.size < 4_096) {
|
|
1342
|
-
advisory.hardFenceReleasedProjectionKeys.add(releaseKey);
|
|
1343
|
-
return false;
|
|
1344
|
-
}
|
|
1345
|
-
}
|
|
1346
|
-
const key = ladder.automaticFailure?.key ?? sha256Value({
|
|
1347
|
-
sessionId: snapshot?.sessionId ?? persistence.state.sessionId,
|
|
1348
|
-
revision: persistence.state.revision,
|
|
1349
|
-
providerMessageSha256: measurements.lastProviderMeasurement?.messageSha256 ?? null,
|
|
1350
|
-
phase: "hard-provider-fence",
|
|
1351
|
-
});
|
|
1352
|
-
ladder.pendingContextNote = `Provider context reached the hard ${brandNoun} fence without a newly committed lossless fold. ` +
|
|
1353
|
-
"The provider request was aborted before transmission; run /compact or make an explicit bounded context fold.";
|
|
1354
|
-
if (advisory.hardFenceNoticeKey !== key) {
|
|
1355
|
-
advisory.hardFenceNoticeKey = key;
|
|
1356
|
-
safeNotify(
|
|
1357
|
-
ctx,
|
|
1358
|
-
"Provider request aborted at the hard context fence; run /compact or make an explicit bounded context fold.",
|
|
1359
|
-
"error",
|
|
1360
|
-
);
|
|
1361
|
-
}
|
|
1362
|
-
if (typeof ctx.abort !== "function") {
|
|
1363
|
-
throw new Error(`Pi hard-fence abort capability is unavailable at ratio ${measurements.latestRatio}`);
|
|
1364
|
-
}
|
|
1365
|
-
ctx.abort();
|
|
1366
|
-
return true;
|
|
1367
|
-
};
|
|
1368
1335
|
|
|
1369
1336
|
const PROJECTION_CALIBRATION_MIN_CHARS = 20_000;
|
|
1370
1337
|
const PROJECTION_CALIBRATION_MIN_TOKENS = 5_000;
|
|
@@ -1385,6 +1352,12 @@ export function registerActiveContext(pi: any, options: {
|
|
|
1385
1352
|
}
|
|
1386
1353
|
const chars = measurements.lastProjectedChars;
|
|
1387
1354
|
if (chars === null || chars < PROJECTION_CALIBRATION_MIN_CHARS) return;
|
|
1355
|
+
// NEVER LEARN THE RATIO FROM A WINDOW HOLDING AN IMAGE. chars/token is a fact about
|
|
1356
|
+
// prose; a projection carrying base64 has a far higher true ratio, and because
|
|
1357
|
+
// projectionCharsPerToken takes the MINIMUM over the window such a sample is either
|
|
1358
|
+
// discarded (leaving the over-read in place) or, once images dominate, poisons the
|
|
1359
|
+
// ratio for the text around them. The clean rule is to calibrate on text alone.
|
|
1360
|
+
if (measurements.lastProjectedImages > 0) return;
|
|
1388
1361
|
if (!Number.isFinite(measurement.tokens) || measurement.tokens < PROJECTION_CALIBRATION_MIN_TOKENS) return;
|
|
1389
1362
|
if (measurements.lastProjectedEstimate !== null && measurements.lastProjectedEstimateCalibrated &&
|
|
1390
1363
|
measurement.tokens > 0) {
|
|
@@ -1418,13 +1391,26 @@ export function registerActiveContext(pi: any, options: {
|
|
|
1418
1391
|
return;
|
|
1419
1392
|
}
|
|
1420
1393
|
const chars = bytes(text);
|
|
1421
|
-
|
|
1394
|
+
// The anchor is differenced against TEXT characters, so it must store them. The mass
|
|
1395
|
+
// belongs to the same `previousText` this anchor is built from, recorded by
|
|
1396
|
+
// noteProjection when that text was produced.
|
|
1397
|
+
const textChars = Math.max(0, chars - instrumentation.previousImageChars);
|
|
1398
|
+
const images = instrumentation.previousImages;
|
|
1399
|
+
// The floor test reads TEXT against tokens: an image-heavy projection has a raw ratio
|
|
1400
|
+
// far above any prose floor and would sail through a test that is meant to reject a
|
|
1401
|
+
// measurement the text cannot explain.
|
|
1402
|
+
if (!(measurement.tokens > 0) ||
|
|
1403
|
+
(measurement.tokens > images * IMAGE_ESTIMATED_TOKENS &&
|
|
1404
|
+
textChars / (measurement.tokens - images * IMAGE_ESTIMATED_TOKENS) <
|
|
1405
|
+
PROJECTION_CHARS_PER_TOKEN_FLOOR)) {
|
|
1422
1406
|
measurements.projectionAnchor = null;
|
|
1423
1407
|
return;
|
|
1424
1408
|
}
|
|
1425
1409
|
measurements.projectionAnchor = {
|
|
1426
1410
|
tokens: measurement.tokens,
|
|
1427
1411
|
chars,
|
|
1412
|
+
textChars,
|
|
1413
|
+
images,
|
|
1428
1414
|
head: text.slice(0, -1),
|
|
1429
1415
|
messageSha256: measurement.messageSha256,
|
|
1430
1416
|
sessionId: persistence.state.sessionId,
|
|
@@ -1446,6 +1432,9 @@ export function registerActiveContext(pi: any, options: {
|
|
|
1446
1432
|
tokens: number;
|
|
1447
1433
|
basis: ProjectionReadingBasis;
|
|
1448
1434
|
chars: number;
|
|
1435
|
+
/** The image-corrected size of THIS projection, independent of any anchor. */
|
|
1436
|
+
sizeTokens: number;
|
|
1437
|
+
images: number;
|
|
1449
1438
|
anchorTokens: number | null;
|
|
1450
1439
|
deltaChars: number | null;
|
|
1451
1440
|
text: string;
|
|
@@ -1453,21 +1442,34 @@ export function registerActiveContext(pi: any, options: {
|
|
|
1453
1442
|
const text = serialized ?? stableStringify(projected);
|
|
1454
1443
|
const chars = Buffer.byteLength(text, "utf8");
|
|
1455
1444
|
const charsPerToken = projectionCharsPerToken();
|
|
1456
|
-
|
|
1445
|
+
// AN IMAGE IS NOT TEXT (2026-08-28, see imageMass). Its base64 leaves the character
|
|
1446
|
+
// count and comes back as a flat per-image cost, because dividing base64 by a ratio
|
|
1447
|
+
// learned from prose read one screenshot as 110,000 tokens against a true 1,500 and
|
|
1448
|
+
// aborted a session that was less than half full.
|
|
1449
|
+
const mass = imageMass(projected);
|
|
1450
|
+
const textChars = Math.max(0, chars - mass.base64Chars);
|
|
1451
|
+
const imageTokens = mass.images * IMAGE_ESTIMATED_TOKENS;
|
|
1452
|
+
const estimate = Math.ceil(textChars / charsPerToken) + imageTokens;
|
|
1457
1453
|
const anchor = measurements.projectionAnchor;
|
|
1458
1454
|
if (!anchor || anchor.generation !== lifecycle.generation ||
|
|
1459
1455
|
anchor.sessionId !== persistence.state?.sessionId) {
|
|
1460
|
-
return { tokens: estimate, basis: "unmeasured", chars, anchorTokens: null, deltaChars: null, text };
|
|
1456
|
+
return { tokens: estimate, basis: "unmeasured", chars, sizeTokens: estimate, images: mass.images, anchorTokens: null, deltaChars: null, text };
|
|
1461
1457
|
}
|
|
1462
1458
|
const separator = text.length > anchor.head.length ? text[anchor.head.length] : "";
|
|
1463
1459
|
if (!text.startsWith(anchor.head) || (separator !== "," && separator !== "]")) {
|
|
1464
|
-
return { tokens: estimate, basis: "rewritten", chars, anchorTokens: anchor.tokens, deltaChars: null, text };
|
|
1460
|
+
return { tokens: estimate, basis: "rewritten", chars, sizeTokens: estimate, images: mass.images, anchorTokens: anchor.tokens, deltaChars: null, text };
|
|
1465
1461
|
}
|
|
1466
|
-
|
|
1462
|
+
// The delta is taken on TEXT characters and images separately, so an image arriving
|
|
1463
|
+
// after the anchor costs its own flat price rather than its base64 length.
|
|
1464
|
+
const deltaChars = textChars - anchor.textChars;
|
|
1465
|
+
const deltaImages = mass.images - anchor.images;
|
|
1467
1466
|
return {
|
|
1468
|
-
tokens: Math.max(0, anchor.tokens + Math.ceil(deltaChars / charsPerToken)
|
|
1467
|
+
tokens: Math.max(0, anchor.tokens + Math.ceil(deltaChars / charsPerToken) +
|
|
1468
|
+
deltaImages * IMAGE_ESTIMATED_TOKENS),
|
|
1469
1469
|
basis: "anchored",
|
|
1470
1470
|
chars,
|
|
1471
|
+
sizeTokens: estimate,
|
|
1472
|
+
images: mass.images,
|
|
1471
1473
|
anchorTokens: anchor.tokens,
|
|
1472
1474
|
deltaChars,
|
|
1473
1475
|
text,
|
|
@@ -1517,7 +1519,7 @@ export function registerActiveContext(pi: any, options: {
|
|
|
1517
1519
|
: Number.POSITIVE_INFINITY;
|
|
1518
1520
|
const reading = projectedTokenReading(projected, serialized);
|
|
1519
1521
|
const tokens = reading.tokens;
|
|
1520
|
-
const sizeTokens =
|
|
1522
|
+
const sizeTokens = reading.sizeTokens;
|
|
1521
1523
|
const marginTokens = Number.isFinite(budgetTokens)
|
|
1522
1524
|
? projectionMarginTokens(tokens, capacity.window)
|
|
1523
1525
|
: 0;
|
|
@@ -1533,40 +1535,6 @@ export function registerActiveContext(pi: any, options: {
|
|
|
1533
1535
|
};
|
|
1534
1536
|
};
|
|
1535
1537
|
|
|
1536
|
-
const abortOverBudgetProjection = (
|
|
1537
|
-
tokens: number,
|
|
1538
|
-
budgetTokens: number,
|
|
1539
|
-
ctx: any,
|
|
1540
|
-
recoveryAttempts = 0,
|
|
1541
|
-
): void => {
|
|
1542
|
-
const key = sha256Value({
|
|
1543
|
-
sessionId: persistence.state?.sessionId ?? null,
|
|
1544
|
-
revision: persistence.state?.revision ?? null,
|
|
1545
|
-
tokens,
|
|
1546
|
-
budgetTokens,
|
|
1547
|
-
phase: "projection-budget-fence",
|
|
1548
|
-
});
|
|
1549
|
-
ladder.pendingContextNote =
|
|
1550
|
-
`The ${brandNoun} projection estimates ${tokens} tokens against a ${budgetTokens}-token serving budget` +
|
|
1551
|
-
(recoveryAttempts
|
|
1552
|
-
? `, after ${recoveryAttempts} recovery attempt(s) that folded everything foldable. The remaining ` +
|
|
1553
|
-
"inflow does not fit at any folding depth, which is an impossibility rather than a recoverable state."
|
|
1554
|
-
: ".") +
|
|
1555
|
-
" The provider request was aborted before transmission; run /compact or make an explicit bounded context fold.";
|
|
1556
|
-
if (advisory.hardFenceNoticeKey !== key) {
|
|
1557
|
-
advisory.hardFenceNoticeKey = key;
|
|
1558
|
-
safeNotify(
|
|
1559
|
-
ctx,
|
|
1560
|
-
"Provider request aborted: the projection exceeds the serving budget. " +
|
|
1561
|
-
"Run /compact or make an explicit bounded context fold.",
|
|
1562
|
-
"error",
|
|
1563
|
-
);
|
|
1564
|
-
}
|
|
1565
|
-
if (typeof ctx.abort !== "function") {
|
|
1566
|
-
throw new Error(`Pi projection-budget abort capability is unavailable at ${tokens} estimated tokens`);
|
|
1567
|
-
}
|
|
1568
|
-
ctx.abort();
|
|
1569
|
-
};
|
|
1570
1538
|
|
|
1571
1539
|
// THE BAND TOP COMMITS ON ITS OWN (Shane, 2026-08-22).
|
|
1572
1540
|
//
|
|
@@ -1731,41 +1699,6 @@ export function registerActiveContext(pi: any, options: {
|
|
|
1731
1699
|
return projected;
|
|
1732
1700
|
};
|
|
1733
1701
|
|
|
1734
|
-
/**
|
|
1735
|
-
* THE TABLE OF CONTENTS IS ONE COPY PER COMMIT (Shane, 2026-08-26). It is admitted
|
|
1736
|
-
* once per frozen projection, exactly as the fold notice above, and that admission
|
|
1737
|
-
* rule IS the refresh law: between commits the copy stays buried at its index, so a
|
|
1738
|
-
* quiet pass moves no byte and costs no occupancy anchor, and the tool result of every
|
|
1739
|
-
* `remember` carries the live key list in the meantime. A commit rewrites the
|
|
1740
|
-
* projection, the freeze drops, the body filter strips the old copy, and the next pass
|
|
1741
|
-
* seats a fresh one at the tail with the latest keys. Nothing here re-renders in
|
|
1742
|
-
* place, because a carrier that moves on a quiet pass is the exact defect the fold
|
|
1743
|
-
* notice's comment records.
|
|
1744
|
-
*/
|
|
1745
|
-
const appendMemoryToc = (projected: unknown[], snapshot: ActiveContextSnapshot): unknown[] => {
|
|
1746
|
-
if (!workingMemory) return projected;
|
|
1747
|
-
if (!persistence.state) return projected;
|
|
1748
|
-
const held = persistence.state.memory ?? [];
|
|
1749
|
-
if (!held.length) return projected;
|
|
1750
|
-
if (!carrierAdmitted("memory-toc")) return projected;
|
|
1751
|
-
const entries = [...held].sort((left, right) => right.ordinal - left.ordinal);
|
|
1752
|
-
const text = memoryTocText({
|
|
1753
|
-
entries: entries.map((entry) => ({ key: entry.key, chars: entry.body.length })),
|
|
1754
|
-
toolName,
|
|
1755
|
-
brandNoun,
|
|
1756
|
-
});
|
|
1757
|
-
projected.push({
|
|
1758
|
-
role: "custom",
|
|
1759
|
-
customType: memoryTocProjectionType,
|
|
1760
|
-
content: text,
|
|
1761
|
-
display: false,
|
|
1762
|
-
details: { source: activeContextSource(entryTypePrefix), ephemeral: true },
|
|
1763
|
-
timestamp: typeof ownValue(snapshot.messages.at(-1), "timestamp") === "number"
|
|
1764
|
-
? ownValue(snapshot.messages.at(-1), "timestamp")
|
|
1765
|
-
: 0,
|
|
1766
|
-
});
|
|
1767
|
-
return projected;
|
|
1768
|
-
};
|
|
1769
1702
|
|
|
1770
1703
|
const enforceBandTop = async (
|
|
1771
1704
|
snapshot: ActiveContextSnapshot,
|
|
@@ -1805,10 +1738,10 @@ export function registerActiveContext(pi: any, options: {
|
|
|
1805
1738
|
snapshot: ActiveContextSnapshot,
|
|
1806
1739
|
projected: unknown[],
|
|
1807
1740
|
ctx: any,
|
|
1808
|
-
): Promise<{ projected: unknown[];
|
|
1741
|
+
): Promise<{ projected: unknown[]; text: string }> => {
|
|
1809
1742
|
let measured = projectionExceedsBudget(projected, ctx);
|
|
1810
1743
|
const rejected = curation.pendingRejection !== null;
|
|
1811
|
-
if (!measured.crowded && !rejected) return { projected,
|
|
1744
|
+
if (!measured.crowded && !rejected) return { projected, text: measured.text };
|
|
1812
1745
|
const trigger = measured;
|
|
1813
1746
|
let reduced = projected;
|
|
1814
1747
|
let attempts = 0;
|
|
@@ -1912,11 +1845,24 @@ export function registerActiveContext(pi: any, options: {
|
|
|
1912
1845
|
}));
|
|
1913
1846
|
curation.pendingRejection = null;
|
|
1914
1847
|
}
|
|
1915
|
-
//
|
|
1916
|
-
//
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1848
|
+
// THE RUNTIME COMMITS AND TRANSMITS; ONLY THE PROVIDER REFUSES (Shane, 2026-08-28).
|
|
1849
|
+
//
|
|
1850
|
+
// This used to abort the request when the projection still read over budget after the
|
|
1851
|
+
// loop. It was deleted because it let an ESTIMATE veto a request that ground truth
|
|
1852
|
+
// would have accepted: a live session read 220,883 tokens where the provider counted
|
|
1853
|
+
// 111,837, was refused before transmission, and could not be rescued, because the
|
|
1854
|
+
// commit before the refusal had applied 28 marks and the next applied 0. The advice it
|
|
1855
|
+
// printed, fold manually or run /compact, named the two things the mechanism had just
|
|
1856
|
+
// proven it could not do.
|
|
1857
|
+
//
|
|
1858
|
+
// maxTarget is a TRIGGER meaning "commit now", not a bound meaning "refuse to
|
|
1859
|
+
// proceed", and only the first is something an estimate can be right about. What
|
|
1860
|
+
// remains is the loop above, which commits everything it can reach; the request then
|
|
1861
|
+
// goes to the provider, and a genuine overflow is handled by the rejection path, which
|
|
1862
|
+
// branches back, folds and retries on the provider's own count rather than ours.
|
|
1863
|
+
// `measured` is recomputed on `reduced` at every turn of the loop, so its text is the
|
|
1864
|
+
// serialization of the array this returns.
|
|
1865
|
+
return { projected: reduced, text: measured.text };
|
|
1920
1866
|
};
|
|
1921
1867
|
|
|
1922
1868
|
const startPreparation = (snapshot: ActiveContextSnapshot, ratio: number | null, ctx: any): void => {
|
|
@@ -2071,7 +2017,7 @@ export function registerActiveContext(pi: any, options: {
|
|
|
2071
2017
|
const customType = ownValue(message, "customType");
|
|
2072
2018
|
return customType !== milestoneProjectionType && customType !== advisoryProjectionType &&
|
|
2073
2019
|
customType !== receiptProjectionType && customType !== curationProjectionType &&
|
|
2074
|
-
customType !== foldNoticeProjectionType
|
|
2020
|
+
customType !== foldNoticeProjectionType;
|
|
2075
2021
|
});
|
|
2076
2022
|
const withdrawn = withdrawConsumedEphemeralPeeks(body);
|
|
2077
2023
|
if (withdrawn.size && freeze.body) {
|
|
@@ -2099,7 +2045,6 @@ export function registerActiveContext(pi: any, options: {
|
|
|
2099
2045
|
freeze.bodyText = bodyText;
|
|
2100
2046
|
appendReceipts(projected, snapshot);
|
|
2101
2047
|
appendFoldNotice(projected, snapshot);
|
|
2102
|
-
appendMemoryToc(projected, snapshot);
|
|
2103
2048
|
return holdFrozen(projected);
|
|
2104
2049
|
};
|
|
2105
2050
|
const noteProjection = (projected: unknown[], serialized?: string): void => {
|
|
@@ -2888,11 +2833,6 @@ export function registerActiveContext(pi: any, options: {
|
|
|
2888
2833
|
try { projected = projectWithAdvisory(snapshot); }
|
|
2889
2834
|
catch (error) {
|
|
2890
2835
|
suspendAutomatic(error, "projection", ctx);
|
|
2891
|
-
abortUnsafeHardContext(snapshot, ctx);
|
|
2892
|
-
return { messages: event.messages };
|
|
2893
|
-
}
|
|
2894
|
-
if (abortUnsafeHardContext(snapshot, ctx, true)) {
|
|
2895
|
-
updateStatus(ctx);
|
|
2896
2836
|
return { messages: event.messages };
|
|
2897
2837
|
}
|
|
2898
2838
|
// THE FRONTIER CUTS FIRST, and it costs the projection nothing to do so: a pending
|
|
@@ -2908,7 +2848,6 @@ export function registerActiveContext(pi: any, options: {
|
|
|
2908
2848
|
try { projected = projectWithAdvisory(snapshot); }
|
|
2909
2849
|
catch (error) {
|
|
2910
2850
|
suspendAutomatic(error, "projection", ctx);
|
|
2911
|
-
abortUnsafeHardContext(snapshot, ctx);
|
|
2912
2851
|
return { messages: event.messages };
|
|
2913
2852
|
}
|
|
2914
2853
|
}
|
|
@@ -2916,14 +2855,13 @@ export function registerActiveContext(pi: any, options: {
|
|
|
2916
2855
|
projected = budgeted.projected;
|
|
2917
2856
|
const reading = projectedTokenReading(projected, budgeted.text);
|
|
2918
2857
|
measurements.lastProjectedChars = reading.chars;
|
|
2858
|
+
measurements.lastProjectedImages = reading.images;
|
|
2919
2859
|
measurements.lastProjectedEstimate = reading.tokens;
|
|
2920
2860
|
measurements.lastProjectedEstimateBasis = reading.basis;
|
|
2921
|
-
|
|
2861
|
+
// The size is the reading's own image-corrected figure. Recomputing it from raw
|
|
2862
|
+
// chars here is what fed base64 straight into the fence's own accounting.
|
|
2863
|
+
measurements.lastProjectedSizeTokens = reading.sizeTokens;
|
|
2922
2864
|
measurements.lastProjectedEstimateCalibrated = measurements.projectionCalibrations.length > 0;
|
|
2923
|
-
if (budgeted.aborted) {
|
|
2924
|
-
updateStatus(ctx);
|
|
2925
|
-
return { messages: projected };
|
|
2926
|
-
}
|
|
2927
2865
|
noteProjection(projected, budgeted.text);
|
|
2928
2866
|
updateStatus(ctx);
|
|
2929
2867
|
return { messages: projected };
|
|
@@ -2937,7 +2875,6 @@ export function registerActiveContext(pi: any, options: {
|
|
|
2937
2875
|
if (!persistedSucceeded) ladder.boundaryFailure = lifecycle.latestSnapshotError;
|
|
2938
2876
|
suspendAutomatic(error, persistedSucceeded ? "post-persist-projection" : "context", ctx);
|
|
2939
2877
|
}
|
|
2940
|
-
abortUnsafeHardContext(lifecycle.latestSnapshot, ctx);
|
|
2941
2878
|
return { messages: event.messages };
|
|
2942
2879
|
}
|
|
2943
2880
|
};
|
|
@@ -4081,102 +4018,6 @@ export function registerActiveContext(pi: any, options: {
|
|
|
4081
4018
|
"reaches it only when the commit folds this span. Nothing in your context moved.",
|
|
4082
4019
|
});
|
|
4083
4020
|
}
|
|
4084
|
-
if (action === "remember") {
|
|
4085
|
-
// THE DIGEST'S OWN CHANNEL. A working-memory write is durable state outside the
|
|
4086
|
-
// projection, so like `brief` it moves no bytes and costs no prefix cache: the
|
|
4087
|
-
// table of contents the projection carries refreshes at the next commit, and this
|
|
4088
|
-
// result carries the live key list in the meantime.
|
|
4089
|
-
const key = String(params.key ?? "").trim();
|
|
4090
|
-
if (!key) throw new Error("remember needs a key");
|
|
4091
|
-
if (key.length > MEMORY_KEY_CHARS) {
|
|
4092
|
-
throw new Error(`Working-memory keys have a hard ${MEMORY_KEY_CHARS}-character ` +
|
|
4093
|
-
"maximum: the key is a table-of-contents line, not the note. Put the detail " +
|
|
4094
|
-
"in the body.");
|
|
4095
|
-
}
|
|
4096
|
-
const body = String(params.body ?? "").trim();
|
|
4097
|
-
if (body.length > MEMORY_BODY_CHARS) {
|
|
4098
|
-
throw new Error(`Working-memory bodies have a hard ${MEMORY_BODY_CHARS}-character ` +
|
|
4099
|
-
`maximum and this one is ${body.length}. Split the entry, or keep the part ` +
|
|
4100
|
-
"that changes and let the transcript carry the rest: folded material stays " +
|
|
4101
|
-
"losslessly recoverable by peek.");
|
|
4102
|
-
}
|
|
4103
|
-
const held = persistence.state!.memory ?? [];
|
|
4104
|
-
const existing = held.find((entry) => entry.key === key);
|
|
4105
|
-
let nextMemory: WorkingMemoryEntry[];
|
|
4106
|
-
if (!body) {
|
|
4107
|
-
if (!existing) {
|
|
4108
|
-
throw new Error(`No working-memory entry '${key}' to remove. ${held.length
|
|
4109
|
-
? `The keys are: ${held.map((entry) => entry.key).join(", ")}.`
|
|
4110
|
-
: "Working memory is empty."}`);
|
|
4111
|
-
}
|
|
4112
|
-
nextMemory = held.filter((entry) => entry.key !== key);
|
|
4113
|
-
} else {
|
|
4114
|
-
if (!existing && held.length >= MEMORY_KEYS_MAX) {
|
|
4115
|
-
throw new Error(`Working memory holds its maximum of ${MEMORY_KEYS_MAX} ` +
|
|
4116
|
-
"entries. Merge two entries, or remove one whose facts no longer matter " +
|
|
4117
|
-
`(${toolName} {"action":"remember","key":"<key>","body":""}), then write ` +
|
|
4118
|
-
"this one.");
|
|
4119
|
-
}
|
|
4120
|
-
const ordinal = held.reduce((max, entry) => Math.max(max, entry.ordinal), 0) + 1;
|
|
4121
|
-
nextMemory = existing
|
|
4122
|
-
? held.map((entry) => entry.key === key ? { key, body, ordinal } : entry)
|
|
4123
|
-
: [...held, { key, body, ordinal }];
|
|
4124
|
-
}
|
|
4125
|
-
const nextState = {
|
|
4126
|
-
...persistence.state!,
|
|
4127
|
-
revision: persistence.state!.revision + 1,
|
|
4128
|
-
...(nextMemory.length ? { memory: nextMemory } : {}),
|
|
4129
|
-
};
|
|
4130
|
-
if (!nextMemory.length) delete (nextState as Partial<ActiveContextState>).memory;
|
|
4131
|
-
persistence.state = nextState;
|
|
4132
|
-
await persistManual(nextState, ctx);
|
|
4133
|
-
updateStatus(ctx);
|
|
4134
|
-
const toc = [...nextMemory].sort((left, right) => right.ordinal - left.ordinal)
|
|
4135
|
-
.map((entry) => ({ key: entry.key, chars: entry.body.length }));
|
|
4136
|
-
return toolPayload({
|
|
4137
|
-
version: 1,
|
|
4138
|
-
action,
|
|
4139
|
-
key,
|
|
4140
|
-
...(body ? { chars: body.length } : { removed: true }),
|
|
4141
|
-
argumentsSha256: executionArgumentsSha256,
|
|
4142
|
-
durableRevision: persistence.state!.revision,
|
|
4143
|
-
entries: toc,
|
|
4144
|
-
activation: body
|
|
4145
|
-
? "durable immediately, and it costs your window nothing: the body lives in " +
|
|
4146
|
-
"state, this list is the live view, and the table of contents in your " +
|
|
4147
|
-
"context refreshes at the next commit."
|
|
4148
|
-
: "removed. The table of contents in your context refreshes at the next commit.",
|
|
4149
|
-
});
|
|
4150
|
-
}
|
|
4151
|
-
if (action === "recall") {
|
|
4152
|
-
const held = persistence.state!.memory ?? [];
|
|
4153
|
-
if (!held.length) {
|
|
4154
|
-
throw new Error(`Working memory is empty. Write an entry with ${toolName} ` +
|
|
4155
|
-
'{"action":"remember","key":"<key>","body":"..."}.');
|
|
4156
|
-
}
|
|
4157
|
-
const requested = params.keys === undefined && params.key === undefined
|
|
4158
|
-
? null
|
|
4159
|
-
: (denseOwnArrayValues(params.keys) ?? [params.key ?? params.keys])
|
|
4160
|
-
.map((value) => String(value ?? "").trim()).filter(Boolean);
|
|
4161
|
-
const entries = [...held].sort((left, right) => right.ordinal - left.ordinal);
|
|
4162
|
-
const chosen = requested === null
|
|
4163
|
-
? entries
|
|
4164
|
-
: entries.filter((entry) => requested.includes(entry.key));
|
|
4165
|
-
if (requested !== null) {
|
|
4166
|
-
const missing = requested.filter((key) => !held.some((entry) => entry.key === key));
|
|
4167
|
-
if (missing.length) {
|
|
4168
|
-
throw new Error(`No working-memory entr${missing.length === 1 ? "y" : "ies"} ` +
|
|
4169
|
-
`${missing.join(", ")}. The keys are: ` +
|
|
4170
|
-
`${entries.map((entry) => entry.key).join(", ")}.`);
|
|
4171
|
-
}
|
|
4172
|
-
}
|
|
4173
|
-
return toolPayload({
|
|
4174
|
-
version: 1,
|
|
4175
|
-
action,
|
|
4176
|
-
argumentsSha256: executionArgumentsSha256,
|
|
4177
|
-
entries: chosen.map((entry) => ({ key: entry.key, chars: entry.body.length, body: entry.body })),
|
|
4178
|
-
});
|
|
4179
|
-
}
|
|
4180
4021
|
throw new Error(`Unknown ${toolName} action '${action}'`);
|
|
4181
4022
|
};
|
|
4182
4023
|
const toolHandler = async (
|
|
@@ -4232,12 +4073,24 @@ export function registerActiveContext(pi: any, options: {
|
|
|
4232
4073
|
try {
|
|
4233
4074
|
const snapshot = authoritativeSnapshotFor(ctx);
|
|
4234
4075
|
const status = activeContextStatus(snapshot, persistence.state, 0, 40);
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4076
|
+
const capacity = currentCapacity(ctx);
|
|
4077
|
+
const staged = pendingMarks(persistence.state);
|
|
4078
|
+
safeNotify(ctx, foldStatusText({
|
|
4079
|
+
brand: brandNoun,
|
|
4080
|
+
usedTokens: measurements.lastProviderMeasurement?.tokens ?? null,
|
|
4081
|
+
budgetTokens: capacity.budgetTokens,
|
|
4082
|
+
commitAtShare: snapshot.thresholds.maxTarget,
|
|
4083
|
+
aimShare: snapshot.thresholds.minTarget,
|
|
4084
|
+
roots: (status.roots as string[]).length,
|
|
4085
|
+
totalFolds: Number(status.totalFolds ?? 0),
|
|
4086
|
+
stagedMarks: staged.length,
|
|
4087
|
+
stagedTokens: staged.reduce((total, mark) =>
|
|
4088
|
+
total + estimatedTokens(markFreedBytes(snapshot, persistence.state!, mark)), 0),
|
|
4089
|
+
pinned: persistence.state.protected.length,
|
|
4090
|
+
suspended: ladder.automaticFailure?.message ?? null,
|
|
4091
|
+
foldCommand: `/${commandNames.fold}`,
|
|
4092
|
+
editorCommand: "/fold-editor",
|
|
4093
|
+
}), "info");
|
|
4241
4094
|
} catch (error) {
|
|
4242
4095
|
safeNotify(ctx, `Active-context status unavailable; native Pi context is unchanged: ${String(error)}`, "warning");
|
|
4243
4096
|
}
|
|
@@ -4377,12 +4230,15 @@ export function registerActiveContext(pi: any, options: {
|
|
|
4377
4230
|
: null;
|
|
4378
4231
|
const accounting = markAccounting(snapshot, state);
|
|
4379
4232
|
return {
|
|
4380
|
-
|
|
4233
|
+
// NO SESSION ID. Eight hex characters of a value a person never types, on the
|
|
4234
|
+
// title line of the surface, is the same defect the rows below it just lost.
|
|
4235
|
+
title: `${brandNoun} window`,
|
|
4381
4236
|
occupancy: {
|
|
4382
4237
|
usedTokens: capacity.usedTokens,
|
|
4383
4238
|
budgetTokens: capacity.budgetTokens,
|
|
4384
4239
|
commitOccupancy: thresholds.maxTarget,
|
|
4385
4240
|
commitDue: occupancy !== null && occupancy >= thresholds.maxTarget,
|
|
4241
|
+
suspended: Boolean(ladder.automaticFailure),
|
|
4386
4242
|
},
|
|
4387
4243
|
blocks: buildFoldEditorData(snapshot, state, {
|
|
4388
4244
|
foldRows: () => (state.folds ?? []).map((fold: any) => {
|