pi-fold 4.1.1 → 4.1.2
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
CHANGED
|
@@ -37,10 +37,10 @@ In an interactive session one row sits directly above Pi's footer and shows the
|
|
|
37
37
|
|
|
38
38
|
<picture>
|
|
39
39
|
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/shaneconner/fold/main/media/fold-bar-dark.svg">
|
|
40
|
-
<img alt="The fold bar: forty cells of the serving budget, oldest on the left, with ticks at the aim and the commit point. Staged spans, raw material and fold placeholders fill it in three colours, and the label reads 42%, commit at 80%, 12 staged,
|
|
40
|
+
<img alt="The fold bar: forty cells of the serving budget, oldest on the left, with ticks at the aim and the commit point. Staged spans, raw material and fold placeholders fill it in three colours, and the label reads 42%, commit at 80%, 12 staged, 3 folds hide 412k tokens." src="https://raw.githubusercontent.com/shaneconner/fold/main/media/fold-bar-light.svg">
|
|
41
41
|
</picture>
|
|
42
42
|
|
|
43
|
-
The bar is the serving budget, forty cells wide, oldest material on the left. The two ticks are the band: the left one is `minTarget`, where a commit aims to land, and the right one is `maxTarget`, where the commit fires. The fill is the provider's own count of what is in the window, never an estimate, so before the first response the row says `not measured yet` and draws nothing. One cell is one fortieth of the budget, and the fill reads left to right in three colours: the placeholders of standing folds first, drawn as low slivers and small by design; then staged spans, the raw material the next commit collapses; then raw material the runtime has not claimed yet. The label repeats the percentage, names the commit point (or reads `COMMIT DUE` once the fill has crossed it and `commit held` once that count has been weighed), states how many spans are staged
|
|
43
|
+
The bar is the serving budget, forty cells wide, oldest material on the left. The two ticks are the band: the left one is `minTarget`, where a commit aims to land, and the right one is `maxTarget`, where the commit fires. The fill is the provider's own count of what is in the window, never an estimate, so before the first response the row says `not measured yet` and draws nothing. One cell is one fortieth of the budget, and the fill reads left to right in three colours: the placeholders of standing folds first, drawn as low slivers and small by design; then staged spans, the raw material the next commit collapses; then raw material the runtime has not claimed yet. The label repeats the percentage, names the commit point (or reads `COMMIT DUE` once the fill has crossed it and `commit held` once that count has been weighed), states how many spans are staged, and ends with what folding has already bought: the tokens the standing folds hide from the window. Every token figure prices a pasted image as one image rather than as its base64 text, the same way the occupancy estimate does. Right after a commit the percentage is still the provider's last count, so the label says it is from before the commit until the next response measures the window again. If automatic folding ever stops, the row reads `FOLDING STOPPED` in place of the bar. On a truecolor terminal the three fill classes take three steps of Crameri's batlow colour map; elsewhere they use the theme's own colours.
|
|
44
44
|
|
|
45
45
|
## Results
|
|
46
46
|
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
selectAutomaticRung,
|
|
36
36
|
setFoldProjectionState,
|
|
37
37
|
withExpandLease,
|
|
38
|
+
renderFoldParts,
|
|
38
39
|
} from "./lib/folding.ts";
|
|
39
40
|
import {
|
|
40
41
|
admissionVerdict,
|
|
@@ -54,6 +55,7 @@ import {
|
|
|
54
55
|
parseNativeCompactionDecision,
|
|
55
56
|
parseProviderContextMeasurementReceipt,
|
|
56
57
|
persistenceProjection,
|
|
58
|
+
pricedBytes,
|
|
57
59
|
protectedStaleMass,
|
|
58
60
|
providerContextMeasurement,
|
|
59
61
|
providerTokens,
|
|
@@ -178,7 +180,6 @@ import {
|
|
|
178
180
|
peekedSourceFoldIds,
|
|
179
181
|
snapFoldCandidate,
|
|
180
182
|
snapToFoldBoundaries,
|
|
181
|
-
spanBytes,
|
|
182
183
|
} from "./lib/selection.ts";
|
|
183
184
|
import type { SpanCorrection } from "./lib/selection.ts";
|
|
184
185
|
import {
|
|
@@ -667,6 +668,12 @@ export function registerActiveContext(pi: any, options: {
|
|
|
667
668
|
|
|
668
669
|
const deliverReceipt = (receipt: ContextReceipt): void => {
|
|
669
670
|
curation.receipts = withReceipt(curation.receipts, receipt);
|
|
671
|
+
// THE BAR'S READING GOES STALE AT A COMMIT. Occupancy is the provider's last count,
|
|
672
|
+
// which does not move until the next response, so after a commit the bar went on
|
|
673
|
+
// showing the pre-commit number as if it were current (38% beside "10 folds hold
|
|
674
|
+
// 8.6M", Shane 2026-09-03). Remember which measurement the commit landed against;
|
|
675
|
+
// while it is still the latest, the bar says the number is from before the commit.
|
|
676
|
+
if (receipt.foldsCommitted > 0) foldBar.staleSince = measurements.lastProviderMeasurement;
|
|
670
677
|
// MONOTONIC, unlike `curation.receipts.length`, which saturates at MAX_CONTEXT_RECEIPTS
|
|
671
678
|
// and would stop distinguishing one approach from the next once the session is long
|
|
672
679
|
// enough to matter. This is the only counter the notice latch reads.
|
|
@@ -718,6 +725,11 @@ export function registerActiveContext(pi: any, options: {
|
|
|
718
725
|
model: null as FoldBarModel | null,
|
|
719
726
|
installed: false,
|
|
720
727
|
requestRender: null as (() => void) | null,
|
|
728
|
+
/** The provider measurement the last landed commit ran against; the reading is
|
|
729
|
+
* stale while it is still the latest one. */
|
|
730
|
+
staleSince: null as unknown,
|
|
731
|
+
/** Each standing fold's source priced by the image law, keyed by the fold object. */
|
|
732
|
+
pricedSource: new WeakMap<ActiveFold, number>(),
|
|
721
733
|
};
|
|
722
734
|
const installFoldBar = (ctx: any): void => {
|
|
723
735
|
if (foldBar.installed || typeof ctx.ui?.setWidget !== "function") return;
|
|
@@ -745,7 +757,20 @@ export function registerActiveContext(pi: any, options: {
|
|
|
745
757
|
if (state && snapshot) {
|
|
746
758
|
for (const mark of pendingMarks(state)) stagedTokens += estimatedTokens(markFreedBytes(snapshot, state, mark));
|
|
747
759
|
for (const root of orderedRoots(state, snapshot)) placeholderChars += root.fold.placeholderChars;
|
|
748
|
-
|
|
760
|
+
// WHAT FOLDING BOUGHT, IN WINDOW TERMS. `sourceChars` is the transcript count, base64
|
|
761
|
+
// and all, and read as tokens it said "8.6M" over a 1M window. Each standing fold's
|
|
762
|
+
// source is priced by the image law once per fold OBJECT (folds are immutable and
|
|
763
|
+
// replaced, never mutated, so a WeakMap memo is exact) and summed over the whole
|
|
764
|
+
// forest, which is additive because a parent's source is its children's placeholders.
|
|
765
|
+
for (const fold of state.folds) {
|
|
766
|
+
let priced = foldBar.pricedSource.get(fold);
|
|
767
|
+
if (priced === undefined) {
|
|
768
|
+
const source = renderFoldParts(fold.parts, state, snapshot);
|
|
769
|
+
priced = source ? pricedBytes(source) : fold.sourceChars;
|
|
770
|
+
foldBar.pricedSource.set(fold, priced);
|
|
771
|
+
}
|
|
772
|
+
hiddenChars += Math.max(0, priced - fold.placeholderChars);
|
|
773
|
+
}
|
|
749
774
|
}
|
|
750
775
|
const budget = input.budgetTokens > 0 ? input.budgetTokens : 1;
|
|
751
776
|
return {
|
|
@@ -760,6 +785,8 @@ export function registerActiveContext(pi: any, options: {
|
|
|
760
785
|
folds: input.roots,
|
|
761
786
|
hiddenTokens: estimatedTokens(hiddenChars),
|
|
762
787
|
weighed: input.weighed,
|
|
788
|
+
staleAfterCommit: input.share !== null && foldBar.staleSince !== null &&
|
|
789
|
+
foldBar.staleSince === measurements.lastProviderMeasurement,
|
|
763
790
|
stopped: ladder.automaticFailure?.message ?? null,
|
|
764
791
|
};
|
|
765
792
|
};
|
|
@@ -2652,7 +2679,7 @@ export function registerActiveContext(pi: any, options: {
|
|
|
2652
2679
|
});
|
|
2653
2680
|
return null;
|
|
2654
2681
|
}
|
|
2655
|
-
const bytesBefore =
|
|
2682
|
+
const bytesBefore = pricedBytes(projectActiveContext(snapshot, state));
|
|
2656
2683
|
// THE COMMIT CUTS ONLY AS DEEP AS THE BUDGET ASKS (Shane, 2026-08-23). The frontier
|
|
2657
2684
|
// stages as material arrives, so by now the pending set is most of the window; without
|
|
2658
2685
|
// this bound the first commit would fold all of it. `freeingTarget` is a share of the
|
|
@@ -2735,7 +2762,7 @@ export function registerActiveContext(pi: any, options: {
|
|
|
2735
2762
|
}
|
|
2736
2763
|
}
|
|
2737
2764
|
persistence.state = result.state;
|
|
2738
|
-
const bytesAfter =
|
|
2765
|
+
const bytesAfter = pricedBytes(projectActiveContext(snapshot, persistence.state));
|
|
2739
2766
|
const freedBytes = Math.max(0, bytesBefore - bytesAfter);
|
|
2740
2767
|
const pinHeld = protectedStaleMass(snapshot, result.state);
|
|
2741
2768
|
const commitEvent = emit("context.commit", {
|
|
@@ -4499,7 +4526,11 @@ export function registerActiveContext(pi: any, options: {
|
|
|
4499
4526
|
safeNotify(
|
|
4500
4527
|
ctx,
|
|
4501
4528
|
committed
|
|
4502
|
-
|
|
4529
|
+
// THE EPOCH RECORD IS camelCase; `applied_marks` is the EVENT's spelling. Reading
|
|
4530
|
+
// the event's name off the record printed "Committed 0 mark(s)" after a /fold that
|
|
4531
|
+
// had just applied forty (Shane, 2026-09-03, session 01a06746: 378,216 tokens to
|
|
4532
|
+
// 97,577 with the notice saying nothing happened).
|
|
4533
|
+
? `Committed ${ownValue(committed, "appliedMarks") ?? 0} mark(s)${topUp ? " with automatic top-up" : ""}. ` +
|
|
4503
4534
|
"Exact source remains expandable."
|
|
4504
4535
|
: "Nothing to commit: no staged mark could apply (pinned or already-folded spans hold).",
|
|
4505
4536
|
"info",
|
|
@@ -4586,7 +4617,7 @@ export function registerActiveContext(pi: any, options: {
|
|
|
4586
4617
|
// what the staged mark answers with after it.
|
|
4587
4618
|
const spanCost = (from: number, to: number) => ({
|
|
4588
4619
|
entries: Math.abs(to - from) + 1,
|
|
4589
|
-
tokens: estimatedTokens(
|
|
4620
|
+
tokens: estimatedTokens(pricedBytes(snapshot.messages.slice(Math.min(from, to), Math.max(from, to) + 1))),
|
|
4590
4621
|
});
|
|
4591
4622
|
|
|
4592
4623
|
/**
|
|
@@ -23,6 +23,8 @@ import {
|
|
|
23
23
|
import {
|
|
24
24
|
ACTIVE_CONTEXT_POLICY,
|
|
25
25
|
DEFAULT_CONTEXT_WINDOW,
|
|
26
|
+
ESTIMATED_BYTES_PER_TOKEN,
|
|
27
|
+
IMAGE_ESTIMATED_TOKENS,
|
|
26
28
|
servingBudgetTokens,
|
|
27
29
|
} from "./policy.ts";
|
|
28
30
|
import type {
|
|
@@ -594,6 +596,25 @@ export function parseNativeCompactionCompletion(value: unknown, expectedSessionI
|
|
|
594
596
|
* needs to be the right order of magnitude instead of wrong by seventy times, and the
|
|
595
597
|
* provider anchor corrects the remainder the moment a real measurement lands.
|
|
596
598
|
*/
|
|
599
|
+
/**
|
|
600
|
+
* WHAT A SPAN COSTS THE WINDOW, in the byte unit every estimator already speaks.
|
|
601
|
+
*
|
|
602
|
+
* `bytes()` counts a message's serialized characters, and a pasted screenshot serializes
|
|
603
|
+
* as hundreds of thousands of base64 characters the provider prices as one image. The
|
|
604
|
+
* occupancy estimate has applied that law since gate 156; the pricing of what a fold
|
|
605
|
+
* FREES had not, so a session holding 83 screenshots (67.3M base64 characters against
|
|
606
|
+
* 1.5M of text) told its owner a commit had "5.8M to free" against a 1M window, and a
|
|
607
|
+
* commit that moved the window 280,000 tokens recorded 8.6M freed (Shane, 2026-09-03,
|
|
608
|
+
* session 01a06746). Text is counted as its bytes; each image is counted as the bytes
|
|
609
|
+
* that estimate to IMAGE_ESTIMATED_TOKENS, so `estimatedTokens(pricedBytes(m))` is the
|
|
610
|
+
* same reading gate 156 gives the projection. Every "frees N" figure derives from this.
|
|
611
|
+
*/
|
|
612
|
+
export function pricedBytes(messages: unknown[]): number {
|
|
613
|
+
const images = imageMass(messages);
|
|
614
|
+
return Math.max(0, bytes(messages) - images.base64Chars) +
|
|
615
|
+
images.images * IMAGE_ESTIMATED_TOKENS * ESTIMATED_BYTES_PER_TOKEN;
|
|
616
|
+
}
|
|
617
|
+
|
|
597
618
|
export function imageMass(projected: unknown[]): { base64Chars: number; images: number } {
|
|
598
619
|
let base64Chars = 0;
|
|
599
620
|
let images = 0;
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
orderedRoots,
|
|
28
28
|
protectedStaleMass,
|
|
29
29
|
refsProtected,
|
|
30
|
+
pricedBytes,
|
|
30
31
|
} from "./measurement.ts";
|
|
31
32
|
import {
|
|
32
33
|
childFoldIds,
|
|
@@ -179,13 +180,15 @@ export function markFreedBytes(
|
|
|
179
180
|
...state,
|
|
180
181
|
expanded: state.expanded.filter((id) => id !== mark.id),
|
|
181
182
|
}, snapshot);
|
|
182
|
-
return expanded && folded ? Math.max(0,
|
|
183
|
+
return expanded && folded ? Math.max(0, pricedBytes(expanded) - pricedBytes(folded)) : 0;
|
|
183
184
|
}
|
|
184
185
|
const source = renderFoldParts(mark.parts, state, snapshot);
|
|
185
186
|
if (!source) return 0;
|
|
186
187
|
const placeholders = mark.kind === "tool-result" ? mark.parts.length : 1;
|
|
187
188
|
const placeholder = placeholders * (mark.brief.length + ESTIMATED_PLACEHOLDER_OVERHEAD_BYTES);
|
|
188
|
-
|
|
189
|
+
// PRICED, NOT COUNTED: a span holding a screenshot is worth one image to the
|
|
190
|
+
// window, not its base64 length (measurement.ts, pricedBytes).
|
|
191
|
+
return Math.max(0, pricedBytes(source) - placeholder);
|
|
189
192
|
}
|
|
190
193
|
|
|
191
194
|
export function markSpanStart(
|
|
@@ -55,6 +55,8 @@ export interface FoldBarModel {
|
|
|
55
55
|
hiddenTokens: number;
|
|
56
56
|
/** Whether a band-top commit has already been weighed against the current count. */
|
|
57
57
|
weighed: boolean;
|
|
58
|
+
/** A commit landed after the count `share` reads; the number is from before it. */
|
|
59
|
+
staleAfterCommit: boolean;
|
|
58
60
|
/** The suspension message when automatic folding has stopped, else null. */
|
|
59
61
|
stopped: string | null;
|
|
60
62
|
}
|
|
@@ -154,19 +156,27 @@ export function renderFoldBar(model: FoldBarModel, width: number, theme: FoldBar
|
|
|
154
156
|
else bar += ink[cells[i]](GLYPH[cells[i]]);
|
|
155
157
|
}
|
|
156
158
|
const pct = Math.round(model.share * 100);
|
|
157
|
-
const parts: string[] = [
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
//
|
|
162
|
-
//
|
|
163
|
-
if (model.
|
|
164
|
-
parts.push(
|
|
165
|
-
|
|
166
|
-
|
|
159
|
+
const parts: string[] = [];
|
|
160
|
+
// A READING FROM BEFORE THE COMMIT SAYS SO. The count only moves when the provider
|
|
161
|
+
// answers, so right after a commit the bar showed the pre-commit percentage in the
|
|
162
|
+
// same type as a live one and read as a commit that did nothing. No estimate is
|
|
163
|
+
// drawn in its place: the freed bytes are transcript mass and the arithmetic would
|
|
164
|
+
// go negative against the window.
|
|
165
|
+
if (model.staleAfterCommit) {
|
|
166
|
+
parts.push(theme.fg("muted", `${pct}% before the commit`));
|
|
167
|
+
} else {
|
|
168
|
+
parts.push(theme.fg("text", `${pct}%`));
|
|
169
|
+
if (model.share < model.commitShare) parts.push(theme.fg("muted", `commit at ${Math.round(model.commitShare * 100)}%`));
|
|
170
|
+
else if (model.weighed) parts.push(theme.fg("muted", "commit held"));
|
|
171
|
+
else parts.push(theme.fg("warning", "COMMIT DUE"));
|
|
167
172
|
}
|
|
173
|
+
// A COUNT FOR WHAT IS STAGED, TOKENS FOR WHAT FOLDING BOUGHT. Both figures are priced
|
|
174
|
+
// by the image law now (measurement.ts pricedBytes): "5.8M to free" beside a 1M window
|
|
175
|
+
// was 83 screenshots' base64 counted as text (Shane 2026-09-03). The unit is written
|
|
176
|
+
// out because pi's k/M format read as megabytes.
|
|
177
|
+
if (model.stagedMarks > 0) parts.push(ink.staged(`${model.stagedMarks} staged`));
|
|
168
178
|
if (model.folds > 0) {
|
|
169
|
-
parts.push(ink.folded(`${model.folds} fold${model.folds === 1 ? "" : "s"}
|
|
179
|
+
parts.push(ink.folded(`${model.folds} fold${model.folds === 1 ? "" : "s"} hide ${formatTokens(model.hiddenTokens)} tokens`));
|
|
170
180
|
}
|
|
171
181
|
// NO BRAND IN FRONT OF THE BAR (Shane 2026-09-02): the bar identifies itself, and the
|
|
172
182
|
// two prose states above keep the brand because nothing else on them says whose they are.
|