pi-fold 4.2.1 → 4.2.3
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
|
@@ -40,11 +40,11 @@ In an interactive session one compact, full-height bar sits directly above Pi's
|
|
|
40
40
|
<img alt="A single full-height usage bar. Batlow colours group consolidated folds, span folds, tool folds, marked content, raw context and pinned content in that order. The suffix shows percentage, commit point, and fold, mark, and pin counts." src="https://raw.githubusercontent.com/shaneconner/fold/main/media/fold-bar-light.svg">
|
|
41
41
|
</picture>
|
|
42
42
|
|
|
43
|
-
The forty-column bar uses full-height half-blocks for eighty steps of fill,
|
|
43
|
+
The forty-column bar uses full-height half-blocks for eighty steps of fill, reading Pi's `getContextUsage()` directly on every render. Its percentage uses Pi's full context window, the same scale as Pi's footer, including Pi's estimate of messages since the last provider response. Within that fill, colours show the estimated visible composition, grouped by distance from compression: **Consolidated Folds, Span Folds, Tool Folds, Marked, Raw, Pinned**. The first three are compressed now, deepest first; marked content folds at the next commit; raw content is eligible later; pinned content is held against every commit until released. The six shades, from consolidated to pinned, are read off a colour map, [Crameri's Batlow](https://www.fabiocrameri.ch/colourmaps/) end to end by default, so the bar reads as a gradient from most compressed to most settled. `/fold-settings` offers Batlow, viridis, plasma, magma and cividis, and where on the chosen map the two ends sit, so a slice of a map or a reversed one is a keypress away; the bar repaints on each step. Whatever the choice, each shade's lightness is moved away from the background, hue held, until it clears 4.5:1 on dark and light terminals, and label colours match the bar. Each nonempty category keeps at least one half-cell when the measured fill has room; remaining width follows relative mass. This visibility floor makes small compressed categories visible, so individual category widths are not token-exact. At very low occupancy, the largest shares take the available space. There are no individual fold edges, scores, or height differences.
|
|
44
44
|
|
|
45
|
-
The suffix reads `34% · commit at 55% · N Folds (X Cons., Y Span, Z Tool, X Mark, Y Pin)`, naming only the kinds with something to count, in the bar's order; a fresh session reads `0 Folds`. `N Folds` counts visible compressed folds; `Mark` counts pending marks and `Pin` counts protected entries, not additional compressed folds. A tool fold is lossless compression of tool-result context, not a count of tool calls. `/fold-status` retains the detailed token and nesting figures.
|
|
45
|
+
The suffix reads `34% · commit at 55% · N Folds (X Cons., Y Span, Z Tool, X Mark, Y Pin)`, naming only the kinds with something to count, in the bar's order; a fresh session reads `0 Folds`. `N Folds` counts visible compressed folds; `Mark` counts pending marks and `Pin` counts protected entries, not additional compressed folds. A tool fold is lossless compression of tool-result context, not a count of tool calls. `/fold-status` retains the detailed token and nesting figures. Its `Budget` row and the editor's `Budget` header report the separate serving budget and the tokens remaining before a commit.
|
|
46
46
|
|
|
47
|
-
Two dotted marks stand in the band's edge cells: one
|
|
47
|
+
Two dotted marks stand in the band's edge cells: one where a commit aims to land and one where automatic folding is triggered. `minTarget` and `maxTarget` remain shares of the serving budget; the marks and displayed commit point are converted to Pi's full-window scale, so the trigger does not move. With a 272,000-token window and the default 255,616-token serving budget, the 80% trigger appears at about 75% of Pi's window. The usage label rounds down to a whole percentage, matching pi-cc's footer; bar fill and folding decisions keep the unrounded value. The displayed commit point rounds to the nearest whole percentage. Each is drawn over the fill's own colour in a deeper tone of that same hue, so the fill continues behind the mark; on the empty track they stand in muted ink. Neither takes a colour of its own or a warning colour. When Pi reports unknown usage, including immediately after native compaction, the row says `not measured yet`; no saved count or local estimate fills the gap. After a fold commit it identifies the old reading until the provider measures again. Crossing the target is routine runtime work, not a user action item. On reload, saved session context supplies the fold counts and composition without a new model message; the next real context event replaces that read-only view. Unavailable composition is stated as `mapping`; if folding stops, `FOLDING STOPPED` replaces the bar. Non-truecolor terminals use the theme's own colours.
|
|
48
48
|
|
|
49
49
|
## Results
|
|
50
50
|
|
|
@@ -85,7 +85,7 @@ Seven options, and every other name is refused.
|
|
|
85
85
|
| Option | Default | Effect |
|
|
86
86
|
| --- | --- | --- |
|
|
87
87
|
| `thresholds` | `{ maxTarget: 0.80, minTarget: 0.20, consolidateAfter: 10, minFoldChars: 8000 }` | The band, set whole or not at all. The first two are proportions of the serving budget. `consolidateAfter` is how many visible roots a parent is owed. `minFoldChars` is the size a fold has to reach to be worth making, and the floor a gap between folds has to clear to stay raw. Validated atomically; an impossible policy is refused by name rather than clamped. |
|
|
88
|
-
| `providerInputBudget` |
|
|
88
|
+
| `providerInputBudget` | derived from Pi's context window | The tokens this deployment may actually put in a request, already net of any output reservation when supplied. Without it, folding retains its default headroom of 16,384 tokens, capped at 10% of the window. This is a folding allowance, not a measured provider output reservation or a read of Pi's per-model compaction settings. Thresholds use this serving budget; the fold bar uses Pi's full-window scale. |
|
|
89
89
|
| `blacklistAutoFoldTools` | `new Set()` | The exception list. Every completed tool batch is foldable without an agent mark; name a tool here and its results stay raw. |
|
|
90
90
|
| `toolFoldThreshold` | `0.50` | The tool-call diet: a share in `[0, 1)` naming the oldest fraction of the window whose tool results are clipped in view. `0` turns it off. |
|
|
91
91
|
| `preCommitNotice` | `false` | The notice switch. False is the shape that won the campaign above: no carrier reaches the projection and every fold ships the runtime's own words. `true` states the window's status to the agent once per approach to a commit: where the commit fires, how far away it is, what is staged, what that frees, what is pinned and what the pin costs, with the verbs listed and no argument made for using any of them. |
|
|
@@ -118,12 +118,13 @@ What the agent owns is the words. `brief` writes its own sentence onto a pending
|
|
|
118
118
|
A commit fires on one condition: occupancy reaching `maxTarget` of the serving budget. It applies marks stalest first and stops at `minTarget`, so the newest events stay raw and a mark past the bound waits for the next epoch. Below `maxTarget` the window is quiet, because a commit is an epoch transition that risks the whole prefix cache and the right cadence is the fewest commits that keep the window inside its budget.
|
|
119
119
|
|
|
120
120
|
```
|
|
121
|
-
272,000
|
|
121
|
+
272,000 Pi's full context window
|
|
122
|
+
255,616 default serving budget, after folding headroom
|
|
122
123
|
─────────────────────────────────────────────────────
|
|
123
124
|
~250k peak: inflow continues while the epoch computes
|
|
124
|
-
|
|
125
|
+
204,493 maxTarget 0.80 ← about 75% on Pi's scale
|
|
125
126
|
⋮ marks accumulate, free, quiet
|
|
126
|
-
|
|
127
|
+
51,123 minTarget 0.20 ← about 19% on Pi's scale
|
|
127
128
|
─────────────────────────────────────────────────────
|
|
128
129
|
```
|
|
129
130
|
|
|
@@ -739,8 +739,8 @@ export function registerActiveContext(pi: any, options: {
|
|
|
739
739
|
/**
|
|
740
740
|
* THE FOLD BAR. One coloured composition row directly above the footer,
|
|
741
741
|
* drawn by `renderFoldBar` from a model this function rebuilds on every status update.
|
|
742
|
-
* The component reads
|
|
743
|
-
*
|
|
742
|
+
* The component reads Pi's context usage and the LIVE theme at render time. Neither
|
|
743
|
+
* reading is cached between status events; the host is asked to render after
|
|
744
744
|
* each rebuild. Installed once per host UI, on the first update that finds one.
|
|
745
745
|
*/
|
|
746
746
|
const foldBar = {
|
|
@@ -753,6 +753,35 @@ export function registerActiveContext(pi: any, options: {
|
|
|
753
753
|
/** Display-only reconstruction while waiting for the first real context event. */
|
|
754
754
|
startupSnapshot: null as ActiveContextSnapshot | null,
|
|
755
755
|
};
|
|
756
|
+
// DISPLAY ONLY. Pi owns the live reading, including its unmeasured trailing messages
|
|
757
|
+
// and post-compaction unknown state. Never substitute our saved provider measurement
|
|
758
|
+
// or projection estimate. The band remains a share of the SERVING budget: convert its
|
|
759
|
+
// points onto Pi's full-window axis, without changing any scheduling arithmetic.
|
|
760
|
+
const foldBarUsage = (ctx: any): Pick<FoldBarModel, "share" | "percent" | "commitShare" | "aimShare"> => {
|
|
761
|
+
let usage: unknown;
|
|
762
|
+
try { usage = ctx.getContextUsage?.(); } catch { }
|
|
763
|
+
const reportedWindow = ownValue(usage, "contextWindow");
|
|
764
|
+
const modelWindow = ownValue(ctx.model, "contextWindow");
|
|
765
|
+
const validWindow = (value: unknown): value is number =>
|
|
766
|
+
typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
767
|
+
const window = validWindow(reportedWindow) ? reportedWindow
|
|
768
|
+
: validWindow(modelWindow) ? modelWindow : DEFAULT_CONTEXT_WINDOW;
|
|
769
|
+
const tokens = ownValue(usage, "tokens");
|
|
770
|
+
const percent = ownValue(usage, "percent");
|
|
771
|
+
const share = validWindow(reportedWindow) && typeof tokens === "number" &&
|
|
772
|
+
Number.isFinite(tokens) && tokens >= 0 && typeof percent === "number" &&
|
|
773
|
+
Number.isFinite(percent) && percent >= 0 ? percent / 100 : null;
|
|
774
|
+
const capacity = capacityAccounting({
|
|
775
|
+
window: providerInputBudget ?? window,
|
|
776
|
+
truthful: providerInputBudget !== null,
|
|
777
|
+
descriptorWindow: window,
|
|
778
|
+
usedTokens: null,
|
|
779
|
+
});
|
|
780
|
+
const budgetShare = capacity.budgetTokens / window;
|
|
781
|
+
return { share, percent: share === null ? null : percent as number,
|
|
782
|
+
commitShare: thresholds.maxTarget * budgetShare,
|
|
783
|
+
aimShare: thresholds.minTarget * budgetShare };
|
|
784
|
+
};
|
|
756
785
|
const installFoldBar = (ctx: any): void => {
|
|
757
786
|
if (foldBar.installed || typeof ctx.ui?.setWidget !== "function") return;
|
|
758
787
|
foldBar.installed = true;
|
|
@@ -760,10 +789,13 @@ export function registerActiveContext(pi: any, options: {
|
|
|
760
789
|
foldBar.requestRender = () => { try { tui.requestRender(); } catch { } };
|
|
761
790
|
return {
|
|
762
791
|
/** The model behind the row, for the lab and the gates. */
|
|
763
|
-
get model(): FoldBarModel | null {
|
|
792
|
+
get model(): FoldBarModel | null {
|
|
793
|
+
return foldBar.model ? { ...foldBar.model, ...foldBarUsage(ctx) } : null;
|
|
794
|
+
},
|
|
764
795
|
render(width: number): string[] {
|
|
765
|
-
|
|
766
|
-
|
|
796
|
+
const model = this.model;
|
|
797
|
+
if (!model) return [];
|
|
798
|
+
try { return [renderFoldBar(model, width, ctx.ui.theme)]; }
|
|
767
799
|
catch { return []; }
|
|
768
800
|
},
|
|
769
801
|
invalidate() { },
|
|
@@ -771,7 +803,8 @@ export function registerActiveContext(pi: any, options: {
|
|
|
771
803
|
}, { placement: "belowEditor" });
|
|
772
804
|
};
|
|
773
805
|
const foldBarModel = (ctx: any, input: {
|
|
774
|
-
share: number | null;
|
|
806
|
+
share: number | null; percent?: number | null;
|
|
807
|
+
commitShare: number; aimShare: number; staged: number; weighed: boolean;
|
|
775
808
|
}): FoldBarModel => {
|
|
776
809
|
const state = persistence.state;
|
|
777
810
|
// THE SAME SNAPSHOT /fold-status PRICES AGAINST. `markFreedBytes` answers ZERO, silently,
|
|
@@ -785,8 +818,9 @@ export function registerActiveContext(pi: any, options: {
|
|
|
785
818
|
const model: FoldBarModel = {
|
|
786
819
|
brand: brandNoun,
|
|
787
820
|
share: input.share,
|
|
788
|
-
|
|
789
|
-
|
|
821
|
+
percent: input.percent,
|
|
822
|
+
commitShare: input.commitShare,
|
|
823
|
+
aimShare: input.aimShare,
|
|
790
824
|
mapped: false, mass: emptyFoldBarMass(),
|
|
791
825
|
stagedMarks: input.staged,
|
|
792
826
|
folds: 0, foldSpans: 0, foldTruncations: 0, foldConsolidations: 0,
|
|
@@ -863,13 +897,13 @@ export function registerActiveContext(pi: any, options: {
|
|
|
863
897
|
const roots = persistence.state && lifecycle.latestSnapshot ? orderedRoots(persistence.state, lifecycle.latestSnapshot).length : 0;
|
|
864
898
|
const staged = persistence.state ? pendingMarks(persistence.state).length : 0;
|
|
865
899
|
const capacity = currentCapacity(ctx);
|
|
866
|
-
const
|
|
867
|
-
const share =
|
|
900
|
+
const usage = foldBarUsage(ctx);
|
|
901
|
+
const { share } = usage;
|
|
868
902
|
try {
|
|
869
903
|
installFoldBar(ctx);
|
|
870
904
|
if (foldBar.installed) {
|
|
871
905
|
foldBar.model = foldBarModel(ctx, {
|
|
872
|
-
|
|
906
|
+
...usage, staged,
|
|
873
907
|
weighed: ladder.bandTopMeasurement !== null &&
|
|
874
908
|
ladder.bandTopMeasurement === measurements.lastProviderMeasurement,
|
|
875
909
|
});
|
|
@@ -883,13 +917,13 @@ export function registerActiveContext(pi: any, options: {
|
|
|
883
917
|
if (ladder.automaticFailure) parts.push("FOLDING STOPPED");
|
|
884
918
|
// Unmeasured is stated, never guessed: an estimate rendered as a fact is the habit
|
|
885
919
|
// that cost a live session, and this line is the most-read surface there is.
|
|
886
|
-
parts.push(share === null ? "not measured" : `${Math.
|
|
920
|
+
parts.push(share === null ? "not measured" : `${Math.floor(usage.percent ?? share * 100)}% full`);
|
|
887
921
|
// THE LIVE BAND, NOT THE LAST SNAPSHOT'S (2026-08-29). This read the threshold off
|
|
888
922
|
// `lifecycle.latestSnapshot`, which is derived per context event, so a band changed
|
|
889
923
|
// in /fold-settings went on being announced at its old value until the next event
|
|
890
924
|
// rebuilt a snapshot. The number this line owes a person is the one that will fire,
|
|
891
925
|
// and that is the runtime's own variable.
|
|
892
|
-
const trigger =
|
|
926
|
+
const trigger = usage.commitShare;
|
|
893
927
|
// WHEN, not just how full. "68% full" only answers the second question a reader has
|
|
894
928
|
// if they already remember where the trigger sits; naming it answers both.
|
|
895
929
|
//
|
|
@@ -911,9 +945,13 @@ export function registerActiveContext(pi: any, options: {
|
|
|
911
945
|
if (share !== null && !ladder.automaticFailure) {
|
|
912
946
|
const weighed = ladder.bandTopMeasurement !== null &&
|
|
913
947
|
ladder.bandTopMeasurement === measurements.lastProviderMeasurement;
|
|
948
|
+
// Pi may include a trailing estimate ahead of the last provider count. Being
|
|
949
|
+
// over the displayed point alone must not promise an imminent runtime commit.
|
|
950
|
+
const due = capacity.usedTokens !== null &&
|
|
951
|
+
capacity.usedTokens >= thresholds.maxTarget * capacity.budgetTokens;
|
|
914
952
|
parts.push(share < trigger
|
|
915
953
|
? `commit at ${Math.round(trigger * 100)}%`
|
|
916
|
-
: weighed ? "commit held" : "COMMIT DUE");
|
|
954
|
+
: weighed ? "commit held" : due ? "COMMIT DUE" : "at commit point");
|
|
917
955
|
}
|
|
918
956
|
if (staged > 0) parts.push(`${staged} staged`);
|
|
919
957
|
if (roots > 0) parts.push(`${roots} fold${roots === 1 ? "" : "s"}`);
|
|
@@ -261,12 +261,13 @@ export function foldStatusText(input: {
|
|
|
261
261
|
|
|
262
262
|
if (input.usedTokens === null || !(input.budgetTokens > 0)) {
|
|
263
263
|
// "provider" is infrastructure vocabulary and "sets it" names no observable event.
|
|
264
|
-
lines.push("
|
|
264
|
+
lines.push(" Budget not measured yet; the first model response will measure it.");
|
|
265
265
|
} else {
|
|
266
266
|
const share = input.usedTokens / input.budgetTokens;
|
|
267
267
|
const filled = Math.max(0, Math.min(STATUS_BAR_WIDTH, Math.round(share * STATUS_BAR_WIDTH)));
|
|
268
268
|
const bar = `${"▇".repeat(filled)}${"░".repeat(STATUS_BAR_WIDTH - filled)}`;
|
|
269
|
-
|
|
269
|
+
// This detailed view reports serving headroom, not Pi's full-window percentage.
|
|
270
|
+
lines.push(` Budget ${bar} ${Math.round(share * 100)}% ` +
|
|
270
271
|
`${n(input.usedTokens)} / ${n(input.budgetTokens)} tokens`);
|
|
271
272
|
const commitAt = input.commitAtShare * input.budgetTokens;
|
|
272
273
|
// The headroom is what a person actually wants: not the trigger's share, but how much
|
|
@@ -1125,7 +1125,7 @@ export class FoldEditorView {
|
|
|
1125
1125
|
// one surface the rule was never checked against. Nothing here is estimated.
|
|
1126
1126
|
if (occupancy.usedTokens === null || !(occupancy.budgetTokens > 0)) {
|
|
1127
1127
|
lines.push(truncateToWidth(
|
|
1128
|
-
"
|
|
1128
|
+
"budget not measured yet; the first model response will measure it", width));
|
|
1129
1129
|
for (const line of this.stagedLines()) lines.push(truncateToWidth(line, width));
|
|
1130
1130
|
return lines;
|
|
1131
1131
|
}
|
|
@@ -1153,7 +1153,7 @@ export class FoldEditorView {
|
|
|
1153
1153
|
: ` · commit at ${(occupancy.commitOccupancy * 100).toFixed(0)}%${headroom}` +
|
|
1154
1154
|
`${occupancy.commitDue ? " · COMMIT DUE" : ""}`;
|
|
1155
1155
|
lines.push(truncateToWidth(
|
|
1156
|
-
|
|
1156
|
+
`Budget ${occupancyBar(occupancy.usedTokens, occupancy.budgetTokens)} ` +
|
|
1157
1157
|
`${percent}%${timing}` +
|
|
1158
1158
|
` · ${usedTokensText}/${occupancy.budgetTokens.toLocaleString("en-US")}`,
|
|
1159
1159
|
width,
|
|
@@ -254,6 +254,8 @@ export function foldBarShades(palette: FoldBarPalette, dark: boolean): Record<Fo
|
|
|
254
254
|
export interface FoldBarModel {
|
|
255
255
|
brand: string;
|
|
256
256
|
share: number | null;
|
|
257
|
+
/** Pi's original percent avoids round-trip drift when flooring the usage label. */
|
|
258
|
+
percent?: number | null;
|
|
257
259
|
commitShare: number;
|
|
258
260
|
aimShare: number;
|
|
259
261
|
/** Estimated visible mass, counted exactly once, in the category order above. */
|
|
@@ -355,7 +357,7 @@ export function renderFoldBar(model: FoldBarModel, width: number, theme: FoldBar
|
|
|
355
357
|
const muted = (text: string): string => theme.fg("muted", text);
|
|
356
358
|
const brand = theme.fg("dim", model.brand);
|
|
357
359
|
if (model.stopped) return cut(`${brand} ${theme.fg("error", theme.bold("FOLDING STOPPED"))}` +
|
|
358
|
-
(model.share === null ? "" : neutral(` · ${Math.
|
|
360
|
+
(model.share === null ? "" : neutral(` · ${Math.floor(model.percent ?? model.share * 100)}% full`)));
|
|
359
361
|
if (model.share === null) return cut(`${brand} ${muted(`not measured yet · folds automatically at ${Math.round(model.commitShare * 100)}%`)}`);
|
|
360
362
|
const truecolorMode = theme.getColorMode?.() === "truecolor";
|
|
361
363
|
const dark = !lightBackground(theme);
|
|
@@ -429,7 +431,7 @@ export function renderFoldBar(model: FoldBarModel, width: number, theme: FoldBar
|
|
|
429
431
|
bar += bg ? `${bg}${ink(left as FoldBarKind, "▌")}\x1b[49m` : solid(left);
|
|
430
432
|
}
|
|
431
433
|
}
|
|
432
|
-
const pct = Math.
|
|
434
|
+
const pct = Math.floor(model.percent ?? model.share * 100);
|
|
433
435
|
const parts: string[] = [];
|
|
434
436
|
if (model.staleAfterCommit) parts.push(muted(`${pct}% before the commit`));
|
|
435
437
|
else {
|