pi-fold 4.0.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.
@@ -11,7 +11,7 @@ export {
11
11
 
12
12
  const PUBLIC_OPTIONS = Object.freeze([
13
13
  "thresholds", "providerInputBudget", "blacklistAutoFoldTools", "toolFoldThreshold",
14
- "postFoldNotice",
14
+ "preCommitNotice", "noticeLeadShare",
15
15
  ]);
16
16
 
17
17
  const GENERATOR_DELETED = "the model brief generator is deleted (2026-08-14): across 15 sealed " +
@@ -32,8 +32,13 @@ const REFUSED_OPTIONS = Object.freeze({
32
32
  guidance: "deleted 2026-08-23: it switched the copy that taught the agent to " +
33
33
  "CHOOSE SPANS, and the agent does not choose spans. The runtime cuts at the frontier and " +
34
34
  "the agent annotates what it cut, so the guidance it switched is replaced wholesale by " +
35
- "the post-fold notice. That notice has its own option, postFoldNotice, which defaults " +
35
+ "the pre-commit notice. That notice has its own option, preCommitNotice, which defaults " +
36
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",
37
42
  toolName: "the deployment identity is hardwired to pi-fold: the tool is pi_fold_context",
38
43
  toolLabel: "the deployment identity is hardwired to pi-fold",
39
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
- if (typeof value !== "string") return false;
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
- if (typeof value !== "string") return false;
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,71 +9,110 @@ import {
9
9
  } from "./policy.ts";
10
10
 
11
11
  /**
12
- * THE POST-FOLD NOTICE: THE ONE MOMENT THE AGENT KNOWS SOMETHING THE RUNTIME DOES NOT.
12
+ * THE PRE-COMMIT NOTICE: STATUS AT THE ONE MOMENT IT CAN STILL BE ACTED ON (2026-08-30).
13
13
  *
14
- * The runtime has just cut folds behind the agent, out of material the agent is still
15
- * looking at. It knows the spans, the sizes and the staleness; it does not know why any
16
- * of it mattered. So the notice states what was cut and asks for exactly one thing, a
17
- * brief, and it states the price of answering, which is nothing: the folds it names are
18
- * PENDING, so a brief written now is stored outside the projection and reaches the window
19
- * only when the commit writes that placeholder for the first time.
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
- * It is batched rather than continuous. One notice per cut is noise, and noise is how
22
- * guidance gets ignored; this one appears when enough folds are standing unbriefed to be
23
- * worth a turn's attention, and it is ephemeral, so it never accumulates.
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
- unbriefed: ReadonlyArray<{ id: string; kind: string; tokens: number; brief?: string }>;
27
- pending: number;
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
- // THE INSTRUCTION IS SEATED BEFORE THE LIST, and the list is what gives when the
33
- // notice runs out of room. The first cut of this built one string with the ids in the
34
- // opening line and handed the whole thing to the bound, so a seven-fold batch spent its
35
- // budget on ids and lost "here is how to answer" off the end: the agent was told which
36
- // folds were unbriefed and not told that briefing them was free, which is the only fact
37
- // in here it cannot work out for itself. Fold count grows without limit and the
38
- // instruction does not, so the instruction is fixed and the ids are the variable part.
39
- const head = [
40
- `[${brand} folds] ${input.unbriefed.length} of your ${input.pending} pending fold(s) carry ` +
41
- "no brief yet. These spans are cut but NOT folded: every byte they cover is still in " +
42
- "front of you exactly as it was, and they enter your window only when a commit " +
43
- "applies them.",
44
- `Give each one a sentence: ${input.toolName} {"action":"brief","id":"<fold-id>","brief":"..."}. ` +
45
- "You know why the span mattered and what you will want back from it; the automatic " +
46
- "brief reads the span alone and does not. Writing it now costs your window nothing, " +
47
- "because the fold is not in your window yet.",
48
- `Wrong boundary? ${input.toolName} {"action":"reboundary","ids":[...]} re-cuts it. ` +
49
- `Should not be taken at all? ${input.toolName} {"action":"unmark","ids":["<fold-id>"]}.`,
50
- ].join("\n");
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 head is the identification; the agent adds what it cannot know.
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.unbriefed
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.unbriefed.length - seated}.`;
103
+ `${input.toolName} {"action":"status"} lists the other ${input.staged.length - seated}.`;
64
104
  let seated = entries.length;
65
- let list = `Unbriefed: ${entries.join("; ")}.`;
105
+ let list = `Staged spans: ${entries.join("; ")}.`;
66
106
  while (seated > 0 && Buffer.byteLength(`${head}\n${list}`, "utf8") > FOLD_NOTICE_BYTES) {
67
107
  seated -= 1;
68
108
  list = seated === 0
69
109
  ? overflowText(0)
70
- : `Unbriefed: ${entries.slice(0, seated).join("; ")}. ${overflowText(seated)}`;
110
+ : `Staged spans: ${entries.slice(0, seated).join("; ")}. ${overflowText(seated)}`;
71
111
  }
72
112
  return boundReceiptText(`${head}\n${list}`, FOLD_NOTICE_BYTES,
73
- `[${brand} folds] Pending folds are waiting for briefs; the list is unavailable this pass.`);
113
+ `[${brand} folds] A commit is approaching; the staged list is unavailable this pass.`);
74
114
  }
75
115
 
76
-
77
116
  export interface ContextReceipt {
78
117
  kind: string;
79
118
  ordinal: number;