pi-fold 3.1.0 → 4.1.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 +26 -28
- package/extensions/active-context.ts +645 -412
- package/extensions/index.js +11 -2
- package/extensions/lib/brief-text.ts +116 -0
- package/extensions/lib/canonical.ts +3 -18
- package/extensions/lib/curation.ts +208 -74
- package/extensions/lib/editor-ui.ts +595 -66
- package/extensions/lib/folding.ts +20 -2
- package/extensions/lib/instrumentation.ts +3 -1
- package/extensions/lib/live-settings.ts +73 -0
- package/extensions/lib/measurement.ts +47 -0
- package/extensions/lib/persistence.ts +42 -39
- package/extensions/lib/policy.ts +72 -51
- package/extensions/lib/selection.ts +9 -44
- package/extensions/lib/tool-surface.ts +2 -32
- package/extensions/lib/transcript.ts +43 -0
- package/extensions/settings.ts +200 -32
- package/package.json +5 -5
package/extensions/index.js
CHANGED
|
@@ -11,7 +11,7 @@ export {
|
|
|
11
11
|
|
|
12
12
|
const PUBLIC_OPTIONS = Object.freeze([
|
|
13
13
|
"thresholds", "providerInputBudget", "blacklistAutoFoldTools", "toolFoldThreshold",
|
|
14
|
-
"
|
|
14
|
+
"preCommitNotice", "noticeLeadShare",
|
|
15
15
|
]);
|
|
16
16
|
|
|
17
17
|
const GENERATOR_DELETED = "the model brief generator is deleted (2026-08-14): across 15 sealed " +
|
|
@@ -25,11 +25,20 @@ const INVERTED_AUTO_FOLD_LIST = "renamed blacklistAutoFoldTools, and the sense i
|
|
|
25
25
|
"it meant to permit";
|
|
26
26
|
|
|
27
27
|
const REFUSED_OPTIONS = Object.freeze({
|
|
28
|
+
workingMemory: "deleted 2026-08-28: a session-scoped dictionary with remember and recall, "
|
|
29
|
+
+ "offered to one sealed run that never called it once, wire-proven. Retention is a durable "
|
|
30
|
+
+ "store's job and folding is the rotation half, so a second unused memory surface was the "
|
|
31
|
+
+ "bloat the KISS rule exists to remove. Sealed state carrying it still loads, entries dropped",
|
|
28
32
|
guidance: "deleted 2026-08-23: it switched the copy that taught the agent to " +
|
|
29
33
|
"CHOOSE SPANS, and the agent does not choose spans. The runtime cuts at the frontier and " +
|
|
30
34
|
"the agent annotates what it cut, so the guidance it switched is replaced wholesale by " +
|
|
31
|
-
"the
|
|
35
|
+
"the pre-commit notice. That notice has its own option, preCommitNotice, which defaults " +
|
|
32
36
|
"false: the deterministic shape is what the campaign measured",
|
|
37
|
+
postFoldNotice: "renamed preCommitNotice on 2026-08-30, and the carrier it switches has " +
|
|
38
|
+
"changed with it: it fires on occupancy approaching a commit rather than on a count of " +
|
|
39
|
+
"unbriefed folds, and it STATES the window's status rather than asking the model for " +
|
|
40
|
+
"briefs, which sol-20260826-full2 scored at 3 correct against 9 wrong. The default is " +
|
|
41
|
+
"still false; noticeLeadShare sets how far ahead of the commit it speaks",
|
|
33
42
|
toolName: "the deployment identity is hardwired to pi-fold: the tool is pi_fold_context",
|
|
34
43
|
toolLabel: "the deployment identity is hardwired to pi-fold",
|
|
35
44
|
brandNoun: "the deployment identity is hardwired to pi-fold",
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/* THE BOUNDING PRIMITIVES. Pure string rules, no fold state and no host types, so a
|
|
2
|
+
second consumer can hold the same rules rather than a second implementation of them.
|
|
3
|
+
Extracted 2026-08-30 when the DeepSeek Harness build became the third runtime that
|
|
4
|
+
needs them: dsh-fold mirrors this file and nothing else from lib.
|
|
5
|
+
|
|
6
|
+
Every deterministic cut in the runtime runs through boundedSubject so a bound always
|
|
7
|
+
leaves its mark: "..." means exactly "content continues in the exact source", and a
|
|
8
|
+
reader never mistakes a cut value for a complete one. oneLine collapses whitespace
|
|
9
|
+
then delegates; factualValue sanitizes then delegates through oneLine. No other site
|
|
10
|
+
slices. Gate 136 pins this.
|
|
11
|
+
|
|
12
|
+
The two brief predicates take their bounds as required arguments here. Their callers
|
|
13
|
+
in canonical.ts re-export them with the policy defaults applied, so every existing
|
|
14
|
+
call site is unchanged and the policy stays owned by policy.ts. */
|
|
15
|
+
|
|
16
|
+
export function boundedSubject(text: string, budget: number): string {
|
|
17
|
+
if (text.length <= budget) return text;
|
|
18
|
+
let kept = text.slice(0, Math.max(0, budget - 3)).trimEnd();
|
|
19
|
+
const finalCode = kept.charCodeAt(kept.length - 1);
|
|
20
|
+
if (finalCode >= 0xd800 && finalCode <= 0xdbff) kept = kept.slice(0, -1);
|
|
21
|
+
return `${kept}...`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function oneLine(value: string, maximum: number): string {
|
|
25
|
+
return boundedSubject(value.replace(/\s+/g, " ").trim(), maximum);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* THE TOOL-CLIP VIEW HEAD (2026-08-24). A clipped result keeps its opening verbatim,
|
|
29
|
+
not collapsed: the view stays a readable result head in place, unlike a brief's
|
|
30
|
+
one-line quote. The paragraph rule is gate 134's: an opening block terminated by a
|
|
31
|
+
blank line is kept whole up to the cap; a result that is bulk from line one keeps its
|
|
32
|
+
first cap's worth. The caller states the cut, per gate 136: the marker rides beside
|
|
33
|
+
the count of what it hides, never silently. */
|
|
34
|
+
export function toolClipHead(text: string, cap: number): string {
|
|
35
|
+
const lines = String(text ?? "").split(/\r?\n/);
|
|
36
|
+
const kept: string[] = [];
|
|
37
|
+
for (const line of lines) {
|
|
38
|
+
if (line.trim()) kept.push(line);
|
|
39
|
+
else if (kept.length) break;
|
|
40
|
+
}
|
|
41
|
+
const paragraph = kept.join("\n");
|
|
42
|
+
const head = paragraph.length > 0 && paragraph.length <= cap
|
|
43
|
+
? paragraph
|
|
44
|
+
: String(text ?? "").slice(0, cap);
|
|
45
|
+
return head;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function usefulBriefWithin(value: unknown, maximum: number, toolName: string): value is string {
|
|
49
|
+
if (typeof value !== "string") return false;
|
|
50
|
+
const brief = value.trim();
|
|
51
|
+
if (!brief || brief.length > maximum) return false;
|
|
52
|
+
const factualLines: string[] = [];
|
|
53
|
+
const lines = brief.split(/\r?\n/);
|
|
54
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
55
|
+
const text = lines[index].trim();
|
|
56
|
+
if (!text || text.toLowerCase().includes(toolName.toLowerCase()) ||
|
|
57
|
+
/^\[[^\]]*(?:fold|active-context)[^\]]*\]$/i.test(text)) continue;
|
|
58
|
+
if (/^(?:topology\b|expand\b|refold\b|list(?:\/page)?\b|page\b|action\b|fold(?:ed)?\s+(?:placeholder|uid|id)\b|(?:this\s+)?fold\b.*\b(?:expand|refold|placeholder|topology|uid|id)\b|(?:parent|children?|previous|next|navigation)\s*[:=])/i.test(text)) continue;
|
|
59
|
+
factualLines.push(text);
|
|
60
|
+
}
|
|
61
|
+
const factual = factualLines.join(" ").trim();
|
|
62
|
+
return /[A-Za-z0-9]{3,}/.test(factual);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function structurallyValidBriefWithin(value: unknown, maximum: number): value is string {
|
|
66
|
+
if (typeof value !== "string") return false;
|
|
67
|
+
const brief = value.trim();
|
|
68
|
+
return Boolean(brief) && brief.length <= maximum &&
|
|
69
|
+
(brief.match(/[A-Za-z0-9]/g)?.length ?? 0) >= 3;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* THE DIVIDED SEATING (extracted 2026-08-30 for the same second consumer). A brief that
|
|
73
|
+
names several subjects does NOT concatenate and slice: a slice at the cap drops whole
|
|
74
|
+
subjects with no statement that it did, which is the silent-cut defect gate 136 exists
|
|
75
|
+
to refuse, one level up. Instead the room left by the lead is divided.
|
|
76
|
+
|
|
77
|
+
Two bounds, in order. The count-slice first: room admits at most one subject per
|
|
78
|
+
minSubjectChars, because a share below that names nothing a reader can use, and every
|
|
79
|
+
subject past that count is DROPPED AND COUNTED in the tail. Then the char division
|
|
80
|
+
over the subjects that did seat, ascending by length so a short subject claims only
|
|
81
|
+
what it needs and returns the rest to the pool; the longest subject is bounded last
|
|
82
|
+
against everything the shorter ones gave back, which is what lets a note shorter than
|
|
83
|
+
its fair share ride whole. Ties hold source order, so the output is deterministic.
|
|
84
|
+
|
|
85
|
+
Every cut inside a seated subject goes through boundedSubject, so it carries the
|
|
86
|
+
marker. The caller owns the lead, the minimum and the noun the tail counts, because
|
|
87
|
+
those are policy; the division is not. */
|
|
88
|
+
export function seatSubjects(
|
|
89
|
+
subjects: readonly string[],
|
|
90
|
+
lead: string,
|
|
91
|
+
options: {
|
|
92
|
+
readonly total: number;
|
|
93
|
+
readonly minSubjectChars: number;
|
|
94
|
+
readonly omittedNoun: string;
|
|
95
|
+
readonly separator?: string;
|
|
96
|
+
},
|
|
97
|
+
): string {
|
|
98
|
+
const separator = options.separator ?? " | ";
|
|
99
|
+
const room = options.total - lead.length - 1;
|
|
100
|
+
const named = Math.max(1, Math.min(subjects.length,
|
|
101
|
+
Math.floor((room + separator.length) / (options.minSubjectChars + separator.length))));
|
|
102
|
+
const omitted = subjects.length - named;
|
|
103
|
+
const tail = omitted ? `${separator}${omitted} more ${options.omittedNoun}` : "";
|
|
104
|
+
const budget = room - tail.length - separator.length * (named - 1);
|
|
105
|
+
const order = subjects.slice(0, named).map((text, index) => ({ text, index })).sort((a, b) =>
|
|
106
|
+
a.text.length - b.text.length || a.index - b.index);
|
|
107
|
+
const bounded = new Array<string>(named);
|
|
108
|
+
let left = budget;
|
|
109
|
+
for (let taken = 0; taken < order.length; taken += 1) {
|
|
110
|
+
const owed = Math.max(1, Math.floor(left / (order.length - taken)));
|
|
111
|
+
const kept = boundedSubject(order[taken].text, owed);
|
|
112
|
+
bounded[order[taken].index] = kept;
|
|
113
|
+
left -= kept.length;
|
|
114
|
+
}
|
|
115
|
+
return `${lead}${bounded.join(separator)}${tail}.`.replace(/\s+/g, " ").trim();
|
|
116
|
+
}
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
stableStringify,
|
|
6
6
|
} from "../json.ts";
|
|
7
7
|
import { ACTIVE_CONTEXT_POLICY, DEFAULT_ACTIVE_CONTEXT_TOOL_NAME } from "./policy.ts";
|
|
8
|
+
import { structurallyValidBriefWithin, usefulBriefWithin } from "./brief-text.ts";
|
|
8
9
|
import type { ActiveContextState } from "./policy.ts";
|
|
9
10
|
|
|
10
11
|
export function clone<T>(value: T): T {
|
|
@@ -185,25 +186,9 @@ export function usefulBrief(
|
|
|
185
186
|
maximum = ACTIVE_CONTEXT_POLICY.maxBriefChars,
|
|
186
187
|
toolName = DEFAULT_ACTIVE_CONTEXT_TOOL_NAME,
|
|
187
188
|
): value is string {
|
|
188
|
-
|
|
189
|
-
const brief = value.trim();
|
|
190
|
-
if (!brief || brief.length > maximum) return false;
|
|
191
|
-
const factualLines: string[] = [];
|
|
192
|
-
const lines = brief.split(/\r?\n/);
|
|
193
|
-
for (let index = 0; index < lines.length; index += 1) {
|
|
194
|
-
const text = lines[index].trim();
|
|
195
|
-
if (!text || text.toLowerCase().includes(toolName.toLowerCase()) ||
|
|
196
|
-
/^\[[^\]]*(?:fold|active-context)[^\]]*\]$/i.test(text)) continue;
|
|
197
|
-
if (/^(?:topology\b|expand\b|refold\b|list(?:\/page)?\b|page\b|action\b|fold(?:ed)?\s+(?:placeholder|uid|id)\b|(?:this\s+)?fold\b.*\b(?:expand|refold|placeholder|topology|uid|id)\b|(?:parent|children?|previous|next|navigation)\s*[:=])/i.test(text)) continue;
|
|
198
|
-
factualLines.push(text);
|
|
199
|
-
}
|
|
200
|
-
const factual = factualLines.join(" ").trim();
|
|
201
|
-
return /[A-Za-z0-9]{3,}/.test(factual);
|
|
189
|
+
return usefulBriefWithin(value, maximum, toolName);
|
|
202
190
|
}
|
|
203
191
|
|
|
204
192
|
export function structurallyValidBrief(value: unknown, maximum = ACTIVE_CONTEXT_POLICY.maxBriefChars): value is string {
|
|
205
|
-
|
|
206
|
-
const brief = value.trim();
|
|
207
|
-
return Boolean(brief) && brief.length <= maximum &&
|
|
208
|
-
(brief.match(/[A-Za-z0-9]/g)?.length ?? 0) >= 3;
|
|
193
|
+
return structurallyValidBriefWithin(value, maximum);
|
|
209
194
|
}
|
|
@@ -9,104 +9,108 @@ import {
|
|
|
9
9
|
} from "./policy.ts";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* THE
|
|
12
|
+
* THE PRE-COMMIT NOTICE: STATUS AT THE ONE MOMENT IT CAN STILL BE ACTED ON (2026-08-30).
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
14
|
+
* WHAT THIS REPLACED, AND WHY. Until 2026-08-30 this carrier was a brief SOLICITATION.
|
|
15
|
+
* It fired on a count of unbriefed folds and its middle sentence read "You know why the
|
|
16
|
+
* span mattered and what you will want back from it; the automatic brief reads the span
|
|
17
|
+
* alone and does not." That is flattery in the shape of an instruction: it tells the
|
|
18
|
+
* model it holds knowledge the runtime lacks at a moment when its actual grounds are a
|
|
19
|
+
* fold id, a kind and a 160-character head. sol-20260826-full2 priced the result. Agent
|
|
20
|
+
* briefs drew 3 correct against 9 wrong, the worst of every draw on the campaign, with
|
|
21
|
+
* the errors tracking the annotations, which is why postFoldNotice was defaulted false
|
|
22
|
+
* on 2026-08-28 rather than fixed. Shane, 2026-08-30: agent-provided briefs "spent
|
|
23
|
+
* tokens to confuse themselves", so the carrier should be "less of a suggestion to add
|
|
24
|
+
* briefs and more informatory".
|
|
20
25
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
26
|
+
* SO IT STATES AND DOES NOT ASK. Every sentence here is a fact the runtime measured:
|
|
27
|
+
* where the commit fires, how far away it is, what is staged, how much that frees, what
|
|
28
|
+
* is pinned and what the pin costs. The verbs are a REFERENCE LIST, seated last and
|
|
29
|
+
* carrying no argument for using any of them. The agent is told what is true and what it
|
|
30
|
+
* may do; it is not told that it knows something.
|
|
31
|
+
*
|
|
32
|
+
* IT SPEAKS ONCE PER APPROACH. The trigger is occupancy, not a fold count, and the caller
|
|
33
|
+
* latches it on the crossing, so a window that sits in the band for twenty passes gets
|
|
34
|
+
* one notice rather than twenty. It is ephemeral, so it never accumulates.
|
|
35
|
+
*
|
|
36
|
+
* THREE CORRECTIONS OF FACT the old copy could not carry, all of them load-bearing:
|
|
37
|
+
* pinning is not preservation (folding already preserves; a pin keeps bytes RAW AND IN
|
|
38
|
+
* VIEW and frees nothing), a staged span has not moved yet, and the headroom figure says
|
|
39
|
+
* whether it was measured or estimated rather than presenting either as the other.
|
|
24
40
|
*/
|
|
25
41
|
export function foldNoticeText(input: {
|
|
26
|
-
|
|
27
|
-
|
|
42
|
+
staged: ReadonlyArray<{ id: string; kind: string; tokens: number; brief?: string; briefed?: boolean }>;
|
|
43
|
+
freedTokens: number;
|
|
44
|
+
briefedCount: number;
|
|
45
|
+
pinnedEntries: number;
|
|
46
|
+
pinnedShare: number;
|
|
47
|
+
maxPinnedShare: number;
|
|
48
|
+
headroomTokens: number;
|
|
49
|
+
commitAtShare: number;
|
|
50
|
+
measured: boolean;
|
|
28
51
|
toolName: string;
|
|
29
52
|
brandNoun?: string;
|
|
30
53
|
}): string {
|
|
31
54
|
const brand = contextBrand(input.brandNoun ?? DEFAULT_ACTIVE_CONTEXT_BRAND_NOUN);
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
//
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
55
|
+
const n = (value: number): string => Math.round(value).toLocaleString("en-US");
|
|
56
|
+
const pct = (share: number): string => `${Math.round(share * 100)}%`;
|
|
57
|
+
// THE STATUS IS SEATED BEFORE THE LIST, and the list is what gives when the notice runs
|
|
58
|
+
// out of room. Inherited unchanged from the solicitation, for the same reason: fold
|
|
59
|
+
// count grows without limit and the status does not, so a wide batch must not be able
|
|
60
|
+
// to push the facts off the end and leave a bare row of ids behind.
|
|
61
|
+
//
|
|
62
|
+
// MEASURED OR ESTIMATED, NAMED. The runtime's own law is that an estimate may schedule
|
|
63
|
+
// work and may not veto a request; scheduling this notice off an estimate is squarely
|
|
64
|
+
// inside that, but presenting an estimate as a count is not, so the basis rides along.
|
|
65
|
+
const status = [
|
|
66
|
+
`[${brand} folds] A commit fires at ${pct(input.commitAtShare)} of the serving budget: ` +
|
|
67
|
+
`about ${n(input.headroomTokens)} tokens of headroom left ` +
|
|
68
|
+
`(${input.measured ? "provider-measured" : "estimated; no provider count yet"}).`,
|
|
69
|
+
`Staged: ${n(input.staged.length)} fold${input.staged.length === 1 ? "" : "s"}` +
|
|
70
|
+
`, ${n(input.briefedCount)} carrying a brief you wrote` +
|
|
71
|
+
`, freeing about ${n(input.freedTokens)} tokens when the commit applies them.`,
|
|
72
|
+
];
|
|
73
|
+
if (input.pinnedEntries > 0) {
|
|
74
|
+
status.push(`Pinned: ${n(input.pinnedEntries)} ` +
|
|
75
|
+
`entr${input.pinnedEntries === 1 ? "y" : "ies"} held raw, ` +
|
|
76
|
+
`${pct(input.pinnedShare)} of the window against a ${pct(input.maxPinnedShare)} cap.`);
|
|
77
|
+
}
|
|
78
|
+
status.push(
|
|
79
|
+
"Nothing has moved yet: every byte these spans cover is still in front of you, and a " +
|
|
80
|
+
"staged fold enters your window only when the commit applies it. Folded material " +
|
|
81
|
+
"stays exactly recoverable through peek. A pin holds an entry raw and in view and " +
|
|
82
|
+
"frees nothing toward the commit, so pinned mass makes the rest fold sooner.",
|
|
83
|
+
`Verbs: ${input.toolName} {"action":"status"} · {"action":"brief","id":"<fold-id>","brief":"..."} · ` +
|
|
84
|
+
'{"action":"reboundary","ids":[...]} · {"action":"unmark","ids":["<fold-id>"]} · ' +
|
|
85
|
+
'{"action":"pin","ids":["<entry-id>"]} · {"action":"unpin","ids":["<entry-id>"]}.',
|
|
86
|
+
);
|
|
87
|
+
const head = status.join("\n");
|
|
51
88
|
// WHAT IT COULD NOT NAME, IT COUNTS (gate 136's law, and gate 115's shape one level
|
|
52
89
|
// out): a list that stops early and says nothing reads as a complete list.
|
|
53
90
|
// EACH ROW IDENTIFIES ITS SPAN. The pending mark carries the deterministic brief the
|
|
54
91
|
// runtime cut it with, and a notice that withholds it makes the agent guess: the
|
|
55
92
|
// dogfooded 1M session answered "I need to brief three pending folds. What are they?
|
|
56
93
|
// Likely: ..." from memory, and a wrong guess writes an accurate-sounding brief onto
|
|
57
|
-
// the wrong fold. The
|
|
94
|
+
// the wrong fold. The row is the identification, and it now names EVERY staged fold
|
|
95
|
+
// rather than only the unbriefed ones, because this is a status: a list that silently
|
|
96
|
+
// omitted the briefed folds would understate what the commit is about to take.
|
|
58
97
|
const NOTICE_ROW_CHARS = 160;
|
|
59
|
-
const entries = input.
|
|
60
|
-
.map((fold) => `${fold.id} (${fold.kind}, ~${fold.tokens} tokens
|
|
98
|
+
const entries = input.staged
|
|
99
|
+
.map((fold) => `${fold.id} (${fold.kind}, ~${fold.tokens} tokens` +
|
|
100
|
+
`${fold.briefed ? ", briefed" : ""})` +
|
|
61
101
|
(fold.brief ? `: ${oneLine(fold.brief, NOTICE_ROW_CHARS)}` : ""));
|
|
62
102
|
const overflowText = (seated: number) =>
|
|
63
|
-
`${input.toolName} {"action":"status"} lists the other ${input.
|
|
103
|
+
`${input.toolName} {"action":"status"} lists the other ${input.staged.length - seated}.`;
|
|
64
104
|
let seated = entries.length;
|
|
65
|
-
let list = `
|
|
66
|
-
while (seated > 0 && Buffer.byteLength(`${head}\n${list}`, "utf8") > FOLD_NOTICE_BYTES) {
|
|
67
|
-
seated -= 1;
|
|
68
|
-
list = seated === 0
|
|
69
|
-
? overflowText(0)
|
|
70
|
-
: `Unbriefed: ${entries.slice(0, seated).join("; ")}. ${overflowText(seated)}`;
|
|
71
|
-
}
|
|
72
|
-
return boundReceiptText(`${head}\n${list}`, FOLD_NOTICE_BYTES,
|
|
73
|
-
`[${brand} folds] Pending folds are waiting for briefs; the list is unavailable this pass.`);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* THE WORKING MEMORY'S TABLE OF CONTENTS (Shane, 2026-08-26). One copy rides the
|
|
78
|
-
* projection, refreshed at each commit by the carrier freeze: keys only, freshest first,
|
|
79
|
-
* because the bodies live in state and are read on demand. The instruction is fixed and
|
|
80
|
-
* the key list is the variable part, exactly as the fold notice above, and a list that
|
|
81
|
-
* stops early counts what it dropped (gate 136's law). It shares FOLD_NOTICE_BYTES: both
|
|
82
|
-
* are one-glance carriers and a second bound would be a knob with no second job.
|
|
83
|
-
*/
|
|
84
|
-
export function memoryTocText(input: {
|
|
85
|
-
entries: ReadonlyArray<{ key: string; chars: number }>;
|
|
86
|
-
toolName: string;
|
|
87
|
-
brandNoun?: string;
|
|
88
|
-
}): string {
|
|
89
|
-
const brand = contextBrand(input.brandNoun ?? DEFAULT_ACTIVE_CONTEXT_BRAND_NOUN);
|
|
90
|
-
const head = [
|
|
91
|
-
`[${brand} memory] Your working memory holds ${input.entries.length} ` +
|
|
92
|
-
`entr${input.entries.length === 1 ? "y" : "ies"}, freshest first.`,
|
|
93
|
-
`Read any of them with ${input.toolName} {"action":"recall","keys":["<key>"]}. ` +
|
|
94
|
-
`Update an entry whose facts have changed, or add one, with ${input.toolName} ` +
|
|
95
|
-
'{"action":"remember","key":"<key>","body":"..."}; an empty body removes the entry.',
|
|
96
|
-
].join("\n");
|
|
97
|
-
const rows = input.entries.map((entry) => `${entry.key} (${entry.chars} chars)`);
|
|
98
|
-
const overflowText = (seated: number) =>
|
|
99
|
-
`${input.toolName} {"action":"recall"} lists the other ${input.entries.length - seated}.`;
|
|
100
|
-
let seated = rows.length;
|
|
101
|
-
let list = `Entries: ${rows.join("; ")}.`;
|
|
105
|
+
let list = `Staged spans: ${entries.join("; ")}.`;
|
|
102
106
|
while (seated > 0 && Buffer.byteLength(`${head}\n${list}`, "utf8") > FOLD_NOTICE_BYTES) {
|
|
103
107
|
seated -= 1;
|
|
104
108
|
list = seated === 0
|
|
105
109
|
? overflowText(0)
|
|
106
|
-
: `
|
|
110
|
+
: `Staged spans: ${entries.slice(0, seated).join("; ")}. ${overflowText(seated)}`;
|
|
107
111
|
}
|
|
108
112
|
return boundReceiptText(`${head}\n${list}`, FOLD_NOTICE_BYTES,
|
|
109
|
-
`[${brand}
|
|
113
|
+
`[${brand} folds] A commit is approaching; the staged list is unavailable this pass.`);
|
|
110
114
|
}
|
|
111
115
|
|
|
112
116
|
export interface ContextReceipt {
|
|
@@ -213,3 +217,133 @@ export function receiptBlockText(input: {
|
|
|
213
217
|
}
|
|
214
218
|
return [header, ...kept, verbs].join("\n");
|
|
215
219
|
}
|
|
220
|
+
|
|
221
|
+
const STATUS_BAR_WIDTH = 24;
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* /fold-status: THE TEXT VIEW, FOR A PERSON.
|
|
225
|
+
*
|
|
226
|
+
* What this replaced said, in full: "Active context: 12 fold(s), roots a1b2, c3d4. Use
|
|
227
|
+
* pi_fold_context status for exact recursive actions." Three things were wrong with it.
|
|
228
|
+
* It told a HUMAN to call an AGENT tool they have no way to invoke. It printed fold ids,
|
|
229
|
+
* which are addresses for a machine and noise for a reader. And it answered a question
|
|
230
|
+
* nobody asks (how many folds exist) while leaving the only ones they do ask unanswered:
|
|
231
|
+
* how full am I, when does something happen, and what can I do about it.
|
|
232
|
+
*
|
|
233
|
+
* The command survives rather than being deleted because it is the documented fallback
|
|
234
|
+
* when /fold-editor cannot open, which is any session without an interactive UI. That
|
|
235
|
+
* makes it the ONLY window view some deployments ever get, so it carries what the editor
|
|
236
|
+
* header carries and closes by naming the two commands that act.
|
|
237
|
+
*
|
|
238
|
+
* Occupancy is stated as the provider's own measurement when there is one, and as
|
|
239
|
+
* "not measured yet" when there is not. It is never guessed: a made-up percentage is
|
|
240
|
+
* worse than an absent one, and this runtime has already paid once for treating an
|
|
241
|
+
* estimate as though it were a fact.
|
|
242
|
+
*/
|
|
243
|
+
export function foldStatusText(input: {
|
|
244
|
+
brand?: string;
|
|
245
|
+
usedTokens: number | null;
|
|
246
|
+
budgetTokens: number;
|
|
247
|
+
commitAtShare: number;
|
|
248
|
+
aimShare: number;
|
|
249
|
+
roots: number;
|
|
250
|
+
totalFolds: number;
|
|
251
|
+
stagedMarks: number;
|
|
252
|
+
stagedTokens: number;
|
|
253
|
+
pinned: number;
|
|
254
|
+
suspended: string | null;
|
|
255
|
+
foldCommand: string;
|
|
256
|
+
editorCommand: string;
|
|
257
|
+
}): string {
|
|
258
|
+
const brand = contextBrand(input.brand ?? DEFAULT_ACTIVE_CONTEXT_BRAND_NOUN);
|
|
259
|
+
const n = (value: number): string => Math.round(value).toLocaleString("en-US");
|
|
260
|
+
const lines: string[] = [`[${brand} status]`];
|
|
261
|
+
|
|
262
|
+
if (input.usedTokens === null || !(input.budgetTokens > 0)) {
|
|
263
|
+
// "provider" is infrastructure vocabulary and "sets it" names no observable event.
|
|
264
|
+
lines.push(" Window not measured yet; the first model response will measure it.");
|
|
265
|
+
} else {
|
|
266
|
+
const share = input.usedTokens / input.budgetTokens;
|
|
267
|
+
const filled = Math.max(0, Math.min(STATUS_BAR_WIDTH, Math.round(share * STATUS_BAR_WIDTH)));
|
|
268
|
+
const bar = `${"▇".repeat(filled)}${"░".repeat(STATUS_BAR_WIDTH - filled)}`;
|
|
269
|
+
lines.push(` Window ${bar} ${Math.round(share * 100)}% ` +
|
|
270
|
+
`${n(input.usedTokens)} / ${n(input.budgetTokens)} tokens`);
|
|
271
|
+
const commitAt = input.commitAtShare * input.budgetTokens;
|
|
272
|
+
// The headroom is what a person actually wants: not the trigger's share, but how much
|
|
273
|
+
// further this session goes before anything happens to it.
|
|
274
|
+
//
|
|
275
|
+
// A SUSPENDED SESSION IS PROMISED NOTHING. This line used to say "the next pass folds
|
|
276
|
+
// down toward 20%" one line above "Automatic folding is suspended", so the surface
|
|
277
|
+
// contradicted itself about the only thing the reader opened it to find out.
|
|
278
|
+
lines.push(input.suspended
|
|
279
|
+
? ` At the ${Math.round(input.commitAtShare * 100)}% commit point.`
|
|
280
|
+
: share >= input.commitAtShare
|
|
281
|
+
? ` At the ${Math.round(input.commitAtShare * 100)}% commit point; ` +
|
|
282
|
+
`folding now targets ${Math.round(input.aimShare * 100)}%.`
|
|
283
|
+
// "Commits at 80%" reads for a beat as a plural noun, or as a verb with no
|
|
284
|
+
// subject, and the missing subject is the PRODUCT: no surface anywhere said the
|
|
285
|
+
// runtime folds BY ITSELF, so a reader assembling this line with "/fold commits
|
|
286
|
+
// them now" concludes they have installed a manual tool.
|
|
287
|
+
: ` Folds automatically at ${Math.round(input.commitAtShare * 100)}%; ` +
|
|
288
|
+
`${n(commitAt - input.usedTokens)} tokens away.`);
|
|
289
|
+
}
|
|
290
|
+
// THE STOP SITS DIRECTLY UNDER THE WINDOW, not below three rows of routine counts.
|
|
291
|
+
// The order answers the reader's questions in the order they ask them: are we at the
|
|
292
|
+
// threshold, will anything fold, and why not.
|
|
293
|
+
if (input.suspended) {
|
|
294
|
+
lines.push(" STOPPED Automatic folding is suspended.");
|
|
295
|
+
lines.push(` Reason: ${oneLine(input.suspended, 160)}`);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// VISIBLE FIRST, AND THE UNIT NAMED. The always-on line counts visible roots and this
|
|
299
|
+
// one led with the total, so the two surfaces looked like they disagreed about how many
|
|
300
|
+
// folds exist. "Top level" is tree-implementation language; "visible" is what a reader
|
|
301
|
+
// can check against the editor in front of them.
|
|
302
|
+
lines.push(input.totalFolds === 0
|
|
303
|
+
? " Folded nothing yet."
|
|
304
|
+
// "Each keeps its exact source" says the bytes survive but never says a SUMMARY stands
|
|
305
|
+
// where they were, and the likeliest wrong model a reader forms is that folded
|
|
306
|
+
// material is simply gone. One clause teaches what a fold actually is.
|
|
307
|
+
: ` Folded ${n(input.roots)} here, ${n(input.totalFolds)} with nesting; ` +
|
|
308
|
+
"each a summary over its exact source.");
|
|
309
|
+
|
|
310
|
+
if (input.stagedMarks > 0) {
|
|
311
|
+
// TWO CORRECTIONS IN ONE LINE.
|
|
312
|
+
//
|
|
313
|
+
// It said "holding about 28,400 tokens" about a number that is the sum of
|
|
314
|
+
// markFreedBytes: what a commit FREES, which is the same figure the editor header
|
|
315
|
+
// prints as "frees". One surface called it held and the other called it freed, and
|
|
316
|
+
// held was the wrong one.
|
|
317
|
+
//
|
|
318
|
+
// And the brief count is gone, input field and all. It counted provenance "supplied"
|
|
319
|
+
// or "augmented", and a brief the USER types on the editor's brief line is "supplied",
|
|
320
|
+
// so the row told a person the sentence they had just written was somebody else's.
|
|
321
|
+
// Nothing on any surface can add a brief to an already-staged mark either, so it was
|
|
322
|
+
// a number that could be wrong and could not be acted on.
|
|
323
|
+
//
|
|
324
|
+
// What remains leads with the fact that stops "staged" being read as "already
|
|
325
|
+
// applied", and fits inside 80 columns, where the old 105-character line wrapped with
|
|
326
|
+
// its continuation flush left inside the label field, reading as a fifth label.
|
|
327
|
+
lines.push(` Staged ${n(input.stagedMarks)} fold${input.stagedMarks === 1 ? "" : "s"}; ` +
|
|
328
|
+
`nothing moves until a commit frees about ${n(input.stagedTokens)} tokens.`);
|
|
329
|
+
}
|
|
330
|
+
if (input.pinned > 0) {
|
|
331
|
+
lines.push(` Pinned ${n(input.pinned)} entr${input.pinned === 1 ? "y" : "ies"} ` +
|
|
332
|
+
`stay${input.pinned === 1 ? "s" : ""} raw; ` +
|
|
333
|
+
`${input.pinned === 1 ? "it does" : "they do"} not free space.`);
|
|
334
|
+
}
|
|
335
|
+
// The closing line offers only what is actually available: proposing a commit with
|
|
336
|
+
// nothing staged is an instruction that does nothing, which is the failure the deleted
|
|
337
|
+
// fence made unforgivable.
|
|
338
|
+
// "commits them" took its antecedent from whichever row happened to come last, which is
|
|
339
|
+
// Pinned as often as Staged. Name the object.
|
|
340
|
+
lines.push(input.stagedMarks > 0
|
|
341
|
+
// 78, not 80: pi-tui wraps at width minus two, so an 80-column line still wraps on an
|
|
342
|
+
// 80-column terminal, and its continuation lands flush left in the label field.
|
|
343
|
+
? ` ${input.foldCommand} commits the staged folds; ${input.editorCommand} opens the window to steer it.`
|
|
344
|
+
// THE FRESH SESSION IS WHERE THE WRONG MODEL FORMS, so this is the line that has to
|
|
345
|
+
// say folding is automatic. A reader who meets the package here and reads only
|
|
346
|
+
// command names concludes nothing happens unless they type one.
|
|
347
|
+
: ` Folding is automatic; ${input.editorCommand} shows the window and ${input.foldCommand} commits early.`);
|
|
348
|
+
return lines.join("\n");
|
|
349
|
+
}
|