@hypercarrier/rarebit 0.1.0-alpha.5 → 0.2.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/CHANGELOG.md +24 -2
- package/README.md +96 -20
- package/RUNBOOK.md +32 -9
- package/bin/piq.mjs +14 -0
- package/docs/examples/rarebit.settings.json +19 -0
- package/package.json +29 -3
- package/scripts/e2e-fork-pi.mjs +824 -0
- package/scripts/e2e-recap-pi.mjs +456 -0
- package/scripts/e2e-settings-pi.mjs +267 -0
- package/scripts/e2e-v020-pi.mjs +269 -0
- package/scripts/verify-package.mjs +20 -12
- package/src/extension.mjs +280 -21
- package/src/index.mjs +1 -0
- package/src/lifecycle.mjs +32 -7
- package/src/pi-session.mjs +237 -0
- package/src/piq-cli.mjs +96 -0
- package/src/piq.mjs +265 -0
- package/src/rarebit-activity.mjs +8 -2
- package/src/rarebit-cli.mjs +103 -5
- package/src/rarebit-command.mjs +29 -1
- package/src/rarebit-core.mjs +87 -9
- package/src/rarebit-fork-lineage.mjs +69 -0
- package/src/rarebit-fork.mjs +241 -0
- package/src/rarebit-recap.mjs +422 -0
- package/src/rarebit-service.mjs +60 -7
- package/src/rarebit-session.mjs +12 -160
- package/src/rarebit-settings-ui.mjs +479 -0
- package/src/rarebit-settings.mjs +90 -7
- package/src/types.d.ts +91 -3
- package/test/lifecycle-contract-independent.test.mjs +2 -0
- package/test/package-artifact-identity.test.mjs +54 -0
- package/test/piq.test.mjs +212 -0
- package/test/rarebit-command.test.mjs +11 -4
- package/test/rarebit-fork.test.mjs +124 -0
- package/test/rarebit-recap.test.mjs +513 -0
- package/test/rarebit-settings-ui.test.mjs +388 -0
- package/test/rarebit-v020.test.mjs +294 -0
- package/test/summary-attention-independent.test.mjs +1 -1
package/src/extension.mjs
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { readFile } from "node:fs/promises";
|
|
1
|
+
import { readFile, rm } from "node:fs/promises";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
|
-
import { complete } from "@earendil-works/pi-ai/compat";
|
|
5
|
-
|
|
6
4
|
export * from "./rarebit-core.mjs";
|
|
7
5
|
export * from "./rarebit-model.mjs";
|
|
8
6
|
export * from "./automatic-summary-policy.mjs";
|
|
@@ -15,6 +13,7 @@ import {
|
|
|
15
13
|
} from "./rarebit-service.mjs";
|
|
16
14
|
import {
|
|
17
15
|
DEFAULT_RAREBIT_SUMMARY_POLICY,
|
|
16
|
+
normalizeRarebitSummaryPrompt,
|
|
18
17
|
selectRarebits,
|
|
19
18
|
sha256,
|
|
20
19
|
} from "./rarebit-core.mjs";
|
|
@@ -25,7 +24,14 @@ import {
|
|
|
25
24
|
getRarebitArgumentCompletions,
|
|
26
25
|
parseRarebitCommand,
|
|
27
26
|
rarebitCommandDescription,
|
|
27
|
+
rarebitCommandUsage,
|
|
28
28
|
} from "./rarebit-command.mjs";
|
|
29
|
+
import {
|
|
30
|
+
buildRarebitForkPlan,
|
|
31
|
+
createRarebitForkEntries,
|
|
32
|
+
sessionFilenameFor,
|
|
33
|
+
writeRarebitForkFile,
|
|
34
|
+
} from "./rarebit-fork.mjs";
|
|
29
35
|
import {
|
|
30
36
|
RAREBIT_CONVERSATION_SCHEMA_VERSION,
|
|
31
37
|
RAREBIT_RECALL_SCHEMA_VERSION,
|
|
@@ -35,6 +41,12 @@ import {
|
|
|
35
41
|
createDetachedMaterializer,
|
|
36
42
|
registerRarebitLifecycle,
|
|
37
43
|
} from "./lifecycle.mjs";
|
|
44
|
+
import { createRarebitRecapController } from "./rarebit-recap.mjs";
|
|
45
|
+
import {
|
|
46
|
+
openRarebitPalette,
|
|
47
|
+
openRarebitSettings,
|
|
48
|
+
rarebitPaletteCommand,
|
|
49
|
+
} from "./rarebit-settings-ui.mjs";
|
|
38
50
|
import {
|
|
39
51
|
automaticSummaryInhibitionIdentity,
|
|
40
52
|
queryAutomaticSummaryPolicy,
|
|
@@ -49,6 +61,17 @@ async function readSettings(path) {
|
|
|
49
61
|
}
|
|
50
62
|
}
|
|
51
63
|
|
|
64
|
+
function resolveReserveTokens(globalSettings, projectSettings, model) {
|
|
65
|
+
const globalCompaction = globalSettings?.compaction ?? {};
|
|
66
|
+
const projectCompaction = projectSettings?.compaction ?? {};
|
|
67
|
+
const compaction = { ...globalCompaction, ...projectCompaction };
|
|
68
|
+
const modelOverrides = { ...(globalCompaction.modelOverrides ?? {}), ...(projectCompaction.modelOverrides ?? {}) };
|
|
69
|
+
const key = model?.provider && model?.id ? `${model.provider}/${model.id}` : null;
|
|
70
|
+
const override = key ? modelOverrides[key] : undefined;
|
|
71
|
+
const value = override?.reserveTokens ?? (Object.hasOwn(projectCompaction, "reserveTokens") ? projectCompaction.reserveTokens : compaction.reserveTokens) ?? 16_384;
|
|
72
|
+
return Number.isSafeInteger(value) && value >= 0 ? value : 16_384;
|
|
73
|
+
}
|
|
74
|
+
|
|
52
75
|
function configuredAgentDir(env = process.env) {
|
|
53
76
|
return env.PI_CODING_AGENT_DIR ?? join(homedir(), ".pi", "agent");
|
|
54
77
|
}
|
|
@@ -56,6 +79,7 @@ function configuredAgentDir(env = process.env) {
|
|
|
56
79
|
export async function readConfiguredRarebitSettings({
|
|
57
80
|
cwd,
|
|
58
81
|
projectTrusted,
|
|
82
|
+
model,
|
|
59
83
|
agentDir = configuredAgentDir(),
|
|
60
84
|
} = {}) {
|
|
61
85
|
const global = await readSettings(join(agentDir, "settings.json"));
|
|
@@ -88,6 +112,11 @@ export async function readConfiguredRarebitSettings({
|
|
|
88
112
|
},
|
|
89
113
|
summaryPolicy: resolved.summaryPolicy,
|
|
90
114
|
autoTitle: resolved.autoTitle,
|
|
115
|
+
maxInputTokens: resolved.maxInputTokens,
|
|
116
|
+
summaryPrompt: resolved.summaryPrompt,
|
|
117
|
+
diagnostics: resolved.diagnostics,
|
|
118
|
+
recap: resolved.recap,
|
|
119
|
+
reserveTokens: resolveReserveTokens(global.value, project.value, model),
|
|
91
120
|
};
|
|
92
121
|
}
|
|
93
122
|
|
|
@@ -104,10 +133,17 @@ export default function registerPiRarebit(pi, config = {}) {
|
|
|
104
133
|
});
|
|
105
134
|
const activityReporter =
|
|
106
135
|
explicit.activityReporter ?? createHerdrActivityReporter();
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
136
|
+
const recapController = createRarebitRecapController({
|
|
137
|
+
delayMs: explicit.recap?.delayMs,
|
|
138
|
+
timezone: explicit.recap?.timezone,
|
|
139
|
+
});
|
|
140
|
+
const projectActivity = (ctx) => {
|
|
141
|
+
const entries = ctx?.sessionManager?.getBranch?.() ?? [];
|
|
142
|
+
return projectRarebitSessionActivity({
|
|
143
|
+
...selectRarebits(entries),
|
|
144
|
+
entries,
|
|
145
|
+
});
|
|
146
|
+
};
|
|
111
147
|
|
|
112
148
|
const notify = (ctx, text, level = "info") => {
|
|
113
149
|
if (!ctx?.hasUI || typeof ctx?.ui?.notify !== "function") return;
|
|
@@ -135,10 +171,11 @@ export default function registerPiRarebit(pi, config = {}) {
|
|
|
135
171
|
? `${name} tokens: ${value}`
|
|
136
172
|
: `${name} tokens: unavailable`;
|
|
137
173
|
|
|
138
|
-
const notifyOutcome = (ctx, result, triggered) => {
|
|
139
|
-
if (
|
|
174
|
+
const notifyOutcome = (ctx, result, triggered, diagnostics = {}) => {
|
|
175
|
+
if (result?.duplicate || result?.inFlight) return;
|
|
140
176
|
switch (result?.record?.status) {
|
|
141
177
|
case "ok":
|
|
178
|
+
if (!triggered || diagnostics.summaryUpdated !== true) return;
|
|
142
179
|
notify(
|
|
143
180
|
ctx,
|
|
144
181
|
`Rarebit Summary updated (${tokenLabel("input", result.record.synthesis?.usage?.inputTokens)} · ${tokenLabel("output", result.record.synthesis?.usage?.outputTokens)} · model ${resultModelLabel(result)})`,
|
|
@@ -179,6 +216,7 @@ export default function registerPiRarebit(pi, config = {}) {
|
|
|
179
216
|
injected = await settingsLoader({
|
|
180
217
|
cwd: ctx?.cwd,
|
|
181
218
|
projectTrusted: ctx?.isProjectTrusted?.() === true,
|
|
219
|
+
model: ctx?.model,
|
|
182
220
|
});
|
|
183
221
|
} catch (error) {
|
|
184
222
|
injected = {
|
|
@@ -200,15 +238,40 @@ export default function registerPiRarebit(pi, config = {}) {
|
|
|
200
238
|
...injected,
|
|
201
239
|
...explicit,
|
|
202
240
|
summaryPolicy,
|
|
203
|
-
|
|
241
|
+
maxInputTokens: explicit.maxInputTokens ?? injected.maxInputTokens,
|
|
242
|
+
summaryPrompt: explicit.summaryPrompt ?? injected.summaryPrompt,
|
|
243
|
+
diagnostics: {
|
|
244
|
+
...(injected.diagnostics ?? {}),
|
|
245
|
+
...(explicit.diagnostics ?? {}),
|
|
246
|
+
},
|
|
247
|
+
recap: {
|
|
248
|
+
...(injected.recap ?? {}),
|
|
249
|
+
...(explicit.recap ?? {}),
|
|
250
|
+
},
|
|
251
|
+
piAi: explicit.piAi ?? {
|
|
252
|
+
complete: async (...args) => (await import("@earendil-works/pi-ai/compat")).complete(...args),
|
|
253
|
+
},
|
|
204
254
|
};
|
|
205
255
|
};
|
|
206
256
|
|
|
207
257
|
const materialize = async (ctx, { force = false } = {}) => {
|
|
258
|
+
const recapToken = recapController.captureMaterialization(ctx);
|
|
208
259
|
let synthesisTriggered = false;
|
|
209
260
|
const effective = await loadEffective(ctx);
|
|
261
|
+
let summaryPrompt;
|
|
262
|
+
try {
|
|
263
|
+
summaryPrompt = normalizeRarebitSummaryPrompt(effective.summaryPrompt).guidance;
|
|
264
|
+
} catch (error) {
|
|
265
|
+
notify(
|
|
266
|
+
ctx,
|
|
267
|
+
`Invalid rarebit.summary_prompt: ${error?.message ?? error}`,
|
|
268
|
+
"error",
|
|
269
|
+
);
|
|
270
|
+
return { record: { status: "invalid_configuration" } };
|
|
271
|
+
}
|
|
210
272
|
const result = await processRarebitSummary(ctx, {
|
|
211
273
|
...effective,
|
|
274
|
+
summaryPrompt,
|
|
212
275
|
forceSynthesis: force,
|
|
213
276
|
lifecycleBoundary: force ? "manual" : ctx?.lifecycleBoundary,
|
|
214
277
|
queryAutomaticSummaryPolicy:
|
|
@@ -216,14 +279,26 @@ export default function registerPiRarebit(pi, config = {}) {
|
|
|
216
279
|
onSynthesisTriggered: (detail) => {
|
|
217
280
|
synthesisTriggered = true;
|
|
218
281
|
const count = detail.rarebitCount ?? detail.rarebitCount;
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
282
|
+
if (effective.diagnostics?.summaryTriggered === true)
|
|
283
|
+
notify(
|
|
284
|
+
ctx,
|
|
285
|
+
`Rarebit Summary triggered (${count} Rarebits · input tokens: ~${detail.estimatedInputTokens} estimated (chars/4) · model ${modelLabel(detail.model)})`,
|
|
286
|
+
"info",
|
|
287
|
+
);
|
|
224
288
|
},
|
|
225
289
|
});
|
|
226
|
-
notifyOutcome(ctx, result, synthesisTriggered);
|
|
290
|
+
notifyOutcome(ctx, result, synthesisTriggered, effective.diagnostics);
|
|
291
|
+
recapController.offer({
|
|
292
|
+
ctx,
|
|
293
|
+
result,
|
|
294
|
+
token: recapToken,
|
|
295
|
+
recapEnabled: effective.recap?.enabled !== false,
|
|
296
|
+
recapDelayMs: effective.recap?.delayMs,
|
|
297
|
+
recapTimezone: effective.recap?.timezone,
|
|
298
|
+
sessionRoot: effective.sessionRoot,
|
|
299
|
+
rarebitRoot: effective.rarebitRoot,
|
|
300
|
+
allowExternalSession: effective.allowExternalSession === true,
|
|
301
|
+
});
|
|
227
302
|
return result;
|
|
228
303
|
};
|
|
229
304
|
const schedule = createDetachedMaterializer(materialize, (_error, ctx) => {
|
|
@@ -331,20 +406,33 @@ export default function registerPiRarebit(pi, config = {}) {
|
|
|
331
406
|
);
|
|
332
407
|
|
|
333
408
|
registerRarebitLifecycle(pi, schedule, {
|
|
334
|
-
onSessionStart: (ctx) => {
|
|
409
|
+
onSessionStart: (ctx, generation) => {
|
|
410
|
+
recapController.invalidate(ctx, generation);
|
|
335
411
|
activeSession = identityFrom(ctx);
|
|
336
412
|
activityReporter.start(projectActivity(ctx));
|
|
337
413
|
},
|
|
338
|
-
|
|
414
|
+
onAgentStart: (ctx, generation) => {
|
|
415
|
+
recapController.invalidate(ctx, generation);
|
|
416
|
+
},
|
|
417
|
+
onTurnStart: (ctx, generation) => {
|
|
418
|
+
recapController.invalidate(ctx, generation);
|
|
419
|
+
},
|
|
420
|
+
onInputSent: (ctx, generation) => {
|
|
421
|
+
recapController.invalidate(ctx, generation);
|
|
422
|
+
},
|
|
423
|
+
onSessionTree: (ctx, _event, generation) => {
|
|
424
|
+
recapController.invalidate(ctx, generation);
|
|
339
425
|
activityReporter.update(projectActivity(ctx));
|
|
340
426
|
},
|
|
341
427
|
onSelectedUserPersisted: (ctx) => {
|
|
342
428
|
activityReporter.update(projectActivity(ctx));
|
|
343
429
|
},
|
|
344
430
|
onAgentSettled: (ctx) => {
|
|
431
|
+
recapController.updateContext(ctx);
|
|
345
432
|
activityReporter.update(projectActivity(ctx));
|
|
346
433
|
},
|
|
347
|
-
onSessionShutdown: () => {
|
|
434
|
+
onSessionShutdown: (ctx, generation) => {
|
|
435
|
+
recapController.invalidate(ctx, generation);
|
|
348
436
|
activeSession = undefined;
|
|
349
437
|
activityReporter.stop();
|
|
350
438
|
},
|
|
@@ -422,7 +510,35 @@ ${fence(requestText)}`;
|
|
|
422
510
|
description: rarebitCommandDescription(),
|
|
423
511
|
getArgumentCompletions: getRarebitArgumentCompletions,
|
|
424
512
|
handler: async (args, ctx) => {
|
|
425
|
-
|
|
513
|
+
let commandInput = String(args ?? "");
|
|
514
|
+
const trimmedCommandInput = commandInput.trim();
|
|
515
|
+
if (!trimmedCommandInput || trimmedCommandInput === "menu") {
|
|
516
|
+
const currentUiState = () => ({
|
|
517
|
+
...identityFrom(ctx),
|
|
518
|
+
branchLeafId: ctx?.sessionManager?.getBranch?.()?.at(-1)?.id ?? null,
|
|
519
|
+
cwd: ctx?.cwd ?? process.cwd(),
|
|
520
|
+
projectTrusted: ctx?.isProjectTrusted?.() === true,
|
|
521
|
+
});
|
|
522
|
+
const initial = currentUiState();
|
|
523
|
+
const selection = await openRarebitPalette(ctx, {
|
|
524
|
+
isCurrent: () => {
|
|
525
|
+
const current = currentUiState();
|
|
526
|
+
return current.sessionId === initial.sessionId &&
|
|
527
|
+
current.sessionFile === initial.sessionFile &&
|
|
528
|
+
current.branchLeafId === initial.branchLeafId &&
|
|
529
|
+
current.cwd === initial.cwd &&
|
|
530
|
+
current.projectTrusted === initial.projectTrusted;
|
|
531
|
+
},
|
|
532
|
+
});
|
|
533
|
+
commandInput = rarebitPaletteCommand(selection) ?? "";
|
|
534
|
+
if (!commandInput) return;
|
|
535
|
+
if (selection?.id === "recall") {
|
|
536
|
+
const prompt = await ctx.ui.input?.("Rarebit recall prompt", "");
|
|
537
|
+
if (prompt === undefined || !String(prompt).trim()) return;
|
|
538
|
+
commandInput = `recall ${prompt}`;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
const command = parseRarebitCommand(commandInput);
|
|
426
542
|
if (!command.ok) {
|
|
427
543
|
notify(
|
|
428
544
|
ctx,
|
|
@@ -434,6 +550,127 @@ ${fence(requestText)}`;
|
|
|
434
550
|
return;
|
|
435
551
|
}
|
|
436
552
|
const { subcommand, arguments: rest } = command;
|
|
553
|
+
if (subcommand === "help") {
|
|
554
|
+
notify(
|
|
555
|
+
ctx,
|
|
556
|
+
[
|
|
557
|
+
rarebitCommandUsage(),
|
|
558
|
+
...(commandInput.trim().split(/\s+/).length === 1
|
|
559
|
+
? ["Use /rarebit to open the interactive command palette."]
|
|
560
|
+
: []),
|
|
561
|
+
].join("\n"),
|
|
562
|
+
"info",
|
|
563
|
+
);
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
if (subcommand === "settings") {
|
|
567
|
+
const currentUiState = () => ({
|
|
568
|
+
...identityFrom(ctx),
|
|
569
|
+
branchLeafId: ctx?.sessionManager?.getBranch?.()?.at(-1)?.id ?? null,
|
|
570
|
+
cwd: ctx?.cwd ?? process.cwd(),
|
|
571
|
+
projectTrusted: ctx?.isProjectTrusted?.() === true,
|
|
572
|
+
});
|
|
573
|
+
const initial = currentUiState();
|
|
574
|
+
await openRarebitSettings(ctx, {
|
|
575
|
+
initialScope: rest[0] === "project" ? "project" : "global",
|
|
576
|
+
projectTrusted: initial.projectTrusted,
|
|
577
|
+
isCurrent: () => {
|
|
578
|
+
const current = currentUiState();
|
|
579
|
+
return current.sessionId === initial.sessionId &&
|
|
580
|
+
current.sessionFile === initial.sessionFile &&
|
|
581
|
+
current.branchLeafId === initial.branchLeafId &&
|
|
582
|
+
current.cwd === initial.cwd &&
|
|
583
|
+
current.projectTrusted === initial.projectTrusted;
|
|
584
|
+
},
|
|
585
|
+
onSaved: async ({ namespace, document }) => {
|
|
586
|
+
const previous = document?.namespace ?? {};
|
|
587
|
+
const changed = (path) => {
|
|
588
|
+
const read = (value) => path.reduce((current, key) => current?.[key], value);
|
|
589
|
+
return JSON.stringify(read(previous)) !== JSON.stringify(read(namespace));
|
|
590
|
+
};
|
|
591
|
+
if (changed(["max_rarebit_ratio"])) {
|
|
592
|
+
const next = { ...policyOverrides };
|
|
593
|
+
delete next.maxRarebitRatio;
|
|
594
|
+
policyOverrides = next;
|
|
595
|
+
}
|
|
596
|
+
if (changed(["min_total_length"])) {
|
|
597
|
+
const next = { ...policyOverrides };
|
|
598
|
+
delete next.minTotalLength;
|
|
599
|
+
policyOverrides = next;
|
|
600
|
+
}
|
|
601
|
+
if (changed(["auto_title"])) autoTitleOverride = undefined;
|
|
602
|
+
const effective = await loadEffective(ctx);
|
|
603
|
+
if (effective.recap?.enabled === false) recapController.invalidate(ctx);
|
|
604
|
+
},
|
|
605
|
+
});
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
if (subcommand === "fork") {
|
|
609
|
+
if (typeof ctx?.switchSession !== "function") {
|
|
610
|
+
notify(ctx, "Rarebit fork is unavailable in this Pi runtime", "error");
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
let destination;
|
|
614
|
+
let switchStarted = false;
|
|
615
|
+
try {
|
|
616
|
+
await ctx.waitForIdle?.();
|
|
617
|
+
const sourceFile = ctx.sessionManager?.getSessionFile?.();
|
|
618
|
+
const branch = ctx.sessionManager?.getBranch?.();
|
|
619
|
+
const sourceHeader = ctx.sessionManager?.getHeader?.();
|
|
620
|
+
if (typeof sourceFile !== "string" || !Array.isArray(branch))
|
|
621
|
+
throw new Error("the current Pi Session must be persisted before forking");
|
|
622
|
+
const maxTokenLength = Number(rest[1] ?? 64_000);
|
|
623
|
+
const model = ctx.model ?? null;
|
|
624
|
+
if (!model || typeof model.provider !== "string" || !model.provider || typeof model.id !== "string" || !model.id || !Number.isFinite(model.contextWindow) || model.contextWindow <= 0)
|
|
625
|
+
throw new Error("destination model/context window is unavailable; select a target Pi model before forking");
|
|
626
|
+
const effective = await loadEffective(ctx);
|
|
627
|
+
const promptOverheadTokens = Math.ceil(String(ctx.getSystemPrompt?.() ?? "").length / 4);
|
|
628
|
+
const activeToolNames = new Set(pi.getActiveTools?.() ?? []);
|
|
629
|
+
const activeToolSchema = (pi.getAllTools?.() ?? [])
|
|
630
|
+
.filter((tool) => activeToolNames.has(tool.name))
|
|
631
|
+
.map(({ name, description, parameters }) => ({ name, description, parameters }));
|
|
632
|
+
const toolOverheadTokens = Math.ceil(JSON.stringify(activeToolSchema).length / 4);
|
|
633
|
+
const plan = buildRarebitForkPlan({
|
|
634
|
+
header: sourceHeader, sessionFile: sourceFile, cwd: ctx.cwd, branch,
|
|
635
|
+
targetCwd: ctx.cwd, maxTokenLength, targetModel: model,
|
|
636
|
+
reserveTokens: effective.reserveTokens ?? 16_384,
|
|
637
|
+
promptOverheadTokens,
|
|
638
|
+
toolOverheadTokens,
|
|
639
|
+
});
|
|
640
|
+
const currentLeaf = ctx.sessionManager.getLeafId?.() ?? branch.at(-1)?.id ?? null;
|
|
641
|
+
if (currentLeaf !== (plan.branchLeafId ?? null))
|
|
642
|
+
throw new Error("the active source branch changed during fork preparation; retry without switching branches");
|
|
643
|
+
const session = createRarebitForkEntries(plan);
|
|
644
|
+
const sessionDir = ctx.sessionManager.getSessionDir?.();
|
|
645
|
+
if (typeof sessionDir !== "string" || !sessionDir)
|
|
646
|
+
throw new Error("the current Pi Session store is unavailable");
|
|
647
|
+
destination = sessionFilenameFor({ sessionDir, forkCreatedAt: plan.forkCreatedAt, sessionId: session.sessionId });
|
|
648
|
+
await writeRarebitForkFile(destination, session);
|
|
649
|
+
const currentSourceFile = ctx.sessionManager.getSessionFile?.();
|
|
650
|
+
const currentSourceId = ctx.sessionManager.getHeader?.()?.id;
|
|
651
|
+
const currentSourceLeaf = ctx.sessionManager.getLeafId?.() ?? ctx.sessionManager.getBranch?.()?.at(-1)?.id ?? null;
|
|
652
|
+
if (resolve(currentSourceFile ?? "") !== resolve(sourceFile) || currentSourceId !== sourceHeader?.id || currentSourceLeaf !== (plan.branchLeafId ?? null))
|
|
653
|
+
throw new Error("the active source Session or branch changed during fork preparation; retry without switching branches");
|
|
654
|
+
switchStarted = true;
|
|
655
|
+
const switched = await ctx.switchSession(destination, {
|
|
656
|
+
withSession: async (next) => {
|
|
657
|
+
notify(next, `Rarebit fork opened (${plan.importedTokens} imported tokens; ${plan.omittedOccurrences} omitted occurrences)`, "info");
|
|
658
|
+
},
|
|
659
|
+
});
|
|
660
|
+
if (switched?.cancelled) {
|
|
661
|
+
await rm(destination, { force: true });
|
|
662
|
+
destination = undefined;
|
|
663
|
+
notify(ctx, "Rarebit fork cancelled before switching", "warning");
|
|
664
|
+
}
|
|
665
|
+
} catch (error) {
|
|
666
|
+
if (destination && !switchStarted) await rm(destination, { force: true }).catch(() => {});
|
|
667
|
+
const retained = destination && switchStarted
|
|
668
|
+
? ` Destination retained at ${destination}; resume it if the replacement is active.`
|
|
669
|
+
: "";
|
|
670
|
+
notify(ctx, `Rarebit fork failed: ${error?.message ?? error}.${retained}`, "error");
|
|
671
|
+
}
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
437
674
|
if (subcommand === "recall") {
|
|
438
675
|
const prompt = rest[0];
|
|
439
676
|
if (typeof pi.sendUserMessage !== "function") {
|
|
@@ -491,7 +728,7 @@ ${fence(requestText)}`;
|
|
|
491
728
|
: "settings_or_default";
|
|
492
729
|
notify(
|
|
493
730
|
ctx,
|
|
494
|
-
`Rarebit ${subcommand}: auto-title=${autoTitleOverride ?? effective.autoTitle ?? true}; max_rarebit_ratio=${policy.maxRarebitRatio} (${ratioSource}); min_total_length=${policy.minTotalLength} estimated tokens via ceil(chars/4) (${lengthSource}); measurement=${policy.measurementVersion}; model=${modelLabel(effective.model)} from ${effective.modelProvenance?.settingsKey ?? "explicit config"}${policyStatus}`,
|
|
731
|
+
`Rarebit ${subcommand}: auto-title=${autoTitleOverride ?? effective.autoTitle ?? true}; recap=${effective.recap?.enabled !== false ? "on" : "off"} after ${effective.recap?.delayMs ?? 60000}ms (${effective.recap?.timezone ?? "host"}); diagnostics=triggered:${effective.diagnostics?.summaryTriggered === true ? "on" : "off"},updated:${effective.diagnostics?.summaryUpdated === true ? "on" : "off"}; max_input_tokens=${effective.maxInputTokens ?? 64000}; max_rarebit_ratio=${policy.maxRarebitRatio} (${ratioSource}); min_total_length=${policy.minTotalLength} estimated tokens via ceil(chars/4) (${lengthSource}); measurement=${policy.measurementVersion}; model=${modelLabel(effective.model)} from ${effective.modelProvenance?.settingsKey ?? "explicit config"}${policyStatus}`,
|
|
495
732
|
"info",
|
|
496
733
|
);
|
|
497
734
|
return;
|
|
@@ -521,6 +758,28 @@ ${fence(requestText)}`;
|
|
|
521
758
|
);
|
|
522
759
|
return;
|
|
523
760
|
}
|
|
761
|
+
if (subcommand === "recap") {
|
|
762
|
+
if (effective.recap?.enabled === false) {
|
|
763
|
+
recapController.invalidate(ctx);
|
|
764
|
+
notify(ctx, "Rarebit Recap is disabled in settings", "info");
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
const recap = await recapController.showExisting(ctx, {
|
|
768
|
+
sessionRoot: effective.sessionRoot,
|
|
769
|
+
rarebitRoot: effective.rarebitRoot,
|
|
770
|
+
recapTimezone: effective.recap?.timezone,
|
|
771
|
+
allowExternalSession: effective.allowExternalSession === true,
|
|
772
|
+
});
|
|
773
|
+
if (!recap.shown)
|
|
774
|
+
notify(
|
|
775
|
+
ctx,
|
|
776
|
+
recap.reason === "no_current_summary"
|
|
777
|
+
? "Rarebit has no current Summary for this Session branch"
|
|
778
|
+
: "Rarebit Recap is unavailable in this UI",
|
|
779
|
+
"warning",
|
|
780
|
+
);
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
524
783
|
if (subcommand === "title") {
|
|
525
784
|
void generateTitle(ctx, { manual: true }).catch(() =>
|
|
526
785
|
notify(ctx, "Rarebit title generation failed", "error"),
|
package/src/index.mjs
CHANGED
|
@@ -8,6 +8,7 @@ export * from "./rarebit-service.mjs";
|
|
|
8
8
|
export * from "./rarebit-session.mjs";
|
|
9
9
|
export * from "./rarebit-store.mjs";
|
|
10
10
|
export * from "./rarebit-artifact-state.mjs";
|
|
11
|
+
export * from "./rarebit-fork-lineage.mjs";
|
|
11
12
|
export * from "./rarebit-visual-language.mjs";
|
|
12
13
|
export * from "./rarebit-settings.mjs";
|
|
13
14
|
export { default } from "./extension.mjs";
|
package/src/lifecycle.mjs
CHANGED
|
@@ -16,7 +16,12 @@ function messageText(event) {
|
|
|
16
16
|
.join("\n");
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
function snapshotMaterializationContext(
|
|
19
|
+
function snapshotMaterializationContext(
|
|
20
|
+
ctx,
|
|
21
|
+
mayNotify,
|
|
22
|
+
lifecycleBoundary,
|
|
23
|
+
rarebitLifecycleGeneration,
|
|
24
|
+
) {
|
|
20
25
|
const sessionManager = ctx?.sessionManager;
|
|
21
26
|
const header = sessionManager?.getHeader?.();
|
|
22
27
|
const branch = sessionManager?.getBranch?.();
|
|
@@ -39,6 +44,7 @@ function snapshotMaterializationContext(ctx, mayNotify, lifecycleBoundary) {
|
|
|
39
44
|
modelRegistry: ctx?.modelRegistry,
|
|
40
45
|
sessionId: ctx?.sessionId,
|
|
41
46
|
lifecycleBoundary,
|
|
47
|
+
rarebitLifecycleGeneration,
|
|
42
48
|
isProjectTrusted: () => projectTrusted,
|
|
43
49
|
sessionManager: {
|
|
44
50
|
getHeader: () => header,
|
|
@@ -126,6 +132,7 @@ export function registerRarebitLifecycle(pi, schedule, options = {}) {
|
|
|
126
132
|
let ownerMessageSeen = false;
|
|
127
133
|
let sessionGeneration = 0;
|
|
128
134
|
let sessionLive = false;
|
|
135
|
+
let rarebitLifecycleGeneration = 0;
|
|
129
136
|
const MAX_PENDING_INPUT_ORIGINS = 256;
|
|
130
137
|
|
|
131
138
|
const clearInputOrigins = () => {
|
|
@@ -169,7 +176,8 @@ export function registerRarebitLifecycle(pi, schedule, options = {}) {
|
|
|
169
176
|
};
|
|
170
177
|
|
|
171
178
|
pi.on("session_start", (_event, ctx) => {
|
|
172
|
-
|
|
179
|
+
rarebitLifecycleGeneration += 1;
|
|
180
|
+
options.onSessionStart?.(ctx, rarebitLifecycleGeneration);
|
|
173
181
|
clearInputOrigins();
|
|
174
182
|
persistedUserInputAwaitingProvider = false;
|
|
175
183
|
selectedUserMessageAwaitingProvider = false;
|
|
@@ -185,13 +193,26 @@ export function registerRarebitLifecycle(pi, schedule, options = {}) {
|
|
|
185
193
|
// the selected Session evidence.
|
|
186
194
|
});
|
|
187
195
|
|
|
188
|
-
pi.on("agent_start", () => {
|
|
196
|
+
pi.on("agent_start", (_event, ctx) => {
|
|
197
|
+
rarebitLifecycleGeneration += 1;
|
|
198
|
+
options.onAgentStart?.(ctx, rarebitLifecycleGeneration);
|
|
189
199
|
// A retry, compaction pass, or queued continuation starts a new agent run;
|
|
190
200
|
// an earlier stop is not the fully settled branch.
|
|
191
201
|
normalStopAwaitingSettlement = false;
|
|
192
202
|
});
|
|
193
203
|
|
|
194
|
-
pi.on("
|
|
204
|
+
pi.on("turn_start", (_event, ctx) => {
|
|
205
|
+
rarebitLifecycleGeneration += 1;
|
|
206
|
+
options.onTurnStart?.(ctx, rarebitLifecycleGeneration);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
pi.on("input", (event, ctx) => {
|
|
210
|
+
rarebitLifecycleGeneration += 1;
|
|
211
|
+
try {
|
|
212
|
+
options.onInputSent?.(ctx, rarebitLifecycleGeneration);
|
|
213
|
+
} catch {
|
|
214
|
+
// Human-facing cancellation is optional and must not alter input delivery.
|
|
215
|
+
}
|
|
195
216
|
const bucket =
|
|
196
217
|
event?.streamingBehavior === "steer"
|
|
197
218
|
? "steer"
|
|
@@ -235,8 +256,9 @@ export function registerRarebitLifecycle(pi, schedule, options = {}) {
|
|
|
235
256
|
});
|
|
236
257
|
|
|
237
258
|
pi.on("session_tree", (event, ctx) => {
|
|
259
|
+
rarebitLifecycleGeneration += 1;
|
|
238
260
|
try {
|
|
239
|
-
options.onSessionTree?.(ctx, event);
|
|
261
|
+
options.onSessionTree?.(ctx, event, rarebitLifecycleGeneration);
|
|
240
262
|
} catch {
|
|
241
263
|
// Tree recency is an optional projection, not materialization truth.
|
|
242
264
|
}
|
|
@@ -261,6 +283,7 @@ export function registerRarebitLifecycle(pi, schedule, options = {}) {
|
|
|
261
283
|
ctx,
|
|
262
284
|
() => sessionLive && sessionGeneration === generation,
|
|
263
285
|
pendingLifecycleBoundary,
|
|
286
|
+
rarebitLifecycleGeneration,
|
|
264
287
|
);
|
|
265
288
|
pendingLifecycleBoundary = "owner_request";
|
|
266
289
|
schedule(snapshot);
|
|
@@ -306,17 +329,19 @@ export function registerRarebitLifecycle(pi, schedule, options = {}) {
|
|
|
306
329
|
ctx,
|
|
307
330
|
() => sessionLive && sessionGeneration === generation,
|
|
308
331
|
"agent_settled",
|
|
332
|
+
rarebitLifecycleGeneration,
|
|
309
333
|
),
|
|
310
334
|
);
|
|
311
335
|
});
|
|
312
336
|
|
|
313
|
-
pi.on("session_shutdown", () => {
|
|
337
|
+
pi.on("session_shutdown", (_event, ctx) => {
|
|
338
|
+
rarebitLifecycleGeneration += 1;
|
|
314
339
|
clearInputOrigins();
|
|
315
340
|
persistedUserInputAwaitingProvider = false;
|
|
316
341
|
selectedUserMessageAwaitingProvider = false;
|
|
317
342
|
firstOwnerInputAwaitingProvider = undefined;
|
|
318
343
|
normalStopAwaitingSettlement = false;
|
|
319
344
|
sessionLive = false;
|
|
320
|
-
options.onSessionShutdown?.();
|
|
345
|
+
options.onSessionShutdown?.(ctx, rarebitLifecycleGeneration);
|
|
321
346
|
});
|
|
322
347
|
}
|