@hypercarrier/rarebit 0.1.0-alpha.6 → 0.2.1
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 +33 -3
- package/README.md +296 -122
- package/RUNBOOK.md +16 -9
- package/docs/examples/rarebit.settings.json +19 -0
- package/package.json +9 -2
- package/schemas/rarebit.schema.json +2 -1
- package/scripts/e2e-recap-pi.mjs +458 -0
- package/scripts/e2e-settings-pi.mjs +267 -0
- package/scripts/e2e-v020-pi.mjs +271 -0
- package/scripts/experiment-recap-fullscreen.mjs +56 -0
- package/scripts/verify-package.mjs +14 -8
- package/src/extension.mjs +246 -24
- package/src/lifecycle.mjs +32 -7
- package/src/rarebit-artifact-state.mjs +8 -12
- package/src/rarebit-cli.mjs +2 -0
- package/src/rarebit-command.mjs +26 -1
- package/src/rarebit-core.mjs +122 -23
- package/src/rarebit-read-checkpoint.mjs +59 -0
- package/src/rarebit-recap.mjs +479 -0
- package/src/rarebit-service.mjs +65 -9
- package/src/rarebit-settings-ui.mjs +479 -0
- package/src/rarebit-settings.mjs +90 -7
- package/src/types.d.ts +65 -5
- package/test/lifecycle-contract-independent.test.mjs +2 -0
- package/test/package-artifact-identity.test.mjs +54 -0
- package/test/rarebit-artifact-state.test.mjs +10 -4
- package/test/rarebit-command.test.mjs +11 -4
- package/test/rarebit-core.test.mjs +50 -0
- package/test/rarebit-recap.test.mjs +662 -0
- package/test/rarebit-settings-ui.test.mjs +400 -0
- package/test/rarebit-sidecar-protocol.test.mjs +20 -0
- package/test/rarebit-v020.test.mjs +299 -0
- package/test/recap-read-checkpoint.test.mjs +126 -0
- package/test/summary-attention-independent.test.mjs +1 -1
package/src/extension.mjs
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
} from "./rarebit-service.mjs";
|
|
14
14
|
import {
|
|
15
15
|
DEFAULT_RAREBIT_SUMMARY_POLICY,
|
|
16
|
+
normalizeRarebitSummaryPrompt,
|
|
16
17
|
selectRarebits,
|
|
17
18
|
sha256,
|
|
18
19
|
} from "./rarebit-core.mjs";
|
|
@@ -23,6 +24,7 @@ import {
|
|
|
23
24
|
getRarebitArgumentCompletions,
|
|
24
25
|
parseRarebitCommand,
|
|
25
26
|
rarebitCommandDescription,
|
|
27
|
+
rarebitCommandUsage,
|
|
26
28
|
} from "./rarebit-command.mjs";
|
|
27
29
|
import {
|
|
28
30
|
buildRarebitForkPlan,
|
|
@@ -39,6 +41,13 @@ import {
|
|
|
39
41
|
createDetachedMaterializer,
|
|
40
42
|
registerRarebitLifecycle,
|
|
41
43
|
} from "./lifecycle.mjs";
|
|
44
|
+
import { createRarebitRecapController } from "./rarebit-recap.mjs";
|
|
45
|
+
import { readRarebitCurrent } from "./rarebit-store.mjs";
|
|
46
|
+
import {
|
|
47
|
+
openRarebitPalette,
|
|
48
|
+
openRarebitSettings,
|
|
49
|
+
rarebitPaletteCommand,
|
|
50
|
+
} from "./rarebit-settings-ui.mjs";
|
|
42
51
|
import {
|
|
43
52
|
automaticSummaryInhibitionIdentity,
|
|
44
53
|
queryAutomaticSummaryPolicy,
|
|
@@ -104,6 +113,10 @@ export async function readConfiguredRarebitSettings({
|
|
|
104
113
|
},
|
|
105
114
|
summaryPolicy: resolved.summaryPolicy,
|
|
106
115
|
autoTitle: resolved.autoTitle,
|
|
116
|
+
maxInputTokens: resolved.maxInputTokens,
|
|
117
|
+
summaryPrompt: resolved.summaryPrompt,
|
|
118
|
+
diagnostics: resolved.diagnostics,
|
|
119
|
+
recap: resolved.recap,
|
|
107
120
|
reserveTokens: resolveReserveTokens(global.value, project.value, model),
|
|
108
121
|
};
|
|
109
122
|
}
|
|
@@ -121,6 +134,23 @@ export default function registerPiRarebit(pi, config = {}) {
|
|
|
121
134
|
});
|
|
122
135
|
const activityReporter =
|
|
123
136
|
explicit.activityReporter ?? createHerdrActivityReporter();
|
|
137
|
+
const recapController = createRarebitRecapController({
|
|
138
|
+
appendEntry: typeof pi.registerEntryRenderer === "function" && typeof pi.appendEntry === "function"
|
|
139
|
+
? (type, data) => pi.appendEntry(type, data) : undefined,
|
|
140
|
+
delayMs: explicit.recap?.delayMs,
|
|
141
|
+
timezone: explicit.recap?.timezone,
|
|
142
|
+
});
|
|
143
|
+
pi.registerEntryRenderer?.("rarebit-recap", recapController.renderEntry);
|
|
144
|
+
const acknowledgeRecap = (ctx) => {
|
|
145
|
+
const result = recapController.acknowledge(ctx);
|
|
146
|
+
notify(ctx, result.acknowledged ? "Recap marked as read" :
|
|
147
|
+
result.reason === "already_read" ? "Recap already marked as read" : "No Recap to mark as read in this branch", "info");
|
|
148
|
+
};
|
|
149
|
+
if (typeof pi.registerEntryRenderer === "function")
|
|
150
|
+
pi.registerShortcut?.("ctrl+alt+g", {
|
|
151
|
+
description: "Mark the latest Recap as read",
|
|
152
|
+
handler: async (ctx) => acknowledgeRecap(ctx),
|
|
153
|
+
});
|
|
124
154
|
const projectActivity = (ctx) => {
|
|
125
155
|
const entries = ctx?.sessionManager?.getBranch?.() ?? [];
|
|
126
156
|
return projectRarebitSessionActivity({
|
|
@@ -155,10 +185,11 @@ export default function registerPiRarebit(pi, config = {}) {
|
|
|
155
185
|
? `${name} tokens: ${value}`
|
|
156
186
|
: `${name} tokens: unavailable`;
|
|
157
187
|
|
|
158
|
-
const notifyOutcome = (ctx, result, triggered) => {
|
|
159
|
-
if (
|
|
188
|
+
const notifyOutcome = (ctx, result, triggered, diagnostics = {}) => {
|
|
189
|
+
if (result?.duplicate || result?.inFlight) return;
|
|
160
190
|
switch (result?.record?.status) {
|
|
161
191
|
case "ok":
|
|
192
|
+
if (!triggered || diagnostics.summaryUpdated !== true) return;
|
|
162
193
|
notify(
|
|
163
194
|
ctx,
|
|
164
195
|
`Rarebit Summary updated (${tokenLabel("input", result.record.synthesis?.usage?.inputTokens)} · ${tokenLabel("output", result.record.synthesis?.usage?.outputTokens)} · model ${resultModelLabel(result)})`,
|
|
@@ -221,6 +252,16 @@ export default function registerPiRarebit(pi, config = {}) {
|
|
|
221
252
|
...injected,
|
|
222
253
|
...explicit,
|
|
223
254
|
summaryPolicy,
|
|
255
|
+
maxInputTokens: explicit.maxInputTokens ?? injected.maxInputTokens,
|
|
256
|
+
summaryPrompt: explicit.summaryPrompt ?? injected.summaryPrompt,
|
|
257
|
+
diagnostics: {
|
|
258
|
+
...(injected.diagnostics ?? {}),
|
|
259
|
+
...(explicit.diagnostics ?? {}),
|
|
260
|
+
},
|
|
261
|
+
recap: {
|
|
262
|
+
...(injected.recap ?? {}),
|
|
263
|
+
...(explicit.recap ?? {}),
|
|
264
|
+
},
|
|
224
265
|
piAi: explicit.piAi ?? {
|
|
225
266
|
complete: async (...args) => (await import("@earendil-works/pi-ai/compat")).complete(...args),
|
|
226
267
|
},
|
|
@@ -228,10 +269,23 @@ export default function registerPiRarebit(pi, config = {}) {
|
|
|
228
269
|
};
|
|
229
270
|
|
|
230
271
|
const materialize = async (ctx, { force = false } = {}) => {
|
|
272
|
+
const recapToken = recapController.captureMaterialization(ctx);
|
|
231
273
|
let synthesisTriggered = false;
|
|
232
274
|
const effective = await loadEffective(ctx);
|
|
275
|
+
let summaryPrompt;
|
|
276
|
+
try {
|
|
277
|
+
summaryPrompt = normalizeRarebitSummaryPrompt(effective.summaryPrompt).guidance;
|
|
278
|
+
} catch (error) {
|
|
279
|
+
notify(
|
|
280
|
+
ctx,
|
|
281
|
+
`Invalid rarebit.summary_prompt: ${error?.message ?? error}`,
|
|
282
|
+
"error",
|
|
283
|
+
);
|
|
284
|
+
return { record: { status: "invalid_configuration" } };
|
|
285
|
+
}
|
|
233
286
|
const result = await processRarebitSummary(ctx, {
|
|
234
287
|
...effective,
|
|
288
|
+
summaryPrompt,
|
|
235
289
|
forceSynthesis: force,
|
|
236
290
|
lifecycleBoundary: force ? "manual" : ctx?.lifecycleBoundary,
|
|
237
291
|
queryAutomaticSummaryPolicy:
|
|
@@ -239,15 +293,27 @@ export default function registerPiRarebit(pi, config = {}) {
|
|
|
239
293
|
onSynthesisTriggered: (detail) => {
|
|
240
294
|
synthesisTriggered = true;
|
|
241
295
|
const count = detail.rarebitCount ?? detail.rarebitCount;
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
296
|
+
if (effective.diagnostics?.summaryTriggered === true)
|
|
297
|
+
notify(
|
|
298
|
+
ctx,
|
|
299
|
+
`Rarebit Summary triggered (${count} Rarebits · input tokens: ~${detail.estimatedInputTokens} estimated (chars/4) · model ${modelLabel(detail.model)})`,
|
|
300
|
+
"info",
|
|
301
|
+
);
|
|
247
302
|
},
|
|
248
303
|
});
|
|
249
|
-
notifyOutcome(ctx, result, synthesisTriggered);
|
|
250
|
-
|
|
304
|
+
if (!force) notifyOutcome(ctx, result, synthesisTriggered, effective.diagnostics);
|
|
305
|
+
recapController.offer({
|
|
306
|
+
ctx,
|
|
307
|
+
result,
|
|
308
|
+
token: recapToken,
|
|
309
|
+
recapEnabled: effective.recap?.enabled !== false,
|
|
310
|
+
recapDelayMs: effective.recap?.delayMs,
|
|
311
|
+
recapTimezone: effective.recap?.timezone,
|
|
312
|
+
sessionRoot: effective.sessionRoot,
|
|
313
|
+
rarebitRoot: effective.rarebitRoot,
|
|
314
|
+
allowExternalSession: effective.allowExternalSession === true,
|
|
315
|
+
});
|
|
316
|
+
return { ...result, rarebitEffective: effective };
|
|
251
317
|
};
|
|
252
318
|
const schedule = createDetachedMaterializer(materialize, (_error, ctx) => {
|
|
253
319
|
notify(
|
|
@@ -256,15 +322,49 @@ export default function registerPiRarebit(pi, config = {}) {
|
|
|
256
322
|
"error",
|
|
257
323
|
);
|
|
258
324
|
});
|
|
259
|
-
const
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
325
|
+
const manualAssessmentIsCurrent = async (result, ctx) => {
|
|
326
|
+
const session = identityFrom(ctx);
|
|
327
|
+
const generation = activeSession?.generation;
|
|
328
|
+
const selectionHash = selectRarebits(ctx?.sessionManager?.getBranch?.() ?? []).manifestHash;
|
|
329
|
+
if (!sameActiveSession(session)) return false;
|
|
330
|
+
const current = await readRarebitCurrent({
|
|
331
|
+
sessionFile: session.sessionFile,
|
|
332
|
+
sessionRoot: result.rarebitEffective?.sessionRoot,
|
|
333
|
+
rarebitRoot: result.rarebitEffective?.rarebitRoot,
|
|
334
|
+
allowExternalSession: result.rarebitEffective?.allowExternalSession === true,
|
|
335
|
+
}).catch(() => null);
|
|
336
|
+
if (!sameActiveSession(session) || activeSession?.generation !== generation ||
|
|
337
|
+
!sameActiveSession(identityFrom(ctx)) ||
|
|
338
|
+
selectRarebits(ctx?.sessionManager?.getBranch?.() ?? []).manifestHash !== selectionHash)
|
|
339
|
+
return false;
|
|
340
|
+
return current?.artifactState?.receiptRef?.jobId === result.record?.jobId &&
|
|
341
|
+
current?.artifactState?.nativeRef?.sessionId === session.sessionId &&
|
|
342
|
+
current?.artifactState?.nativeRef?.selectionManifestHash === selectionHash &&
|
|
343
|
+
current?.artifactState?.syncState === "assessment_current";
|
|
344
|
+
};
|
|
345
|
+
const scheduleManualSummary = (ctx) => {
|
|
346
|
+
void materialize(ctx, { force: true }).then(async (result) => {
|
|
347
|
+
if (result?.inFlight) {
|
|
348
|
+
notify(ctx, "Rarebit Summary is already in flight", "info");
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
if (result?.record?.status === "ok") {
|
|
352
|
+
const isCurrent = await manualAssessmentIsCurrent(result, ctx);
|
|
353
|
+
const duplicate = result?.duplicate === true;
|
|
354
|
+
notify(ctx,
|
|
355
|
+
duplicate
|
|
356
|
+
? isCurrent ? "Rarebit Summary is already current" : "Rarebit Summary already exists but is not current"
|
|
357
|
+
: isCurrent ? "Rarebit Summary updated and current" : "Rarebit Summary generated but is not current",
|
|
358
|
+
isCurrent ? "info" : "warning",
|
|
359
|
+
);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
if (result?.record?.status === "skipped_ephemeral_session")
|
|
363
|
+
notify(ctx, "Rarebit Summary unavailable: Session is not persisted", "warning");
|
|
364
|
+
else
|
|
365
|
+
notifyOutcome(ctx, { record: result?.record }, false, result?.rarebitEffective?.diagnostics ?? {});
|
|
366
|
+
}).catch(() => notify(ctx, "Rarebit Summary failed; inspect its private materialization", "error"));
|
|
367
|
+
};
|
|
268
368
|
|
|
269
369
|
const identityFrom = (ctx) => ({
|
|
270
370
|
sessionId: String(
|
|
@@ -354,20 +454,33 @@ export default function registerPiRarebit(pi, config = {}) {
|
|
|
354
454
|
);
|
|
355
455
|
|
|
356
456
|
registerRarebitLifecycle(pi, schedule, {
|
|
357
|
-
onSessionStart: (ctx) => {
|
|
358
|
-
|
|
457
|
+
onSessionStart: (ctx, generation) => {
|
|
458
|
+
recapController.invalidate(ctx, generation);
|
|
459
|
+
activeSession = { ...identityFrom(ctx), generation };
|
|
359
460
|
activityReporter.start(projectActivity(ctx));
|
|
360
461
|
},
|
|
361
|
-
|
|
462
|
+
onAgentStart: (ctx, generation) => {
|
|
463
|
+
recapController.invalidate(ctx, generation);
|
|
464
|
+
},
|
|
465
|
+
onTurnStart: (ctx, generation) => {
|
|
466
|
+
recapController.invalidate(ctx, generation);
|
|
467
|
+
},
|
|
468
|
+
onInputSent: (ctx, generation) => {
|
|
469
|
+
recapController.invalidate(ctx, generation);
|
|
470
|
+
},
|
|
471
|
+
onSessionTree: (ctx, _event, generation) => {
|
|
472
|
+
recapController.invalidate(ctx, generation);
|
|
362
473
|
activityReporter.update(projectActivity(ctx));
|
|
363
474
|
},
|
|
364
475
|
onSelectedUserPersisted: (ctx) => {
|
|
365
476
|
activityReporter.update(projectActivity(ctx));
|
|
366
477
|
},
|
|
367
478
|
onAgentSettled: (ctx) => {
|
|
479
|
+
recapController.updateContext(ctx);
|
|
368
480
|
activityReporter.update(projectActivity(ctx));
|
|
369
481
|
},
|
|
370
|
-
onSessionShutdown: () => {
|
|
482
|
+
onSessionShutdown: (ctx, generation) => {
|
|
483
|
+
recapController.invalidate(ctx, generation);
|
|
371
484
|
activeSession = undefined;
|
|
372
485
|
activityReporter.stop();
|
|
373
486
|
},
|
|
@@ -445,7 +558,35 @@ ${fence(requestText)}`;
|
|
|
445
558
|
description: rarebitCommandDescription(),
|
|
446
559
|
getArgumentCompletions: getRarebitArgumentCompletions,
|
|
447
560
|
handler: async (args, ctx) => {
|
|
448
|
-
|
|
561
|
+
let commandInput = String(args ?? "");
|
|
562
|
+
const trimmedCommandInput = commandInput.trim();
|
|
563
|
+
if (!trimmedCommandInput || trimmedCommandInput === "menu") {
|
|
564
|
+
const currentUiState = () => ({
|
|
565
|
+
...identityFrom(ctx),
|
|
566
|
+
branchLeafId: ctx?.sessionManager?.getBranch?.()?.at(-1)?.id ?? null,
|
|
567
|
+
cwd: ctx?.cwd ?? process.cwd(),
|
|
568
|
+
projectTrusted: ctx?.isProjectTrusted?.() === true,
|
|
569
|
+
});
|
|
570
|
+
const initial = currentUiState();
|
|
571
|
+
const selection = await openRarebitPalette(ctx, {
|
|
572
|
+
isCurrent: () => {
|
|
573
|
+
const current = currentUiState();
|
|
574
|
+
return current.sessionId === initial.sessionId &&
|
|
575
|
+
current.sessionFile === initial.sessionFile &&
|
|
576
|
+
current.branchLeafId === initial.branchLeafId &&
|
|
577
|
+
current.cwd === initial.cwd &&
|
|
578
|
+
current.projectTrusted === initial.projectTrusted;
|
|
579
|
+
},
|
|
580
|
+
});
|
|
581
|
+
commandInput = rarebitPaletteCommand(selection) ?? "";
|
|
582
|
+
if (!commandInput) return;
|
|
583
|
+
if (selection?.id === "recall") {
|
|
584
|
+
const prompt = await ctx.ui.input?.("Rarebit recall prompt", "");
|
|
585
|
+
if (prompt === undefined || !String(prompt).trim()) return;
|
|
586
|
+
commandInput = `recall ${prompt}`;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
const command = parseRarebitCommand(commandInput);
|
|
449
590
|
if (!command.ok) {
|
|
450
591
|
notify(
|
|
451
592
|
ctx,
|
|
@@ -457,6 +598,61 @@ ${fence(requestText)}`;
|
|
|
457
598
|
return;
|
|
458
599
|
}
|
|
459
600
|
const { subcommand, arguments: rest } = command;
|
|
601
|
+
if (subcommand === "help") {
|
|
602
|
+
notify(
|
|
603
|
+
ctx,
|
|
604
|
+
[
|
|
605
|
+
rarebitCommandUsage(),
|
|
606
|
+
...(commandInput.trim().split(/\s+/).length === 1
|
|
607
|
+
? ["Use /rarebit to open the interactive command palette."]
|
|
608
|
+
: []),
|
|
609
|
+
].join("\n"),
|
|
610
|
+
"info",
|
|
611
|
+
);
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
if (subcommand === "settings") {
|
|
615
|
+
const currentUiState = () => ({
|
|
616
|
+
...identityFrom(ctx),
|
|
617
|
+
branchLeafId: ctx?.sessionManager?.getBranch?.()?.at(-1)?.id ?? null,
|
|
618
|
+
cwd: ctx?.cwd ?? process.cwd(),
|
|
619
|
+
projectTrusted: ctx?.isProjectTrusted?.() === true,
|
|
620
|
+
});
|
|
621
|
+
const initial = currentUiState();
|
|
622
|
+
await openRarebitSettings(ctx, {
|
|
623
|
+
initialScope: rest[0] === "project" ? "project" : "global",
|
|
624
|
+
projectTrusted: initial.projectTrusted,
|
|
625
|
+
isCurrent: () => {
|
|
626
|
+
const current = currentUiState();
|
|
627
|
+
return current.sessionId === initial.sessionId &&
|
|
628
|
+
current.sessionFile === initial.sessionFile &&
|
|
629
|
+
current.branchLeafId === initial.branchLeafId &&
|
|
630
|
+
current.cwd === initial.cwd &&
|
|
631
|
+
current.projectTrusted === initial.projectTrusted;
|
|
632
|
+
},
|
|
633
|
+
onSaved: async ({ namespace, document }) => {
|
|
634
|
+
const previous = document?.namespace ?? {};
|
|
635
|
+
const changed = (path) => {
|
|
636
|
+
const read = (value) => path.reduce((current, key) => current?.[key], value);
|
|
637
|
+
return JSON.stringify(read(previous)) !== JSON.stringify(read(namespace));
|
|
638
|
+
};
|
|
639
|
+
if (changed(["max_rarebit_ratio"])) {
|
|
640
|
+
const next = { ...policyOverrides };
|
|
641
|
+
delete next.maxRarebitRatio;
|
|
642
|
+
policyOverrides = next;
|
|
643
|
+
}
|
|
644
|
+
if (changed(["min_total_length"])) {
|
|
645
|
+
const next = { ...policyOverrides };
|
|
646
|
+
delete next.minTotalLength;
|
|
647
|
+
policyOverrides = next;
|
|
648
|
+
}
|
|
649
|
+
if (changed(["auto_title"])) autoTitleOverride = undefined;
|
|
650
|
+
const effective = await loadEffective(ctx);
|
|
651
|
+
if (effective.recap?.enabled === false) recapController.invalidate(ctx);
|
|
652
|
+
},
|
|
653
|
+
});
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
460
656
|
if (subcommand === "fork") {
|
|
461
657
|
if (typeof ctx?.switchSession !== "function") {
|
|
462
658
|
notify(ctx, "Rarebit fork is unavailable in this Pi runtime", "error");
|
|
@@ -580,7 +776,7 @@ ${fence(requestText)}`;
|
|
|
580
776
|
: "settings_or_default";
|
|
581
777
|
notify(
|
|
582
778
|
ctx,
|
|
583
|
-
`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}`,
|
|
779
|
+
`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}`,
|
|
584
780
|
"info",
|
|
585
781
|
);
|
|
586
782
|
return;
|
|
@@ -610,6 +806,32 @@ ${fence(requestText)}`;
|
|
|
610
806
|
);
|
|
611
807
|
return;
|
|
612
808
|
}
|
|
809
|
+
if (subcommand === "got-it") {
|
|
810
|
+
acknowledgeRecap(ctx);
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
if (subcommand === "recap") {
|
|
814
|
+
if (effective.recap?.enabled === false) {
|
|
815
|
+
recapController.invalidate(ctx);
|
|
816
|
+
notify(ctx, "Rarebit Recap is disabled in settings", "info");
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
const recap = await recapController.showExisting(ctx, {
|
|
820
|
+
sessionRoot: effective.sessionRoot,
|
|
821
|
+
rarebitRoot: effective.rarebitRoot,
|
|
822
|
+
recapTimezone: effective.recap?.timezone,
|
|
823
|
+
allowExternalSession: effective.allowExternalSession === true,
|
|
824
|
+
});
|
|
825
|
+
if (!recap.shown)
|
|
826
|
+
notify(
|
|
827
|
+
ctx,
|
|
828
|
+
recap.reason === "no_current_summary"
|
|
829
|
+
? "Rarebit has no current Summary for this Session branch"
|
|
830
|
+
: "Rarebit Recap is unavailable in this UI",
|
|
831
|
+
"warning",
|
|
832
|
+
);
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
613
835
|
if (subcommand === "title") {
|
|
614
836
|
void generateTitle(ctx, { manual: true }).catch(() =>
|
|
615
837
|
notify(ctx, "Rarebit title generation failed", "error"),
|
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
|
}
|
|
@@ -383,6 +383,7 @@ export function validateRarebitArtifactReceipt(record) {
|
|
|
383
383
|
"hc-rarebit-summary-v4",
|
|
384
384
|
"hc-rarebit-summary-v5",
|
|
385
385
|
"hc-rarebit-summary-v6",
|
|
386
|
+
"hc-rarebit-summary-v7",
|
|
386
387
|
].includes(
|
|
387
388
|
record.implementationVersion,
|
|
388
389
|
) ||
|
|
@@ -407,7 +408,7 @@ export function validateRarebitArtifactReceipt(record) {
|
|
|
407
408
|
if (
|
|
408
409
|
(hasInputCoverage && !validInputCoverage(record.inputCoverage)) ||
|
|
409
410
|
(record.status === "ok" &&
|
|
410
|
-
["hc-rarebit-summary-v5", "hc-rarebit-summary-v6"].includes(
|
|
411
|
+
["hc-rarebit-summary-v5", "hc-rarebit-summary-v6", "hc-rarebit-summary-v7"].includes(
|
|
411
412
|
record.implementationVersion,
|
|
412
413
|
) &&
|
|
413
414
|
!hasInputCoverage)
|
|
@@ -776,16 +777,7 @@ export function projectRarebitArtifactState({
|
|
|
776
777
|
const settled = newest(
|
|
777
778
|
exact.filter(({ record }) => record.lifecycleBoundary === "agent_settled"),
|
|
778
779
|
);
|
|
779
|
-
if (
|
|
780
|
-
return result({
|
|
781
|
-
syncState: "assessment_current",
|
|
782
|
-
projection: projection(settled.record),
|
|
783
|
-
applicability: "exact_selection",
|
|
784
|
-
nativeRef: nativeReference(native),
|
|
785
|
-
receiptRef: ref(settled.record),
|
|
786
|
-
retry: null,
|
|
787
|
-
});
|
|
788
|
-
if (activeRequest) {
|
|
780
|
+
if (activeRequest && !(settled && newerThan(settled, activeRequest))) {
|
|
789
781
|
const cutLength = activeRequest.record.selection.occurrenceCount;
|
|
790
782
|
const settlementPending = native.selection.occurrences
|
|
791
783
|
.slice(cutLength)
|
|
@@ -805,7 +797,11 @@ export function projectRarebitArtifactState({
|
|
|
805
797
|
});
|
|
806
798
|
}
|
|
807
799
|
const assessment = newest(
|
|
808
|
-
exact.filter(({ record }) =>
|
|
800
|
+
exact.filter(({ record }) =>
|
|
801
|
+
record.lifecycleBoundary === "agent_settled" ||
|
|
802
|
+
record.lifecycleBoundary === "manual" ||
|
|
803
|
+
record.lifecycleBoundary === "session_start",
|
|
804
|
+
),
|
|
809
805
|
);
|
|
810
806
|
if (assessment)
|
|
811
807
|
return result({
|
package/src/rarebit-cli.mjs
CHANGED
|
@@ -205,6 +205,7 @@ export async function readRarebitCliSettings({
|
|
|
205
205
|
rawRefs: [globalPath, projectPath],
|
|
206
206
|
},
|
|
207
207
|
summaryPolicy: resolved.summaryPolicy,
|
|
208
|
+
summaryPrompt: resolved.summaryPrompt,
|
|
208
209
|
autoTitle: resolved.autoTitle,
|
|
209
210
|
};
|
|
210
211
|
}
|
|
@@ -351,6 +352,7 @@ export async function runRarebitCli(options, dependencies = {}) {
|
|
|
351
352
|
model: runtime.model,
|
|
352
353
|
modelProvenance: runtime.modelProvenance,
|
|
353
354
|
summaryPolicy: runtime.summaryPolicy,
|
|
355
|
+
summaryPrompt: runtime.summaryPrompt,
|
|
354
356
|
forceSynthesis: options.force === true,
|
|
355
357
|
allowExternalSession: true,
|
|
356
358
|
rarebitRoot: dependencies.rarebitRoot,
|
package/src/rarebit-command.mjs
CHANGED
|
@@ -17,8 +17,23 @@ const rest = (usage) => ({
|
|
|
17
17
|
|
|
18
18
|
export const RAREBIT_COMMAND_GRAMMAR = Object.freeze({
|
|
19
19
|
name: "rarebit",
|
|
20
|
-
defaultSubcommand: "
|
|
20
|
+
defaultSubcommand: "menu",
|
|
21
21
|
subcommands: Object.freeze([
|
|
22
|
+
{
|
|
23
|
+
name: "menu",
|
|
24
|
+
description: "Open the interactive Rarebit command palette",
|
|
25
|
+
forms: [[]],
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: "settings",
|
|
29
|
+
description: "Open interactive Rarebit settings",
|
|
30
|
+
forms: [[], [literal("global", "Edit global Pi settings")], [literal("project", "Edit trusted project settings")]],
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: "help",
|
|
34
|
+
description: "Show Rarebit command help",
|
|
35
|
+
forms: [[]],
|
|
36
|
+
},
|
|
22
37
|
{
|
|
23
38
|
name: "status",
|
|
24
39
|
description: "Show effective Rarebit configuration",
|
|
@@ -73,6 +88,16 @@ export const RAREBIT_COMMAND_GRAMMAR = Object.freeze({
|
|
|
73
88
|
usageNote: "use Pi's native /name for a literal title",
|
|
74
89
|
forms: [[]],
|
|
75
90
|
},
|
|
91
|
+
{
|
|
92
|
+
name: "recap",
|
|
93
|
+
description: "Show the current Recap in the TUI",
|
|
94
|
+
forms: [[]],
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
name: "got-it",
|
|
98
|
+
description: "Mark the latest displayed Recap as read",
|
|
99
|
+
forms: [[]],
|
|
100
|
+
},
|
|
76
101
|
{
|
|
77
102
|
name: "summarize",
|
|
78
103
|
description: "Force a Rarebit Summary materialization",
|