pi-fold 2.0.0 → 3.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 +79 -191
- package/extensions/active-context.ts +1332 -526
- package/extensions/evidence.js +1 -5
- package/extensions/index.js +17 -3
- package/extensions/json.ts +23 -1
- package/extensions/lib/canonical.ts +34 -0
- package/extensions/lib/curation.ts +94 -61
- package/extensions/lib/editor-ui.ts +739 -0
- package/extensions/lib/folding.ts +207 -59
- package/extensions/lib/instrumentation.ts +10 -9
- package/extensions/lib/measurement.ts +43 -16
- package/extensions/lib/persistence.ts +273 -46
- package/extensions/lib/policy.ts +251 -95
- package/extensions/lib/scheduling.ts +446 -126
- package/extensions/lib/selection.ts +117 -116
- package/extensions/lib/tool-surface.ts +60 -33
- package/extensions/lib/transcript.ts +82 -74
- package/extensions/settings.ts +456 -0
- package/package.json +4 -1
package/extensions/evidence.js
CHANGED
|
@@ -562,10 +562,6 @@ function mcpServerForTool(toolName, fallback) {
|
|
|
562
562
|
: fallback;
|
|
563
563
|
}
|
|
564
564
|
|
|
565
|
-
function conventionalMcpTool(toolName) {
|
|
566
|
-
return mcpServerForTool(toolName, null) !== null;
|
|
567
|
-
}
|
|
568
|
-
|
|
569
565
|
export function registerEvidenceIngestion(pi, {
|
|
570
566
|
entryTypePrefix = DEFAULT_ACTIVE_CONTEXT_ENTRY_TYPE_PREFIX,
|
|
571
567
|
} = {}) {
|
|
@@ -626,7 +622,7 @@ export function registerEvidenceIngestion(pi, {
|
|
|
626
622
|
return projectEvidenceText(text, descriptor, "utf8-head", truncation ? { truncation } : undefined);
|
|
627
623
|
}
|
|
628
624
|
|
|
629
|
-
if (
|
|
625
|
+
if (mcpServerForTool(toolName, null) !== null) {
|
|
630
626
|
const envelope = eventEnvelope(event);
|
|
631
627
|
const serialized = `${stableStringify(envelope)}\n`;
|
|
632
628
|
if (utf8Bytes(serialized) <= TOOL_RESULT_PROJECTION_BYTES) return;
|
package/extensions/index.js
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
import { registerActiveContext } from "./active-context.ts";
|
|
2
2
|
import { registerEvidenceIngestion } from "./evidence.js";
|
|
3
3
|
|
|
4
|
+
export {
|
|
5
|
+
applyFoldSettingsEdit,
|
|
6
|
+
loadFoldSettingsFile,
|
|
7
|
+
readFoldSettingsFile,
|
|
8
|
+
registerFoldSettings,
|
|
9
|
+
saveFoldSettingsFile,
|
|
10
|
+
} from "./settings.ts";
|
|
11
|
+
|
|
4
12
|
const PUBLIC_OPTIONS = Object.freeze([
|
|
5
|
-
"thresholds", "providerInputBudget", "blacklistAutoFoldTools", "
|
|
13
|
+
"thresholds", "providerInputBudget", "blacklistAutoFoldTools", "toolFoldThreshold",
|
|
14
|
+
"workingMemory", "postFoldNotice",
|
|
6
15
|
]);
|
|
7
16
|
|
|
8
17
|
const GENERATOR_DELETED = "the model brief generator is deleted (2026-08-14): across 15 sealed " +
|
|
@@ -16,15 +25,20 @@ const INVERTED_AUTO_FOLD_LIST = "renamed blacklistAutoFoldTools, and the sense i
|
|
|
16
25
|
"it meant to permit";
|
|
17
26
|
|
|
18
27
|
const REFUSED_OPTIONS = Object.freeze({
|
|
28
|
+
guidance: "deleted 2026-08-23: it switched the copy that taught the agent to " +
|
|
29
|
+
"CHOOSE SPANS, and the agent does not choose spans. The runtime cuts at the frontier and " +
|
|
30
|
+
"the agent annotates what it cut, so the guidance it switched is replaced wholesale by " +
|
|
31
|
+
"the post-fold notice. That notice has its own option, postFoldNotice, which defaults " +
|
|
32
|
+
"false: the deterministic shape is what the campaign measured",
|
|
19
33
|
toolName: "the deployment identity is hardwired to pi-fold: the tool is pi_fold_context",
|
|
20
34
|
toolLabel: "the deployment identity is hardwired to pi-fold",
|
|
21
35
|
brandNoun: "the deployment identity is hardwired to pi-fold",
|
|
22
36
|
entryTypePrefix: "the deployment identity is hardwired to pi-fold: durable state lives " +
|
|
23
37
|
"under pi-fold-active-context, and moving it would strand every fold already written",
|
|
24
38
|
commandPrefix: "the deployment identity is hardwired to pi-fold: the commands are " +
|
|
25
|
-
"/
|
|
39
|
+
"/fold, /fold-editor, /fold-status and /fold-settings",
|
|
26
40
|
commandNames: "the deployment identity is hardwired to pi-fold: the commands are " +
|
|
27
|
-
"/
|
|
41
|
+
"/fold, /fold-editor, /fold-status and /fold-settings",
|
|
28
42
|
isMcpTool: "MCP tools are recognized by the mcp__server__tool naming convention. The " +
|
|
29
43
|
"predicate defaulted to false, so conventionally named MCP tools were never classified " +
|
|
30
44
|
"unless a host supplied one, which is a documented feature that never ran",
|
package/extensions/json.ts
CHANGED
|
@@ -194,8 +194,30 @@ export function isObjectRef(value: unknown): value is EvidenceRef {
|
|
|
194
194
|
typeof sha256 === "string" && reflectApply(regexpTest, sha256Pattern, [sha256]);
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
+
// THE HOT PATH (issue #2, 2026-08-28). This is the most-called function in the package:
|
|
198
|
+
// every mark, span, claim, eligibility and measurement pass keys its refs through it,
|
|
199
|
+
// and a reporter profiling a 467KB window found the serializer under it at the top of a
|
|
200
|
+
// V8 profile with the process pegged at 80 percent for forty minutes. The general
|
|
201
|
+
// serializer earns its cost on arbitrary values, checking for proxies, cycles, accessors
|
|
202
|
+
// and non-plain prototypes; none of that is in question for three strings off a ref.
|
|
203
|
+
//
|
|
204
|
+
// The output is IDENTICAL, not merely equivalent, and that is the whole design of this
|
|
205
|
+
// shape: safeJson emits object keys in own-property order rather than sorted, so the
|
|
206
|
+
// literal below is the same byte sequence its object form produced, and string escaping
|
|
207
|
+
// is native JSON.stringify on both paths. It matters because these keys order
|
|
208
|
+
// `protectionSha256`'s sort and reach `branchSha256`'s missing-digest, so a format that
|
|
209
|
+
// merely round-trips would move digests that sealed sessions carry. Anything that is not
|
|
210
|
+
// three strings falls through to the serializer, which owns every remaining case.
|
|
211
|
+
// Measured 8.5x, 4.27us to 0.50us per call, over 2,000 refs. Gate 150 pins the identity.
|
|
197
212
|
export function objectRefKey(ref: EvidenceRef): string {
|
|
198
|
-
|
|
213
|
+
const sessionId = ref.sessionId;
|
|
214
|
+
const entryId = ref.entryId;
|
|
215
|
+
const role = ref.role;
|
|
216
|
+
if (typeof sessionId === "string" && typeof entryId === "string" && typeof role === "string") {
|
|
217
|
+
return `{"sessionId":${jsonStringify(sessionId)},"entryId":${jsonStringify(entryId)},` +
|
|
218
|
+
`"role":${jsonStringify(role)}}`;
|
|
219
|
+
}
|
|
220
|
+
return stableStringify({ sessionId, entryId, role });
|
|
199
221
|
}
|
|
200
222
|
|
|
201
223
|
export function sameObjectIdentity(left: EvidenceRef, right: EvidenceRef): boolean {
|
|
@@ -15,6 +15,40 @@ export function bytes(value: unknown): number {
|
|
|
15
15
|
return Buffer.byteLength(typeof value === "string" ? value : stableStringify(value), "utf8");
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Does `heldText`, the serialization of the first `held` elements, stand as a prefix of
|
|
20
|
+
* `bodyText`, the serialization of the whole array?
|
|
21
|
+
*
|
|
22
|
+
* ARRAY SERIALIZATION IS PREFIX-FREE BY CONSTRUCTION, which is the whole reason this can be
|
|
23
|
+
* answered from a string the caller already has instead of by serializing a slice. `safeJson`
|
|
24
|
+
* emits `[`, then the elements separated by `,`, then `]`: no whitespace, no trailing comma.
|
|
25
|
+
* So the serialization of the first `held` elements is exactly the head of the whole one with
|
|
26
|
+
* `]` in place of the `,` that follows element `held - 1`.
|
|
27
|
+
*
|
|
28
|
+
* The two ends break that rule and are branched on rather than argued away. At `held` 0 the
|
|
29
|
+
* prefix is `[]` and there is no comma to match, so testing `"[," ` would be nonsense. At
|
|
30
|
+
* `held === length` there is no following element and the two strings are simply equal.
|
|
31
|
+
*
|
|
32
|
+
* The comma is the reason this is not a bare `startsWith`, and it is worth being exact about
|
|
33
|
+
* when it earns its place. JSON's object and string forms SELF-TERMINATE, with `}` and a
|
|
34
|
+
* closing quote, so for the message arrays this has one caller for, a bare `startsWith` would
|
|
35
|
+
* happen to agree. Numbers do not self-terminate: `[1]` is a leading substring of `[12]` and
|
|
36
|
+
* a bare `startsWith` accepts it. The separator is what makes the predicate correct for any
|
|
37
|
+
* array rather than for today's caller, and a false positive in that caller splices a stale
|
|
38
|
+
* head in front of a live tail. Gate 154 drives the numeric case for exactly this reason.
|
|
39
|
+
*/
|
|
40
|
+
export function serializedPrefixHolds(
|
|
41
|
+
bodyText: string,
|
|
42
|
+
heldText: string | null | undefined,
|
|
43
|
+
held: number,
|
|
44
|
+
length: number,
|
|
45
|
+
): boolean {
|
|
46
|
+
if (heldText === null || heldText === undefined || length < held) return false;
|
|
47
|
+
if (held === 0) return heldText === "[]";
|
|
48
|
+
if (held === length) return bodyText === heldText;
|
|
49
|
+
return bodyText.startsWith(`${heldText.slice(0, -1)},`);
|
|
50
|
+
}
|
|
51
|
+
|
|
18
52
|
export function boundedUtf8(text: string, maximumBytes: number): string {
|
|
19
53
|
const buffer = Buffer.from(text, "utf8");
|
|
20
54
|
if (buffer.length <= maximumBytes) return text;
|
|
@@ -1,71 +1,112 @@
|
|
|
1
1
|
import { boundReceiptText } from "./measurement.ts";
|
|
2
|
+
import { oneLine } from "./selection.ts";
|
|
2
3
|
import {
|
|
3
4
|
contextBrand,
|
|
4
|
-
CONTEXT_MARK_RESPONSE_BYTES,
|
|
5
5
|
CONTEXT_RECEIPT_BLOCK_BYTES,
|
|
6
|
+
FOLD_NOTICE_BYTES,
|
|
6
7
|
DEFAULT_ACTIVE_CONTEXT_BRAND_NOUN,
|
|
7
8
|
MAX_CONTEXT_RECEIPTS,
|
|
8
9
|
} from "./policy.ts";
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
/**
|
|
12
|
+
* THE POST-FOLD NOTICE: THE ONE MOMENT THE AGENT KNOWS SOMETHING THE RUNTIME DOES NOT.
|
|
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.
|
|
20
|
+
*
|
|
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.
|
|
24
|
+
*/
|
|
25
|
+
export function foldNoticeText(input: {
|
|
26
|
+
unbriefed: ReadonlyArray<{ id: string; kind: string; tokens: number; brief?: string }>;
|
|
27
|
+
pending: number;
|
|
13
28
|
toolName: string;
|
|
14
29
|
brandNoun?: string;
|
|
15
30
|
}): string {
|
|
16
31
|
const brand = contextBrand(input.brandNoun ?? DEFAULT_ACTIVE_CONTEXT_BRAND_NOUN);
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
`[${brand}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
`
|
|
30
|
-
|
|
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");
|
|
51
|
+
// WHAT IT COULD NOT NAME, IT COUNTS (gate 136's law, and gate 115's shape one level
|
|
52
|
+
// out): a list that stops early and says nothing reads as a complete list.
|
|
53
|
+
// EACH ROW IDENTIFIES ITS SPAN. The pending mark carries the deterministic brief the
|
|
54
|
+
// runtime cut it with, and a notice that withholds it makes the agent guess: the
|
|
55
|
+
// dogfooded 1M session answered "I need to brief three pending folds. What are they?
|
|
56
|
+
// 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.
|
|
58
|
+
const NOTICE_ROW_CHARS = 160;
|
|
59
|
+
const entries = input.unbriefed
|
|
60
|
+
.map((fold) => `${fold.id} (${fold.kind}, ~${fold.tokens} tokens)` +
|
|
61
|
+
(fold.brief ? `: ${oneLine(fold.brief, NOTICE_ROW_CHARS)}` : ""));
|
|
62
|
+
const overflowText = (seated: number) =>
|
|
63
|
+
`${input.toolName} {"action":"status"} lists the other ${input.unbriefed.length - seated}.`;
|
|
64
|
+
let seated = entries.length;
|
|
65
|
+
let list = `Unbriefed: ${entries.join("; ")}.`;
|
|
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.`);
|
|
31
74
|
}
|
|
32
75
|
|
|
33
|
-
|
|
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 }>;
|
|
34
86
|
toolName: string;
|
|
35
87
|
brandNoun?: string;
|
|
36
|
-
pendingAgentMarks: number;
|
|
37
|
-
eligibleMarks: number;
|
|
38
|
-
freedTokens: number;
|
|
39
|
-
eligibleFreedTokens: number;
|
|
40
|
-
anchors: string[];
|
|
41
|
-
pinnedShare: number;
|
|
42
|
-
maxPinnedShare: number;
|
|
43
88
|
}): string {
|
|
44
89
|
const brand = contextBrand(input.brandNoun ?? DEFAULT_ACTIVE_CONTEXT_BRAND_NOUN);
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
`
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
].join("\n"),
|
|
66
|
-
2_048,
|
|
67
|
-
`[${brand} notice] Post-commit curation details are unavailable this pass.`,
|
|
68
|
-
);
|
|
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("; ")}.`;
|
|
102
|
+
while (seated > 0 && Buffer.byteLength(`${head}\n${list}`, "utf8") > FOLD_NOTICE_BYTES) {
|
|
103
|
+
seated -= 1;
|
|
104
|
+
list = seated === 0
|
|
105
|
+
? overflowText(0)
|
|
106
|
+
: `Entries: ${rows.slice(0, seated).join("; ")}. ${overflowText(seated)}`;
|
|
107
|
+
}
|
|
108
|
+
return boundReceiptText(`${head}\n${list}`, FOLD_NOTICE_BYTES,
|
|
109
|
+
`[${brand} memory] Working memory has entries; the list is unavailable this pass.`);
|
|
69
110
|
}
|
|
70
111
|
|
|
71
112
|
export interface ContextReceipt {
|
|
@@ -79,8 +120,6 @@ export interface ContextReceipt {
|
|
|
79
120
|
occupancyAfter: number | null;
|
|
80
121
|
spansFolded: number;
|
|
81
122
|
toolResultsFolded: number;
|
|
82
|
-
splitFolds: number;
|
|
83
|
-
splitFromChars: number;
|
|
84
123
|
absorbedWedges: number;
|
|
85
124
|
recovered: boolean;
|
|
86
125
|
protectedBytes: number;
|
|
@@ -99,8 +138,6 @@ export function contextReceipt(input: Partial<ContextReceipt> & { kind: string;
|
|
|
99
138
|
occupancyAfter: input.occupancyAfter ?? null,
|
|
100
139
|
spansFolded: input.spansFolded ?? 0,
|
|
101
140
|
toolResultsFolded: input.toolResultsFolded ?? 0,
|
|
102
|
-
splitFolds: input.splitFolds ?? 0,
|
|
103
|
-
splitFromChars: input.splitFromChars ?? 0,
|
|
104
141
|
absorbedWedges: input.absorbedWedges ?? 0,
|
|
105
142
|
recovered: input.recovered ?? false,
|
|
106
143
|
protectedBytes: input.protectedBytes ?? 0,
|
|
@@ -131,9 +168,6 @@ export function receiptLine(receipt: ContextReceipt): string {
|
|
|
131
168
|
`about ${receipt.freedTokens} tokens freed.`,
|
|
132
169
|
occupancy,
|
|
133
170
|
folded,
|
|
134
|
-
receipt.splitFolds
|
|
135
|
-
? `A ${receipt.splitFromChars}-char span was split into ${receipt.splitFolds} bite-sized folds.`
|
|
136
|
-
: "",
|
|
137
171
|
receipt.absorbedWedges
|
|
138
172
|
? `${receipt.absorbedWedges} short stale span(s) wedged between folds were absorbed into their ` +
|
|
139
173
|
"later neighbour rather than left as crumbs."
|
|
@@ -142,7 +176,7 @@ export function receiptLine(receipt: ContextReceipt): string {
|
|
|
142
176
|
? "This ran as overflow recovery: the request would not have been transmissible, and it was rebuilt rather than dropped."
|
|
143
177
|
: "",
|
|
144
178
|
receipt.protectedBytes
|
|
145
|
-
? `${receipt.protectedBytes} byte(s) stay pinned
|
|
179
|
+
? `${receipt.protectedBytes} byte(s) stay pinned; unpin releases them when the work moves on.`
|
|
146
180
|
: "",
|
|
147
181
|
receipt.note ?? "",
|
|
148
182
|
];
|
|
@@ -160,9 +194,8 @@ export function receiptBlockText(input: {
|
|
|
160
194
|
`[${brand} actions] Recent automatic context actions, newest last. Exact source remains expandable.`;
|
|
161
195
|
const verbs = `Correct any of it: ${input.toolName} ` +
|
|
162
196
|
"{\"action\":\"expand\",\"id\":\"<fold-id>\"} restores a span in place; " +
|
|
163
|
-
"{\"action\":\"
|
|
164
|
-
"
|
|
165
|
-
"mis-cut fold to raw so you can fold the span you meant.";
|
|
197
|
+
"{\"action\":\"reboundary\",\"id\":\"<fold-id>\"} returns a mis-cut fold to raw so you can " +
|
|
198
|
+
"fold the span you meant.";
|
|
166
199
|
const fixed = Buffer.byteLength(header, "utf8") + Buffer.byteLength(verbs, "utf8") + 1;
|
|
167
200
|
if (fixed > CONTEXT_RECEIPT_BLOCK_BYTES) {
|
|
168
201
|
return `[${brand} actions] Recent automatic context actions are unavailable this pass.`;
|