@vanillagreen/pi-qol 1.4.3 → 1.6.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 +38 -5
- package/extensions/qol/budget-guard-runtime.ts +100 -0
- package/extensions/qol/budget-guard.ts +317 -0
- package/extensions/qol/compaction-handoff.ts +190 -0
- package/extensions/qol/compaction.ts +135 -20
- package/extensions/qol/constants.ts +23 -1
- package/extensions/qol/context-usage.ts +35 -0
- package/extensions/qol/editor.ts +13 -5
- package/extensions/qol/glyphs.ts +122 -0
- package/extensions/qol/rate-limit-auto-resume.ts +447 -0
- package/extensions/qol/schedule.ts +320 -0
- package/extensions/qol/session-search/component.ts +33 -11
- package/extensions/qol/status-message.ts +7 -1
- package/extensions/qol/statusline.ts +5 -4
- package/extensions/qol/transcript-risk.ts +28 -0
- package/extensions/qol.ts +101 -21
- package/package.json +140 -5
package/extensions/qol.ts
CHANGED
|
@@ -2,7 +2,9 @@ import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@e
|
|
|
2
2
|
import type { TUI } from "@earendil-works/pi-tui";
|
|
3
3
|
import { criticalInfo, lastAssistantTextFromAgentEnd, needsDirection, taskStats } from "./qol/agent-end.js";
|
|
4
4
|
import { getQuestionService, readCavemanBridge, type QuestionOpenedEventLike } from "./qol/bridges.js";
|
|
5
|
+
import { BudgetGuardDriver } from "./qol/budget-guard-runtime.js";
|
|
5
6
|
import {
|
|
7
|
+
budgetGuardTrigger,
|
|
6
8
|
compactionTriggerReason,
|
|
7
9
|
handleQolBranchSummary,
|
|
8
10
|
handleQolCompaction,
|
|
@@ -44,6 +46,7 @@ import {
|
|
|
44
46
|
restorePendingQueueThemePatch,
|
|
45
47
|
} from "./qol/pending-queue.js";
|
|
46
48
|
import { permissionGateMatch, permissionGatePrompt } from "./qol/permission-gate.js";
|
|
49
|
+
import { createRateLimitAutoResumeController, RATE_LIMIT_AUTO_RESUME_EVENT } from "./qol/rate-limit-auto-resume.js";
|
|
47
50
|
import {
|
|
48
51
|
autoRenameEnabled,
|
|
49
52
|
autoRenameNotify,
|
|
@@ -53,6 +56,7 @@ import {
|
|
|
53
56
|
isStaleCtxError,
|
|
54
57
|
withAutoRenamePrefix,
|
|
55
58
|
} from "./qol/session-rename.js";
|
|
59
|
+
import { createScheduleController, getScheduleArgumentCompletions } from "./qol/schedule.js";
|
|
56
60
|
import {
|
|
57
61
|
consumePendingSessionSearchContext,
|
|
58
62
|
openQolSessionSearch,
|
|
@@ -257,6 +261,35 @@ export default function qol(pi: ExtensionAPI): void {
|
|
|
257
261
|
idleCompactionTimer = undefined;
|
|
258
262
|
};
|
|
259
263
|
|
|
264
|
+
const budgetGuardDriver = new BudgetGuardDriver();
|
|
265
|
+
|
|
266
|
+
const resetBudgetGuard = () => {
|
|
267
|
+
budgetGuardDriver.reset();
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const maybeFireBudgetGuard = (ctx: ExtensionContext) => {
|
|
271
|
+
let trigger;
|
|
272
|
+
try {
|
|
273
|
+
trigger = budgetGuardTrigger(ctx);
|
|
274
|
+
} catch (error) {
|
|
275
|
+
if (isStaleCtxError(error)) return;
|
|
276
|
+
throw error;
|
|
277
|
+
}
|
|
278
|
+
const notifySafely = (message: string, level: "info" | "warning" | "error") => {
|
|
279
|
+
try {
|
|
280
|
+
if (ctx.hasUI && settingBoolean("compaction.notify", true, ctx.cwd)) ctx.ui.notify(message, level);
|
|
281
|
+
} catch (error) {
|
|
282
|
+
if (isStaleCtxError(error)) return;
|
|
283
|
+
throw error;
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
budgetGuardDriver.dispatch({
|
|
287
|
+
compact: typeof ctx.compact === "function" ? ctx.compact.bind(ctx) : undefined,
|
|
288
|
+
notify: notifySafely,
|
|
289
|
+
trigger,
|
|
290
|
+
});
|
|
291
|
+
};
|
|
292
|
+
|
|
260
293
|
const scheduleIdleCompaction = (ctx: ExtensionContext) => {
|
|
261
294
|
clearIdleCompactionTimer();
|
|
262
295
|
if (!settingBoolean("compaction.idleEnabled", false, ctx.cwd)) return;
|
|
@@ -344,7 +377,13 @@ export default function qol(pi: ExtensionAPI): void {
|
|
|
344
377
|
let lastSessionTitle: string | undefined;
|
|
345
378
|
|
|
346
379
|
const requestRender = () => activeTui?.requestRender();
|
|
380
|
+
const scheduleController = createScheduleController(pi);
|
|
381
|
+
scheduleController.setOnChange(requestRender);
|
|
382
|
+
const rateLimitAutoResumeController = createRateLimitAutoResumeController(pi);
|
|
383
|
+
rateLimitAutoResumeController.setOnChange(requestRender);
|
|
384
|
+
const statuslineEnabled = (ctx: ExtensionContext): boolean => settingBoolean("statusline.enabled", true, ctx.cwd);
|
|
347
385
|
const refreshStatusline = (ctx: ExtensionContext) => {
|
|
386
|
+
if (!statuslineEnabled(ctx)) return Promise.resolve();
|
|
348
387
|
if (refreshInFlight) return refreshInFlight;
|
|
349
388
|
refreshInFlight = refreshGitState(pi, ctx)
|
|
350
389
|
.then((next) => {
|
|
@@ -503,13 +542,22 @@ export default function qol(pi: ExtensionAPI): void {
|
|
|
503
542
|
const event = data as QuestionOpenedEventLike;
|
|
504
543
|
notifyQuestionOpened(currentCtx, event, "question");
|
|
505
544
|
});
|
|
545
|
+
pi.events.on(RATE_LIMIT_AUTO_RESUME_EVENT, (payload: unknown) => {
|
|
546
|
+
if (!currentCtx) return;
|
|
547
|
+
rateLimitAutoResumeController.noteExternalRateLimitEvent(payload, currentCtx);
|
|
548
|
+
});
|
|
506
549
|
|
|
507
550
|
pi.on("session_start", (event, ctx) => {
|
|
508
551
|
currentCtx = ctx;
|
|
509
552
|
subscribeCavemanBridge();
|
|
510
553
|
latestSystemPromptOptions = undefined;
|
|
511
554
|
resetAutoRename();
|
|
555
|
+
resetBudgetGuard();
|
|
512
556
|
resetThinkingTimer(ctx);
|
|
557
|
+
const scheduleEnabled = settingBoolean("enableScheduleCommand", true, ctx.cwd);
|
|
558
|
+
const rateLimitAutoResumeEnabled = rateLimitAutoResumeController.enabled(ctx);
|
|
559
|
+
if (scheduleEnabled) scheduleController.restoreFromBranch(ctx);
|
|
560
|
+
else scheduleController.clearTimers();
|
|
513
561
|
void consumePendingSessionSearchContext(pi, ctx, event.reason);
|
|
514
562
|
installAutocompleteHintStyling(ctx);
|
|
515
563
|
installPendingQueueThemePatch(ctx);
|
|
@@ -517,7 +565,8 @@ export default function qol(pi: ExtensionAPI): void {
|
|
|
517
565
|
ctx.ui.setHiddenThinkingLabel(hiddenThinkingLabel(ctx.ui.theme, ctx.cwd));
|
|
518
566
|
applyWorkingIndicatorMode(ctx);
|
|
519
567
|
gitState = makeFallbackGitState(ctx.cwd);
|
|
520
|
-
|
|
568
|
+
const showStatusline = statuslineEnabled(ctx);
|
|
569
|
+
if (showStatusline) void refreshStatusline(ctx);
|
|
521
570
|
installSessionTitle(ctx);
|
|
522
571
|
installSessionTitleSync(ctx);
|
|
523
572
|
ctx.ui.setEditorComponent((tui, theme, keybindings) => {
|
|
@@ -526,27 +575,32 @@ export default function qol(pi: ExtensionAPI): void {
|
|
|
526
575
|
? new QolCompactPromptEditor(tui, theme, keybindings, Math.max(0, Math.floor(settingNumber("inputBottomPaddingLines", DEFAULT_INPUT_BOTTOM_PADDING_LINES, ctx.cwd))), ctx)
|
|
527
576
|
: new QolEditor(tui, theme, keybindings, ctx);
|
|
528
577
|
});
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
ctx.ui.setFooter((tui, _theme, footerData) => {
|
|
543
|
-
activeTui = tui;
|
|
544
|
-
const unsubscribe = footerData.onBranchChange(() => {
|
|
545
|
-
void refreshStatusline(ctx);
|
|
546
|
-
requestRender();
|
|
578
|
+
if (showStatusline || scheduleEnabled || rateLimitAutoResumeEnabled) {
|
|
579
|
+
const statusWidgetTimer = setTimeout(() => {
|
|
580
|
+
ctx.ui.setWidget("statusline", (tui, theme) => {
|
|
581
|
+
activeTui = tui;
|
|
582
|
+
return {
|
|
583
|
+
invalidate() {},
|
|
584
|
+
render(width: number): string[] {
|
|
585
|
+
const lines = rateLimitAutoResumeController.enabled(ctx) ? rateLimitAutoResumeController.renderPreviewLines(width) : [];
|
|
586
|
+
if (scheduleEnabled) lines.push(...scheduleController.renderPreviewLines(width));
|
|
587
|
+
if (showStatusline) lines.push(renderStatusLine(width, ctx, gitState ?? makeFallbackGitState(ctx.cwd), pi, theme));
|
|
588
|
+
return lines;
|
|
589
|
+
},
|
|
590
|
+
};
|
|
547
591
|
});
|
|
548
|
-
|
|
549
|
-
|
|
592
|
+
}, 0);
|
|
593
|
+
statusWidgetTimer.unref?.();
|
|
594
|
+
if (showStatusline && settingBoolean("replaceFooter", true, ctx.cwd)) {
|
|
595
|
+
ctx.ui.setFooter((tui, _theme, footerData) => {
|
|
596
|
+
activeTui = tui;
|
|
597
|
+
const unsubscribe = footerData.onBranchChange(() => {
|
|
598
|
+
void refreshStatusline(ctx);
|
|
599
|
+
requestRender();
|
|
600
|
+
});
|
|
601
|
+
return { dispose: unsubscribe, invalidate() {}, render: () => [] };
|
|
602
|
+
});
|
|
603
|
+
}
|
|
550
604
|
}
|
|
551
605
|
}
|
|
552
606
|
startQuestionSubscription(ctx);
|
|
@@ -568,7 +622,10 @@ export default function qol(pi: ExtensionAPI): void {
|
|
|
568
622
|
pi.on("session_shutdown", (_event, ctx) => {
|
|
569
623
|
cavemanUnsubscribe?.();
|
|
570
624
|
cavemanUnsubscribe = undefined;
|
|
625
|
+
scheduleController.clearTimers();
|
|
626
|
+
rateLimitAutoResumeController.clearTimers();
|
|
571
627
|
resetAutoRename();
|
|
628
|
+
resetBudgetGuard();
|
|
572
629
|
clearIdleCompactionTimer();
|
|
573
630
|
clearQuestionSubscribeTimer();
|
|
574
631
|
if (sessionSearchWarmupTimer) clearTimeout(sessionSearchWarmupTimer);
|
|
@@ -598,6 +655,7 @@ export default function qol(pi: ExtensionAPI): void {
|
|
|
598
655
|
pi.on("agent_start", (_event, ctx) => {
|
|
599
656
|
clearIdleCompactionTimer();
|
|
600
657
|
clearTmuxWindowMark();
|
|
658
|
+
rateLimitAutoResumeController.noteAgentStart(ctx);
|
|
601
659
|
if (ctx.hasUI) {
|
|
602
660
|
void refreshStatusline(ctx);
|
|
603
661
|
requestRender();
|
|
@@ -624,6 +682,7 @@ export default function qol(pi: ExtensionAPI): void {
|
|
|
624
682
|
}
|
|
625
683
|
});
|
|
626
684
|
pi.on("message_end", (event, ctx) => {
|
|
685
|
+
rateLimitAutoResumeController.noteMessageEnd(event, ctx);
|
|
627
686
|
if (!updateThinkingTimerEnabled(ctx)) return;
|
|
628
687
|
const message = event.message as any;
|
|
629
688
|
if (!message || message.role !== "assistant" || !Array.isArray(message.content)) return;
|
|
@@ -637,6 +696,8 @@ export default function qol(pi: ExtensionAPI): void {
|
|
|
637
696
|
void refreshStatusline(ctx);
|
|
638
697
|
requestRender();
|
|
639
698
|
}
|
|
699
|
+
rateLimitAutoResumeController.noteAgentEnd(event, ctx);
|
|
700
|
+
maybeFireBudgetGuard(ctx);
|
|
640
701
|
scheduleIdleCompaction(ctx);
|
|
641
702
|
void attemptAutoRename(ctx);
|
|
642
703
|
const text = lastAssistantTextFromAgentEnd(event, ctx);
|
|
@@ -660,10 +721,21 @@ export default function qol(pi: ExtensionAPI): void {
|
|
|
660
721
|
sendQolNotification(ctx, "ready", settingString("notification.readyMessage", "Ready for input", ctx.cwd), "info", "ready");
|
|
661
722
|
});
|
|
662
723
|
pi.on("session_compact", (_event, ctx) => {
|
|
724
|
+
// After a successful compaction usage drops below the budget, so reset the
|
|
725
|
+
// crossing key so the next threshold crossing re-fires the guard.
|
|
726
|
+
budgetGuardDriver.noteSessionCompacted();
|
|
663
727
|
if (!ctx.hasUI) return;
|
|
664
728
|
void refreshStatusline(ctx);
|
|
665
729
|
requestRender();
|
|
666
730
|
});
|
|
731
|
+
pi.on("session_tree", (_event, ctx) => {
|
|
732
|
+
currentCtx = ctx;
|
|
733
|
+
if (settingBoolean("enableScheduleCommand", true, ctx.cwd)) scheduleController.restoreFromBranch(ctx);
|
|
734
|
+
else scheduleController.clearTimers();
|
|
735
|
+
});
|
|
736
|
+
pi.on("after_provider_response", (event, ctx) => {
|
|
737
|
+
rateLimitAutoResumeController.noteProviderResponse(event, ctx);
|
|
738
|
+
});
|
|
667
739
|
pi.on("session_before_compact", (event, ctx) => handleQolCompaction(event, ctx));
|
|
668
740
|
pi.on("session_before_tree", (event, ctx) => handleQolBranchSummary(event, ctx));
|
|
669
741
|
pi.on("tool_call", async (event: any, ctx) => {
|
|
@@ -721,6 +793,14 @@ export default function qol(pi: ExtensionAPI): void {
|
|
|
721
793
|
});
|
|
722
794
|
}
|
|
723
795
|
|
|
796
|
+
if (settingBoolean("enableScheduleCommand", true)) {
|
|
797
|
+
pi.registerCommand("schedule", {
|
|
798
|
+
description: "Send a user message after a timer without invoking the model now.",
|
|
799
|
+
getArgumentCompletions: getScheduleArgumentCompletions,
|
|
800
|
+
handler: async (args, ctx) => scheduleController.handleCommand(args, ctx),
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
|
|
724
804
|
if (settingBoolean("enableContextCommand", true)) {
|
|
725
805
|
pi.registerMessageRenderer(CONTEXT_USAGE_MESSAGE_TYPE, renderQolContextUsageMessage);
|
|
726
806
|
pi.registerCommand("context", {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vanillagreen/pi-qol",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Pi quality-of-life extension: compact statusline/π prompt, reliable multiline input, styled pasted-image chips, session naming/search/context import, handoff, permission prompts, notifications, custom compaction, and a collapsed-thinking timer.",
|
|
3
|
+
"version": "1.6.0",
|
|
4
|
+
"description": "Pi quality-of-life extension: compact statusline/π prompt, reliable multiline input, styled pasted-image chips, session naming/search/context import, scheduled prompts, handoff, permission prompts, notifications, custom compaction, and a collapsed-thinking timer.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"pi-package",
|
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
],
|
|
17
17
|
"image": "https://raw.githubusercontent.com/vanillagreencom/vstack/main/pi-extensions/pi-qol/assets/settings-panel.png"
|
|
18
18
|
},
|
|
19
|
+
"scripts": {
|
|
20
|
+
"test": "bun test ./tests"
|
|
21
|
+
},
|
|
19
22
|
"vstack": {
|
|
20
23
|
"extensionManager": {
|
|
21
24
|
"displayName": "QOL",
|
|
@@ -30,10 +33,33 @@
|
|
|
30
33
|
"apply": "reload",
|
|
31
34
|
"requiresReload": true
|
|
32
35
|
},
|
|
36
|
+
{
|
|
37
|
+
"key": "glyphStyle",
|
|
38
|
+
"label": "Glyph style",
|
|
39
|
+
"description": "Glyphs for vstack-owned UI chrome. Unicode is the default; ASCII uses plain box/status/separator characters for terminal compatibility. pi-tool-renderer's global override wins when set.",
|
|
40
|
+
"type": "enum",
|
|
41
|
+
"enumValues": [
|
|
42
|
+
"unicode",
|
|
43
|
+
"ascii"
|
|
44
|
+
],
|
|
45
|
+
"default": "unicode",
|
|
46
|
+
"category": "Rendering",
|
|
47
|
+
"apply": "live"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"key": "statusline.enabled",
|
|
51
|
+
"label": "Show compact statusline",
|
|
52
|
+
"description": "Render the QOL statusline row with repo, branch, model, thinking level, and context percentage. Disable when another statusline/footer extension owns this UI.",
|
|
53
|
+
"type": "boolean",
|
|
54
|
+
"default": true,
|
|
55
|
+
"category": "Statusline",
|
|
56
|
+
"apply": "reload",
|
|
57
|
+
"requiresReload": true
|
|
58
|
+
},
|
|
33
59
|
{
|
|
34
60
|
"key": "replaceFooter",
|
|
35
61
|
"label": "Replace built-in footer",
|
|
36
|
-
"description": "Hide Pi default footer while the compact QOL statusline is
|
|
62
|
+
"description": "Hide Pi default footer while the compact QOL statusline is enabled.",
|
|
37
63
|
"type": "boolean",
|
|
38
64
|
"default": true,
|
|
39
65
|
"category": "Statusline",
|
|
@@ -318,6 +344,61 @@
|
|
|
318
344
|
"apply": "reload",
|
|
319
345
|
"requiresReload": true
|
|
320
346
|
},
|
|
347
|
+
{
|
|
348
|
+
"key": "enableScheduleCommand",
|
|
349
|
+
"label": "Enable /schedule command",
|
|
350
|
+
"description": "Register /schedule for timer-based user messages, e.g. /schedule 20m retry after rate reset. The timer runs in the extension without invoking the model immediately.",
|
|
351
|
+
"type": "boolean",
|
|
352
|
+
"default": true,
|
|
353
|
+
"category": "Session",
|
|
354
|
+
"apply": "reload",
|
|
355
|
+
"requiresReload": true
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
"key": "rateLimitAutoResume.enabled",
|
|
359
|
+
"label": "Auto-resume after rate limits",
|
|
360
|
+
"description": "When Pi detects a rate-limit stop with a reset time, schedule a follow-up user message at the reset time so the agent can continue if nothing else has continued since.",
|
|
361
|
+
"type": "boolean",
|
|
362
|
+
"default": false,
|
|
363
|
+
"category": "Rate Limit Recovery",
|
|
364
|
+
"apply": "live"
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
"key": "rateLimitAutoResume.message",
|
|
368
|
+
"label": "Auto-resume message",
|
|
369
|
+
"description": "User message sent after a detected rate limit resets.",
|
|
370
|
+
"type": "string",
|
|
371
|
+
"default": "Hit API rate limit. It has been reset. Continue",
|
|
372
|
+
"category": "Rate Limit Recovery",
|
|
373
|
+
"apply": "live"
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
"key": "rateLimitAutoResume.bufferSeconds",
|
|
377
|
+
"label": "Reset buffer seconds",
|
|
378
|
+
"description": "Seconds to wait after the reported reset time before sending the auto-resume message.",
|
|
379
|
+
"type": "number",
|
|
380
|
+
"default": 10,
|
|
381
|
+
"category": "Rate Limit Recovery",
|
|
382
|
+
"apply": "live"
|
|
383
|
+
},
|
|
384
|
+
{
|
|
385
|
+
"key": "rateLimitAutoResume.maxDelayMinutes",
|
|
386
|
+
"label": "Maximum auto-resume delay",
|
|
387
|
+
"description": "Ignore detected reset times farther away than this many minutes, to avoid stale or malformed schedules.",
|
|
388
|
+
"type": "number",
|
|
389
|
+
"default": 1440,
|
|
390
|
+
"category": "Rate Limit Recovery",
|
|
391
|
+
"apply": "live"
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
"key": "rateLimitAutoResume.notify",
|
|
395
|
+
"label": "Notify on auto-resume",
|
|
396
|
+
"description": "Show Pi UI notices when rate-limit auto-resume is scheduled, sent, or fails.",
|
|
397
|
+
"type": "boolean",
|
|
398
|
+
"default": true,
|
|
399
|
+
"category": "Rate Limit Recovery",
|
|
400
|
+
"apply": "live"
|
|
401
|
+
},
|
|
321
402
|
{
|
|
322
403
|
"key": "handoffReviewPrompt",
|
|
323
404
|
"label": "Review handoff prompt",
|
|
@@ -654,9 +735,9 @@
|
|
|
654
735
|
{
|
|
655
736
|
"key": "compaction.model",
|
|
656
737
|
"label": "Compaction model",
|
|
657
|
-
"description": "Summarizer model as provider/model, a bare model id, or current. Default
|
|
738
|
+
"description": "Summarizer model as provider/model, a bare model id, or current. Default uses Pi's active model.",
|
|
658
739
|
"type": "string",
|
|
659
|
-
"default": "
|
|
740
|
+
"default": "current",
|
|
660
741
|
"category": "Compaction",
|
|
661
742
|
"apply": "live"
|
|
662
743
|
},
|
|
@@ -782,6 +863,60 @@
|
|
|
782
863
|
"category": "Compaction Threshold",
|
|
783
864
|
"apply": "live"
|
|
784
865
|
},
|
|
866
|
+
{
|
|
867
|
+
"key": "compaction.budgetGuardEnabled",
|
|
868
|
+
"label": "Long-session budget guard",
|
|
869
|
+
"description": "Trigger compaction on agent_end (no idle wait) when context usage crosses budgetPercent or budgetTokens. Designed for autonomous/Flightdeck runs so transcript growth cannot silently hit provider/buffer limits. Fires once per threshold crossing, not in a loop.",
|
|
870
|
+
"type": "boolean",
|
|
871
|
+
"default": true,
|
|
872
|
+
"category": "Compaction Budget Guard",
|
|
873
|
+
"apply": "live"
|
|
874
|
+
},
|
|
875
|
+
{
|
|
876
|
+
"key": "compaction.budgetPercent",
|
|
877
|
+
"label": "Budget guard percent",
|
|
878
|
+
"description": "Context-window percentage that fires the budget guard at agent_end. Set -1 to disable percent-based firing and rely on absolute tokens instead.",
|
|
879
|
+
"type": "number",
|
|
880
|
+
"default": 85,
|
|
881
|
+
"category": "Compaction Budget Guard",
|
|
882
|
+
"apply": "live"
|
|
883
|
+
},
|
|
884
|
+
{
|
|
885
|
+
"key": "compaction.budgetTokens",
|
|
886
|
+
"label": "Budget guard token limit",
|
|
887
|
+
"description": "Absolute token count above which the budget guard fires at agent_end. Set -1 to use only budgetPercent.",
|
|
888
|
+
"type": "number",
|
|
889
|
+
"default": -1,
|
|
890
|
+
"category": "Compaction Budget Guard",
|
|
891
|
+
"apply": "live"
|
|
892
|
+
},
|
|
893
|
+
{
|
|
894
|
+
"key": "compaction.maxInputChars",
|
|
895
|
+
"label": "Chunked compaction input cap",
|
|
896
|
+
"description": "Maximum serialized characters sent to the summarizer in a single request. Inputs larger than this are split into chunks, summarized chunk-by-chunk, then merged via a summary-of-summaries pass so the compaction request payload itself cannot blow past provider buffer limits. Set 0 to disable chunking.",
|
|
897
|
+
"type": "number",
|
|
898
|
+
"default": 240000,
|
|
899
|
+
"category": "Compaction Budget Guard",
|
|
900
|
+
"apply": "live"
|
|
901
|
+
},
|
|
902
|
+
{
|
|
903
|
+
"key": "compaction.handoffArtifactEnabled",
|
|
904
|
+
"label": "Write pre-compaction handoff artifact",
|
|
905
|
+
"description": "Before compaction, write a JSON handoff artifact under ~/.pi/agent/vstack/sessions/<session>/pi-qol/handoff/ containing the previous summary, last task state, and recently-referenced file/artifact paths. Always writes a stamped file and a latest.json pointer. Runs whenever the QOL bounded compaction handler runs — that is, on every budget-guard-triggered compaction (regardless of compaction.customEnabled), and on manual/idle compactions only when compaction.customEnabled is on. Write failures surface a QOL warning and a handoffArtifactError field in the compaction details.",
|
|
906
|
+
"type": "boolean",
|
|
907
|
+
"default": true,
|
|
908
|
+
"category": "Compaction Budget Guard",
|
|
909
|
+
"apply": "live"
|
|
910
|
+
},
|
|
911
|
+
{
|
|
912
|
+
"key": "compaction.transcriptRiskWarnChars",
|
|
913
|
+
"label": "Transcript-risk warn budget (chars)",
|
|
914
|
+
"description": "Show a transcript-risk warning in /context when the serialized payload of the messages-to-send exceeds this many characters, even if the token count is still below the model context window. Set 0 to disable the warning.",
|
|
915
|
+
"type": "number",
|
|
916
|
+
"default": 600000,
|
|
917
|
+
"category": "Compaction Budget Guard",
|
|
918
|
+
"apply": "live"
|
|
919
|
+
},
|
|
785
920
|
{
|
|
786
921
|
"key": "thinkingLabel.text",
|
|
787
922
|
"label": "Hidden thinking label",
|