@hypercarrier/rarebit 0.1.0-alpha.2 → 0.1.0-alpha.4
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/CHANGELOG.md +19 -0
- package/README.md +21 -7
- package/RUNBOOK.md +11 -3
- package/package.json +1 -1
- package/scripts/e2e-recall-pi083.mjs +79 -21
- package/scripts/verify-package.mjs +1 -1
- package/src/extension.mjs +37 -17
- package/test/rarebit-recall.test.mjs +96 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.0-alpha.4 — 2026-08-02
|
|
4
|
+
|
|
5
|
+
Alpha.4 is a new artifact with a sanitized current source lineage and unchanged
|
|
6
|
+
product capability from the alpha.3 cutoff tree `db7d388`. The old tag graphs
|
|
7
|
+
remain public and are not privacy-clean. All existing alpha.1, alpha.2, and
|
|
8
|
+
alpha.3 tags, releases, and npm artifacts remain immutable (there is no alpha.1
|
|
9
|
+
GitHub Release). Alpha.3 remains the original readable-Recall release. Intended
|
|
10
|
+
npm routing after publication is `next=alpha.4` and `latest=alpha.1`.
|
|
11
|
+
The source-only machine-readable candidate derivation and vendored-scanner
|
|
12
|
+
binding is [`release/privacy-lineage.v1.json`](release/privacy-lineage.v1.json).
|
|
13
|
+
It is excluded from npm and does not claim publication completion.
|
|
14
|
+
|
|
15
|
+
This release changes only version and release documentation; runtime behavior
|
|
16
|
+
and readable Markdown Recall are unchanged.
|
|
17
|
+
|
|
18
|
+
## 0.1.0-alpha.3 — 2026-08-02
|
|
19
|
+
|
|
20
|
+
- Recall now sends one human-readable Markdown user message with the exact request and local private JSON evidence references. Idle and busy steer behavior remains unchanged, and JSON files remain machine authority.
|
|
21
|
+
|
|
3
22
|
## 0.1.0-alpha.2 — 2026-08-02
|
|
4
23
|
|
|
5
24
|
First promoted OIDC prerelease candidate. This corrective release has no
|
package/README.md
CHANGED
|
@@ -10,12 +10,22 @@ and visual language can change. It is not a Task, Project, runtime, priority,
|
|
|
10
10
|
attention, or delivery authority. Native Pi Session JSONL remains the evidence
|
|
11
11
|
authority.
|
|
12
12
|
|
|
13
|
+
Alpha.4 is a new artifact with a sanitized current source lineage. Product
|
|
14
|
+
capability is unchanged from the alpha.3 cutoff tree `db7d388`. The old tag
|
|
15
|
+
graphs remain public and are not privacy-clean. All existing alpha.1, alpha.2,
|
|
16
|
+
and alpha.3 tags, releases, and npm artifacts remain immutable (there is no
|
|
17
|
+
alpha.1 GitHub Release). Alpha.3 remains the original readable-Recall release.
|
|
18
|
+
Intended npm routing after publication is `next=alpha.4` and `latest=alpha.1`.
|
|
19
|
+
The source-only machine-readable candidate derivation and vendored-scanner
|
|
20
|
+
binding is [`release/privacy-lineage.v1.json`](release/privacy-lineage.v1.json).
|
|
21
|
+
It is excluded from npm and does not claim publication completion.
|
|
22
|
+
|
|
13
23
|
## Install
|
|
14
24
|
|
|
15
25
|
Use Node 22 or later and Pi 0.83 or later. Install it in Pi from npm:
|
|
16
26
|
|
|
17
27
|
```sh
|
|
18
|
-
pi install npm:@hypercarrier/rarebit@0.1.0-alpha.
|
|
28
|
+
pi install npm:@hypercarrier/rarebit@0.1.0-alpha.4
|
|
19
29
|
pi list
|
|
20
30
|
```
|
|
21
31
|
|
|
@@ -34,7 +44,7 @@ To use the CLI in a normal Node project, install it and invoke the local bin
|
|
|
34
44
|
with npm:
|
|
35
45
|
|
|
36
46
|
```sh
|
|
37
|
-
npm install @hypercarrier/rarebit@0.1.0-alpha.
|
|
47
|
+
npm install @hypercarrier/rarebit@0.1.0-alpha.4
|
|
38
48
|
npm exec -- rarebit --help
|
|
39
49
|
```
|
|
40
50
|
|
|
@@ -101,8 +111,8 @@ remain until you remove them. Session JSONL retention is controlled by Pi.
|
|
|
101
111
|
|
|
102
112
|
Run `/rarebit recall <prompt>` in a Pi Session. Rarebit writes the exact active
|
|
103
113
|
branch selection to two private files: a conversation view and detailed
|
|
104
|
-
lineage evidence. It then sends one atomic
|
|
105
|
-
your exact request and absolute pointers to both files.
|
|
114
|
+
lineage evidence. It then sends one atomic, human-readable Markdown user
|
|
115
|
+
message containing your exact request and absolute pointers to both files.
|
|
106
116
|
|
|
107
117
|
When Pi is idle, that message starts one turn. When Pi is busy, it queues one
|
|
108
118
|
steering message. Recall does not send a second follow-up, add a custom Session
|
|
@@ -110,7 +120,7 @@ entry, or create a durable Recall receipt.
|
|
|
110
120
|
|
|
111
121
|
The temporary directory is mode 0700 and its JSON files are mode 0600. The
|
|
112
122
|
files remain after the request so Pi can read them; delete the directory after
|
|
113
|
-
the turn no longer needs it. Both files and the persisted Pi
|
|
123
|
+
the turn no longer needs it. Both files and the persisted Pi message are
|
|
114
124
|
sensitive. Do not put their paths or content in a ticket.
|
|
115
125
|
|
|
116
126
|
## Update, uninstall, and rollback
|
|
@@ -118,11 +128,15 @@ sensitive. Do not put their paths or content in a ticket.
|
|
|
118
128
|
Pin alpha versions for reproducible installs:
|
|
119
129
|
|
|
120
130
|
```sh
|
|
121
|
-
pi install npm:@hypercarrier/rarebit@0.1.0-alpha.
|
|
122
|
-
pi update npm:@hypercarrier/rarebit
|
|
131
|
+
pi install npm:@hypercarrier/rarebit@0.1.0-alpha.4
|
|
123
132
|
pi remove npm:@hypercarrier/rarebit
|
|
124
133
|
```
|
|
125
134
|
|
|
135
|
+
The exact-version install also moves an existing Rarebit npm installation to
|
|
136
|
+
that pin. Pi skips versioned npm sources during package updates. Do not use an
|
|
137
|
+
unpinned source for alpha.4 recovery: unpinned npm resolution follows the
|
|
138
|
+
retained `latest`, which remains alpha.1.
|
|
139
|
+
|
|
126
140
|
To roll back, reinstall a known version with `pi install npm:@hypercarrier/rarebit@<version>`.
|
|
127
141
|
Removal stops package loading. It does not alter Pi Session JSONL or delete
|
|
128
142
|
Rarebit sidecars, job leases, or Recall temp files. Remove retained local data
|
package/RUNBOOK.md
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
# Rarebit runbook
|
|
2
2
|
|
|
3
|
+
This runbook applies to `0.1.0-alpha.4`. Operational behavior, including
|
|
4
|
+
readable Markdown Recall, is unchanged from alpha.3; see [CHANGELOG.md](CHANGELOG.md)
|
|
5
|
+
for sanitized-lineage and release-routing details.
|
|
6
|
+
|
|
3
7
|
Set `rarebit.model` in global Pi settings or in a trusted Project's
|
|
4
8
|
`.pi/settings.json`. It must be `provider/model` or an object with `provider`
|
|
5
9
|
and `id`. Rarebit fails closed when it is missing or invalid. It does not use
|
|
6
10
|
Pi's interactive `defaultModel`.
|
|
7
11
|
|
|
8
|
-
|
|
9
|
-
`pi
|
|
10
|
-
|
|
12
|
+
Install or move this package to the alpha.4 pin with
|
|
13
|
+
`pi install npm:@hypercarrier/rarebit@0.1.0-alpha.4`. Pi treats versioned npm
|
|
14
|
+
sources as pinned, so package update commands skip it. Do not use an unpinned
|
|
15
|
+
source for alpha.4 recovery: unpinned npm resolution follows the retained
|
|
16
|
+
`latest`, which remains alpha.1. Run `pi remove npm:@hypercarrier/rarebit` to
|
|
17
|
+
stop loading it. These commands do not modify native Sessions or remove Rarebit
|
|
18
|
+
retained data.
|
|
11
19
|
|
|
12
20
|
Inspect derived state without treating it as a transcript:
|
|
13
21
|
|
package/package.json
CHANGED
|
@@ -15,6 +15,55 @@ async function waitFor(predicate, label, timeout = 2_000) {
|
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
function expectedRecallMarkdown(conversationPath, detailedPath, request) {
|
|
19
|
+
return `# Rarebit Recall
|
|
20
|
+
|
|
21
|
+
*Use these local private evidence files to recover historical context for this turn.*
|
|
22
|
+
|
|
23
|
+
## How to use this bundle
|
|
24
|
+
|
|
25
|
+
1. Treat **Current request** below as the exact current user request.
|
|
26
|
+
2. Read **Conversation** first for meaning.
|
|
27
|
+
3. Use **Detailed evidence** only for source, Session, branch, or lineage facts.
|
|
28
|
+
4. Answer the current request using this evidence.
|
|
29
|
+
|
|
30
|
+
## Local private evidence files
|
|
31
|
+
|
|
32
|
+
**Conversation** — \`rarebit_conversation/v1\`
|
|
33
|
+
|
|
34
|
+
\`\`\`text
|
|
35
|
+
${conversationPath}
|
|
36
|
+
\`\`\`
|
|
37
|
+
|
|
38
|
+
**Detailed evidence** — \`rarebit_message_recall/v1\`
|
|
39
|
+
|
|
40
|
+
\`\`\`text
|
|
41
|
+
${detailedPath}
|
|
42
|
+
\`\`\`
|
|
43
|
+
|
|
44
|
+
## Current request
|
|
45
|
+
|
|
46
|
+
\`\`\`text
|
|
47
|
+
${request}
|
|
48
|
+
\`\`\``;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function conversationPathFrom(message) {
|
|
52
|
+
const match = message.match(
|
|
53
|
+
/\*\*Conversation\*\* — `rarebit_conversation\/v1`\n\n```text\n([^\n]*rarebit-conversation\.json)\n```/,
|
|
54
|
+
);
|
|
55
|
+
assert.ok(match, "Recall Markdown must contain the fenced conversation path");
|
|
56
|
+
return match[1];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function detailedPathFrom(message) {
|
|
60
|
+
const match = message.match(
|
|
61
|
+
/\*\*Detailed evidence\*\* — `rarebit_message_recall\/v1`\n\n```text\n([^\n]*rarebit-evidence\.json)\n```/,
|
|
62
|
+
);
|
|
63
|
+
assert.ok(match, "Recall Markdown must contain the fenced detailed path");
|
|
64
|
+
return match[1];
|
|
65
|
+
}
|
|
66
|
+
|
|
18
67
|
const root =
|
|
19
68
|
process.env.PI_CODING_AGENT_ROOT ??
|
|
20
69
|
resolve(
|
|
@@ -107,11 +156,20 @@ try {
|
|
|
107
156
|
.filter((entry) => JSON.stringify(entry).includes("exact idle request"));
|
|
108
157
|
assert.equal(userEntries.length, 1);
|
|
109
158
|
const message = userEntries[0].message ?? userEntries[0];
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
159
|
+
const idleMessageText = message.content[0].text;
|
|
160
|
+
const idleConversationPath = conversationPathFrom(idleMessageText);
|
|
161
|
+
const idleDetailedPath = detailedPathFrom(idleMessageText);
|
|
162
|
+
assert.equal(
|
|
163
|
+
idleMessageText,
|
|
164
|
+
expectedRecallMarkdown(
|
|
165
|
+
idleConversationPath,
|
|
166
|
+
idleDetailedPath,
|
|
167
|
+
"exact idle request",
|
|
168
|
+
),
|
|
169
|
+
);
|
|
170
|
+
recallDirectories.push(dirname(idleConversationPath));
|
|
171
|
+
await access(idleConversationPath);
|
|
172
|
+
await access(idleDetailedPath);
|
|
115
173
|
assert.equal(
|
|
116
174
|
manager
|
|
117
175
|
.getBranch()
|
|
@@ -141,29 +199,29 @@ try {
|
|
|
141
199
|
assert.equal(queued.steering.length, 1);
|
|
142
200
|
assert.equal(queued.followUp?.length ?? 0, 0);
|
|
143
201
|
const queuedMessage = queued.steering[0];
|
|
144
|
-
const
|
|
202
|
+
const busyMessageText =
|
|
145
203
|
typeof queuedMessage === "string"
|
|
146
204
|
? queuedMessage
|
|
147
|
-
: (queuedMessage.content?.[0]?.text ?? queuedMessage.content)
|
|
205
|
+
: (queuedMessage.content?.[0]?.text ?? queuedMessage.content);
|
|
206
|
+
const busyConversationPath = conversationPathFrom(busyMessageText);
|
|
207
|
+
const busyDetailedPath = detailedPathFrom(busyMessageText);
|
|
208
|
+
assert.equal(
|
|
209
|
+
busyMessageText,
|
|
210
|
+
expectedRecallMarkdown(
|
|
211
|
+
busyConversationPath,
|
|
212
|
+
busyDetailedPath,
|
|
213
|
+
"exact busy request",
|
|
214
|
+
),
|
|
148
215
|
);
|
|
149
|
-
recallDirectories.push(dirname(
|
|
150
|
-
await access(
|
|
151
|
-
await access(
|
|
152
|
-
assert.equal(busyEnvelope.request.text, "exact busy request");
|
|
216
|
+
recallDirectories.push(dirname(busyConversationPath));
|
|
217
|
+
await access(busyConversationPath);
|
|
218
|
+
await access(busyDetailedPath);
|
|
153
219
|
releaseBusy();
|
|
154
220
|
await busyRun;
|
|
155
221
|
await session.agent.waitForIdle();
|
|
156
222
|
assert.equal(provider.state.callCount, 4);
|
|
157
223
|
const recallCount = (texts) =>
|
|
158
|
-
texts
|
|
159
|
-
.map((text) => {
|
|
160
|
-
try {
|
|
161
|
-
return JSON.parse(text);
|
|
162
|
-
} catch {
|
|
163
|
-
return null;
|
|
164
|
-
}
|
|
165
|
-
})
|
|
166
|
-
.filter((value) => value?.request?.text === "exact busy request").length;
|
|
224
|
+
texts.filter((text) => text === busyMessageText).length;
|
|
167
225
|
const fourthUserTexts = (fourthContext?.messages ?? [])
|
|
168
226
|
.filter((message) => message.role === "user")
|
|
169
227
|
.map((message) => message.content?.[0]?.text ?? "");
|
|
@@ -184,7 +242,7 @@ try {
|
|
|
184
242
|
false,
|
|
185
243
|
);
|
|
186
244
|
console.log(
|
|
187
|
-
`Pi ${packageJson.version} Recall E2E passed: idle and busy steer
|
|
245
|
+
`Pi ${packageJson.version} Recall E2E passed: idle and busy steer Markdown messages; no custom receipt.`,
|
|
188
246
|
);
|
|
189
247
|
} finally {
|
|
190
248
|
releaseBusy?.();
|
|
@@ -22,7 +22,7 @@ const forbiddenPath = /(^|\/)(\.git|node_modules|\.github|hc-rarebit\.mjs)(\/|$)
|
|
|
22
22
|
const allowedBare = new Set(["@earendil-works/pi-ai"]);
|
|
23
23
|
const packageFor = (specifier) => specifier.startsWith("@") ? specifier.split("/").slice(0, 2).join("/") : specifier.split("/")[0];
|
|
24
24
|
const packageJson = JSON.parse(await readFile(join(root, "package.json"), "utf8"));
|
|
25
|
-
assert.equal(packageJson.version, "0.1.0-alpha.
|
|
25
|
+
assert.equal(packageJson.version, "0.1.0-alpha.4", "release version mismatch");
|
|
26
26
|
|
|
27
27
|
const packed = JSON.parse(npm(["pack", "--json", "--ignore-scripts"]))[0];
|
|
28
28
|
const tarball = join(root, packed.filename);
|
package/src/extension.mjs
CHANGED
|
@@ -381,22 +381,42 @@ export default function registerPiRarebit(pi, config = {}) {
|
|
|
381
381
|
);
|
|
382
382
|
};
|
|
383
383
|
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
384
|
+
const fence = (value) => {
|
|
385
|
+
const safeLength = (marker) => {
|
|
386
|
+
const runs = value.match(new RegExp(`${marker}+`, "g")) ?? [];
|
|
387
|
+
return Math.max(3, ...runs.map((run) => run.length + 1));
|
|
388
|
+
};
|
|
389
|
+
const backtickLength = safeLength("`");
|
|
390
|
+
const tildeLength = safeLength("~");
|
|
391
|
+
const marker = backtickLength <= tildeLength ? "`" : "~";
|
|
392
|
+
const length = marker === "`" ? backtickLength : tildeLength;
|
|
393
|
+
const boundary = marker.repeat(length);
|
|
394
|
+
return `${boundary}text\n${value}\n${boundary}`;
|
|
395
|
+
};
|
|
396
|
+
const recallMessage = (recall, requestText) => `# Rarebit Recall
|
|
397
|
+
|
|
398
|
+
*Use these local private evidence files to recover historical context for this turn.*
|
|
399
|
+
|
|
400
|
+
## How to use this bundle
|
|
401
|
+
|
|
402
|
+
1. Treat **Current request** below as the exact current user request.
|
|
403
|
+
2. Read **Conversation** first for meaning.
|
|
404
|
+
3. Use **Detailed evidence** only for source, Session, branch, or lineage facts.
|
|
405
|
+
4. Answer the current request using this evidence.
|
|
406
|
+
|
|
407
|
+
## Local private evidence files
|
|
408
|
+
|
|
409
|
+
**Conversation** — \`rarebit_conversation/v${RAREBIT_CONVERSATION_SCHEMA_VERSION}\`
|
|
410
|
+
|
|
411
|
+
${fence(recall.conversationPath)}
|
|
412
|
+
|
|
413
|
+
**Detailed evidence** — \`rarebit_message_recall/v${RAREBIT_RECALL_SCHEMA_VERSION}\`
|
|
414
|
+
|
|
415
|
+
${fence(recall.detailedPath)}
|
|
416
|
+
|
|
417
|
+
## Current request
|
|
418
|
+
|
|
419
|
+
${fence(requestText)}`;
|
|
400
420
|
|
|
401
421
|
pi.registerCommand?.("rarebit", {
|
|
402
422
|
description: rarebitCommandDescription(),
|
|
@@ -432,7 +452,7 @@ export default function registerPiRarebit(pi, config = {}) {
|
|
|
432
452
|
"the active Pi Session or branch changed during materialization",
|
|
433
453
|
);
|
|
434
454
|
}
|
|
435
|
-
pi.sendUserMessage(
|
|
455
|
+
pi.sendUserMessage(recallMessage(recall, prompt), {
|
|
436
456
|
deliverAs: "steer",
|
|
437
457
|
});
|
|
438
458
|
notify(ctx, "Rarebit recall bundle prepared; turn requested", "info");
|
|
@@ -303,36 +303,120 @@ function extensionHarness({ recallMaterializer, sendUserMessage } = {}) {
|
|
|
303
303
|
return { commands, handlers, pi, sent, tools };
|
|
304
304
|
}
|
|
305
305
|
|
|
306
|
-
test("recall sends one
|
|
306
|
+
test("recall sends one exact fenced Markdown steer message in idle and busy contexts", async () => {
|
|
307
307
|
const sessionFile = "/tmp/session-recall.jsonl";
|
|
308
308
|
const recall = {
|
|
309
|
-
conversationPath:
|
|
310
|
-
|
|
309
|
+
conversationPath:
|
|
310
|
+
"/tmp/Rare bit [会话] (one) **/tick`` and ~~\nnext/rarebit-conversation.json",
|
|
311
|
+
detailedPath:
|
|
312
|
+
"/tmp/Detailed [证据] (two) **/tick```` and ~~~\n~~~line-start/rarebit-evidence.json",
|
|
311
313
|
sessionId: "session-recall",
|
|
312
314
|
sessionFile,
|
|
313
315
|
branchLeafId: "stop",
|
|
314
316
|
};
|
|
317
|
+
const prompt =
|
|
318
|
+
"\n# Heading-looking request\n- list-looking item\n```text\ninside backticks\n```\n~~~~\ninside tildes\n~~~~\nTrailing request\n";
|
|
319
|
+
const expectedMessage = `# Rarebit Recall
|
|
320
|
+
|
|
321
|
+
*Use these local private evidence files to recover historical context for this turn.*
|
|
322
|
+
|
|
323
|
+
## How to use this bundle
|
|
324
|
+
|
|
325
|
+
1. Treat **Current request** below as the exact current user request.
|
|
326
|
+
2. Read **Conversation** first for meaning.
|
|
327
|
+
3. Use **Detailed evidence** only for source, Session, branch, or lineage facts.
|
|
328
|
+
4. Answer the current request using this evidence.
|
|
329
|
+
|
|
330
|
+
## Local private evidence files
|
|
331
|
+
|
|
332
|
+
**Conversation** — \`rarebit_conversation/v1\`
|
|
333
|
+
|
|
334
|
+
\`\`\`text
|
|
335
|
+
${recall.conversationPath}
|
|
336
|
+
\`\`\`
|
|
337
|
+
|
|
338
|
+
**Detailed evidence** — \`rarebit_message_recall/v1\`
|
|
339
|
+
|
|
340
|
+
~~~~text
|
|
341
|
+
${recall.detailedPath}
|
|
342
|
+
~~~~
|
|
343
|
+
|
|
344
|
+
## Current request
|
|
345
|
+
|
|
346
|
+
\`\`\`\`text
|
|
347
|
+
${prompt}
|
|
348
|
+
\`\`\`\``;
|
|
349
|
+
|
|
315
350
|
for (const busy of [false, true]) {
|
|
316
351
|
const notices = [];
|
|
317
352
|
const harness = extensionHarness({ recallMaterializer: async () => recall });
|
|
318
353
|
const ctx = contextFor(branch(), sessionFile, notices);
|
|
319
354
|
ctx.isIdle = () => !busy;
|
|
320
|
-
const prompt = "Use \"this\" evidence\\path\nwith spacing. ";
|
|
321
355
|
await harness.commands.get("rarebit").handler(`recall ${prompt}`, ctx);
|
|
322
356
|
assert.equal(harness.sent.length, 1);
|
|
357
|
+
assert.equal(harness.sent[0].args[0], expectedMessage);
|
|
323
358
|
assert.deepEqual(harness.sent[0].args[1], { deliverAs: "steer" });
|
|
324
|
-
|
|
325
|
-
assert.equal(
|
|
326
|
-
assert.
|
|
327
|
-
assert.equal(envelope.files.conversation.path, recall.conversationPath);
|
|
328
|
-
assert.equal(envelope.files.conversation.schema, "rarebit_conversation/v1");
|
|
329
|
-
assert.equal(envelope.files.detailedEvidence.path, recall.detailedPath);
|
|
330
|
-
assert.equal(envelope.files.detailedEvidence.schema, "rarebit_message_recall/v1");
|
|
331
|
-
assert.match(envelope.instructions, /conversation file first/i);
|
|
359
|
+
assert.equal(expectedMessage.includes(recall.conversationPath), true);
|
|
360
|
+
assert.equal(expectedMessage.includes(recall.detailedPath), true);
|
|
361
|
+
assert.equal(expectedMessage.includes(prompt), true);
|
|
332
362
|
assert.match(notices.at(-1).text, /bundle prepared; turn requested/i);
|
|
333
363
|
}
|
|
334
364
|
});
|
|
335
365
|
|
|
366
|
+
test("long delimiter runs ending at value boundaries use the shortest safe fences", async () => {
|
|
367
|
+
const pathBackticks = "`".repeat(80);
|
|
368
|
+
const pathTildes = "~".repeat(47);
|
|
369
|
+
const conversationPath = `/tmp/long-runs/${pathBackticks}/${pathTildes}`;
|
|
370
|
+
const requestTildes = "~".repeat(96);
|
|
371
|
+
const requestBackticks = "`".repeat(63);
|
|
372
|
+
const request = `Boundary ${requestTildes} ${requestBackticks}`;
|
|
373
|
+
const harness = extensionHarness({
|
|
374
|
+
recallMaterializer: async () => ({
|
|
375
|
+
conversationPath,
|
|
376
|
+
detailedPath: "/tmp/rarebit-evidence.json",
|
|
377
|
+
sessionId: "session-recall",
|
|
378
|
+
sessionFile: "/tmp/session-recall.jsonl",
|
|
379
|
+
branchLeafId: "stop",
|
|
380
|
+
}),
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
await harness.commands
|
|
384
|
+
.get("rarebit")
|
|
385
|
+
.handler(
|
|
386
|
+
`recall ${request}`,
|
|
387
|
+
contextFor(branch(), "/tmp/session-recall.jsonl"),
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
assert.equal(harness.sent.length, 1);
|
|
391
|
+
const message = harness.sent[0].args[0];
|
|
392
|
+
const conversationBlock = message.match(
|
|
393
|
+
/\*\*Conversation\*\* — `rarebit_conversation\/v1`\n\n([`~]+)text\n([^\n]*)\n\1\n\n\*\*Detailed evidence\*\*/,
|
|
394
|
+
);
|
|
395
|
+
assert.ok(conversationBlock);
|
|
396
|
+
assert.equal(conversationBlock[2], conversationPath);
|
|
397
|
+
assert.equal(conversationBlock[2].endsWith(pathTildes), true);
|
|
398
|
+
const conversationFence = conversationBlock[1];
|
|
399
|
+
assert.equal(conversationFence[0], "~");
|
|
400
|
+
assert.ok(conversationFence.length >= 3);
|
|
401
|
+
assert.equal(conversationFence.length, pathTildes.length + 1);
|
|
402
|
+
assert.ok(conversationFence.length > pathTildes.length);
|
|
403
|
+
assert.ok(conversationFence.length < pathBackticks.length + 1);
|
|
404
|
+
|
|
405
|
+
const requestBlock = message.match(
|
|
406
|
+
/## Current request\n\n([`~]+)text\n([\s\S]*)\n\1$/,
|
|
407
|
+
);
|
|
408
|
+
assert.ok(requestBlock);
|
|
409
|
+
assert.equal(requestBlock[2], request);
|
|
410
|
+
assert.equal(requestBlock[2].endsWith(requestBackticks), true);
|
|
411
|
+
const requestFence = requestBlock[1];
|
|
412
|
+
assert.equal(requestFence[0], "`");
|
|
413
|
+
assert.ok(requestFence.length >= 3);
|
|
414
|
+
assert.equal(requestFence.length, requestBackticks.length + 1);
|
|
415
|
+
assert.ok(requestFence.length > requestBackticks.length);
|
|
416
|
+
assert.ok(requestFence.length < requestTildes.length + 1);
|
|
417
|
+
assert.equal(message.endsWith(`${request}\n${requestFence}`), true);
|
|
418
|
+
});
|
|
419
|
+
|
|
336
420
|
test("single-send failure discards the prepared Recall bundle", async () => {
|
|
337
421
|
const notices = [];
|
|
338
422
|
let discarded = 0;
|