@vanillagreen/pi-qol 1.6.0 → 1.7.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
CHANGED
|
@@ -15,7 +15,7 @@ Quality-of-life extension for Pi: compact statusline, multiline input, session n
|
|
|
15
15
|
- `/search` browses previous sessions with snippet previews; the configured shortcut opens it instantly.
|
|
16
16
|
- `/context` shows a Claude-style context-window breakdown.
|
|
17
17
|
- `/handoff <goal>` drafts a focused prompt for a new session.
|
|
18
|
-
- `/schedule 20m <message>` sends a delayed prompt without invoking the model until the timer fires.
|
|
18
|
+
- `/schedule 20m <message>` or `/schedule 1h45m <message>` sends a delayed prompt without invoking the model until the timer fires.
|
|
19
19
|
- Optional rate-limit auto-resume sends a configurable continuation after reset.
|
|
20
20
|
- Permission gate prompts before risky `bash` commands. Default match: `rm -Rf`.
|
|
21
21
|
- Notifications for ready, questions, blocked states, and task completion.
|
|
@@ -53,11 +53,11 @@ Restart Pi after installation.
|
|
|
53
53
|
| `/search [query]` | Open previous-session search. |
|
|
54
54
|
| `/search:refresh` | Refresh the session search cache. |
|
|
55
55
|
| `/handoff <goal>` | Draft a handoff prompt for a new session. |
|
|
56
|
-
| `/schedule <delay> <message>` | Send a user message after a timer without invoking the model now. Example: `/schedule
|
|
56
|
+
| `/schedule <delay> <message>` | Send a user message after a timer without invoking the model now. Example: `/schedule 1h45m retry the previous request`. |
|
|
57
57
|
|
|
58
58
|
Arguments support autocomplete.
|
|
59
59
|
|
|
60
|
-
`/schedule` accepts `ms`, `s`, `m`, `h`, and `d` units; bare numbers mean minutes. Pending prompts render above the statusline like steering/follow-up previews until they are sent or cancelled. Manage pending prompts with `/schedule list` and `/schedule cancel <id|all>`. Schedules are stored in the Pi session and re-armed on reload/resume; if Pi is not running at the due time, an overdue prompt sends when that session is next loaded.
|
|
60
|
+
`/schedule` accepts `ms`, `s`, `m`, `h`, and `d` units; bare numbers mean minutes. Compact composite durations are accepted in largest-to-smallest order, like `1h45m`, `45m10s`, or `1h45m30s`. Pending prompts render above the statusline like steering/follow-up previews until they are sent or cancelled. Manage pending prompts with `/schedule list` and `/schedule cancel <id|all>`. Schedules are stored in the Pi session and re-armed on reload/resume; if Pi is not running at the due time, an overdue prompt sends when that session is next loaded.
|
|
61
61
|
|
|
62
62
|
## Settings
|
|
63
63
|
|
|
@@ -138,7 +138,7 @@ Master toggle: **Enable notifications**.
|
|
|
138
138
|
|
|
139
139
|
Triggers (notify when): ready, direction needed, question popups, all tasks complete, critical/blocked.
|
|
140
140
|
|
|
141
|
-
Channels: terminal bell, native terminal notifications (OSC 777/99 or Windows toast), tmux `display-message`, tmux window marking, OSC passthrough, and an optional in-Pi UI notice.
|
|
141
|
+
Channels: terminal bell, **Mute bell sound**, native terminal notifications (OSC 777/99 or Windows toast), tmux `display-message`, tmux window marking, OSC passthrough, and an optional in-Pi UI notice.
|
|
142
142
|
|
|
143
143
|
Tuning: cooldown seconds, title, ready message, body length, tmux durations.
|
|
144
144
|
|
|
@@ -146,9 +146,10 @@ Notes:
|
|
|
146
146
|
|
|
147
147
|
- **Terminal notification protocol** picks between OSC 99 (Kitty) and OSC 777 automatically.
|
|
148
148
|
- **Bell when tmux window active** is off so you don't get bells while looking at Pi.
|
|
149
|
+
- **Mute bell sound** keeps notification routing enabled but suppresses QOL-emitted BEL bytes and uses ST terminators for OSC 777/99 where supported. Terminals or operating systems may still play their own sound for native notifications outside QOL control.
|
|
149
150
|
- **tmux native via client TTY** sends OSC notifications to attached tmux clients so notifications still appear when the Pi window is inactive.
|
|
150
151
|
|
|
151
|
-
Use `/qol notify-test` to verify your terminal/tmux setup.
|
|
152
|
+
Use `/qol notify-test` to verify your terminal/tmux setup, including silent behavior with **Mute bell sound** enabled.
|
|
152
153
|
|
|
153
154
|
### Permission gate
|
|
154
155
|
|
|
@@ -192,6 +193,8 @@ For autonomous/Flightdeck-style runs the agent never goes idle, so idle compacti
|
|
|
192
193
|
|
|
193
194
|
When the budget guard fires it injects a sentinel into the compaction request so the QOL bounded handler always runs — chunked summarizer + handoff artifact — even if **Custom compaction summaries** is off. Manual compactions (`/tree`, idle compaction, user-triggered) still only use the QOL handler when **Custom compaction summaries** is on; otherwise they fall through to Pi's default compaction with no handoff artifact and no chunking. If you want every compaction to use the QOL bounded path, turn **Custom compaction summaries** on.
|
|
194
195
|
|
|
196
|
+
While budget-guard compaction is running, QOL keeps a persistent status line above the prompt (and in the normal status footer when the compact statusline is disabled). After Pi prints the compacted-summary block, the line changes to `QOL budget guard finalizing compaction…` until `ctx.compact()` reports completion, so long reload/finalization gaps do not look frozen.
|
|
197
|
+
|
|
195
198
|
Recommended values for autonomous/Flightdeck runs:
|
|
196
199
|
|
|
197
200
|
- Keep **Long-session budget guard** on. Lower **Budget guard percent** to `75` if your provider buffers are tight.
|
|
@@ -17,6 +17,7 @@ export interface GuardDispatchInput {
|
|
|
17
17
|
trigger: BudgetTrigger | undefined;
|
|
18
18
|
compact: ((options: GuardCompactOptions) => void) | undefined;
|
|
19
19
|
notify: (message: string, level: GuardLevel) => void;
|
|
20
|
+
onStatus?: (message: string | undefined) => void;
|
|
20
21
|
staleCtx?: () => boolean;
|
|
21
22
|
}
|
|
22
23
|
|
|
@@ -70,6 +71,7 @@ export class BudgetGuardDriver {
|
|
|
70
71
|
return { kind: "no-compact-fn" };
|
|
71
72
|
}
|
|
72
73
|
input.notify(`QOL budget guard starting compaction: ${trigger.reason}`, "info");
|
|
74
|
+
input.onStatus?.(`QOL budget guard compacting session: ${trigger.reason}`);
|
|
73
75
|
this.inFlight = true;
|
|
74
76
|
this.lastKey = trigger.key;
|
|
75
77
|
const instructions = `${QOL_BUDGET_GUARD_SENTINEL} QOL budget guard triggered at agent_end because ${trigger.reason}. Bound the summary input, preserve current task state, decisions, files, blockers, and next steps.`;
|
|
@@ -78,6 +80,7 @@ export class BudgetGuardDriver {
|
|
|
78
80
|
customInstructions: instructions,
|
|
79
81
|
onComplete: () => {
|
|
80
82
|
this.inFlight = false;
|
|
83
|
+
input.onStatus?.(undefined);
|
|
81
84
|
input.notify("QOL budget guard compaction completed.", "info");
|
|
82
85
|
},
|
|
83
86
|
onError: (error: Error) => {
|
|
@@ -85,6 +88,7 @@ export class BudgetGuardDriver {
|
|
|
85
88
|
// Allow the next agent_end to retry rather than poisoning the
|
|
86
89
|
// crossing key after a transient failure.
|
|
87
90
|
this.lastKey = undefined;
|
|
91
|
+
input.onStatus?.(undefined);
|
|
88
92
|
input.notify(`QOL budget guard compaction failed: ${error.message}`, "error");
|
|
89
93
|
},
|
|
90
94
|
});
|
|
@@ -92,6 +96,7 @@ export class BudgetGuardDriver {
|
|
|
92
96
|
} catch (error) {
|
|
93
97
|
this.inFlight = false;
|
|
94
98
|
this.lastKey = undefined;
|
|
99
|
+
input.onStatus?.(undefined);
|
|
95
100
|
const message = error instanceof Error ? error.message : String(error);
|
|
96
101
|
input.notify(`QOL budget guard compaction failed to start: ${message}`, "error");
|
|
97
102
|
return { kind: "dispatch-threw", error: message, reason: trigger.reason };
|
|
@@ -52,6 +52,19 @@ function tmuxPassthrough(sequence: string): string {
|
|
|
52
52
|
return `\x1bPtmux;${sequence.replace(/\x1b/g, "\x1b\x1b")}\x1b\\`;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
export function terminalBellSequence(muteBellSound: boolean): string | undefined {
|
|
56
|
+
return muteBellSound ? undefined : "\x07";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function osc777NotificationSequence(title: string, body: string, muteBellSound = false): string {
|
|
60
|
+
const terminator = muteBellSound ? "\x1b\\" : "\x07";
|
|
61
|
+
return `\x1b]777;notify;${title};${body}${terminator}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function notificationBellMuted(cwd?: string): boolean {
|
|
65
|
+
return settingBoolean("notification.muteBellSound", false, cwd);
|
|
66
|
+
}
|
|
67
|
+
|
|
55
68
|
function sourcePaneTty(): string | undefined {
|
|
56
69
|
try {
|
|
57
70
|
if (!process.env.TMUX_PANE) return undefined;
|
|
@@ -127,21 +140,22 @@ function writeTerminalSequence(sequence: string, cwd?: string): void {
|
|
|
127
140
|
if (process.env.TMUX && settingBoolean("notification.tmuxNativeClientTty", true, cwd)) {
|
|
128
141
|
// Inactive tmux windows do not forward arbitrary OSC output to the terminal.
|
|
129
142
|
// Send native terminal notifications straight to attached tmux client TTYs,
|
|
130
|
-
// while
|
|
143
|
+
// while the explicit terminal bell still goes through the source pane when unmuted.
|
|
131
144
|
if (writeRawToPaths(tmuxClientTtys(), sequence)) return;
|
|
132
145
|
}
|
|
133
146
|
const output = process.env.TMUX && settingBoolean("notification.tmuxPassthrough", true, cwd) ? tmuxPassthrough(sequence) : sequence;
|
|
134
147
|
writeToTerminal(output);
|
|
135
148
|
}
|
|
136
149
|
|
|
137
|
-
function writeTerminalBell(): void {
|
|
150
|
+
function writeTerminalBell(cwd?: string): void {
|
|
138
151
|
// Match Claude-style hooks: resolve the source pane TTY and write raw BEL there.
|
|
139
152
|
// This lets tmux set window_bell_flag for the correct source window.
|
|
140
|
-
|
|
153
|
+
const sequence = terminalBellSequence(notificationBellMuted(cwd));
|
|
154
|
+
if (sequence) writeToTerminal(sequence);
|
|
141
155
|
}
|
|
142
156
|
|
|
143
157
|
function notifyOSC777(title: string, body: string, cwd?: string): void {
|
|
144
|
-
writeTerminalSequence(
|
|
158
|
+
writeTerminalSequence(osc777NotificationSequence(title, body, notificationBellMuted(cwd)), cwd);
|
|
145
159
|
}
|
|
146
160
|
|
|
147
161
|
function notifyOSC99(title: string, body: string, cwd?: string): void {
|
|
@@ -242,7 +256,7 @@ export function sendQolNotification(ctx: ExtensionContext | undefined, kind: Qol
|
|
|
242
256
|
const title = sanitizeNotificationPart(settingString("notification.title", DEFAULT_NOTIFICATION_TITLE, cwd), 80) || DEFAULT_NOTIFICATION_TITLE;
|
|
243
257
|
const text = sanitizeNotificationPart(body, Math.max(40, Math.floor(settingNumber("notification.bodyMaxChars", DEFAULT_NOTIFICATION_BODY_MAX_CHARS, cwd))));
|
|
244
258
|
const tmuxWindowActive = sourceTmuxWindowActive();
|
|
245
|
-
if (settingBoolean("notification.bell", true, cwd) && (!tmuxWindowActive || settingBoolean("notification.bellWhenActive", false, cwd))) writeTerminalBell();
|
|
259
|
+
if (settingBoolean("notification.bell", true, cwd) && (!tmuxWindowActive || settingBoolean("notification.bellWhenActive", false, cwd))) writeTerminalBell(cwd);
|
|
246
260
|
if (settingBoolean("notification.native", true, cwd)) notifyNativeTerminal(title, text, cwd);
|
|
247
261
|
if (!tmuxWindowActive) markTmuxWindow(cwd);
|
|
248
262
|
if (settingBoolean("notification.tmux", false, cwd)) notifyTmux(title, text, cwd);
|
|
@@ -56,6 +56,7 @@ const SCHEDULE_COMPLETIONS: AutocompleteItem[] = [
|
|
|
56
56
|
{ value: "list", label: "list", description: "Show pending scheduled messages" },
|
|
57
57
|
{ value: "cancel ", label: "cancel <id|all>", description: "Cancel one scheduled message or all pending messages" },
|
|
58
58
|
{ value: "20m ", label: "20m <message>", description: "Send a message after 20 minutes" },
|
|
59
|
+
{ value: "1h30m ", label: "1h30m <message>", description: "Send a message after 1 hour 30 minutes" },
|
|
59
60
|
{ value: "1h ", label: "1h <message>", description: "Send a message after 1 hour" },
|
|
60
61
|
];
|
|
61
62
|
|
|
@@ -66,25 +67,47 @@ export function getScheduleArgumentCompletions(prefix: string): AutocompleteItem
|
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
export function parseDurationMs(input: string): number | undefined {
|
|
69
|
-
const
|
|
70
|
-
if (!
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
70
|
+
const text = input.trim().toLowerCase();
|
|
71
|
+
if (!text) return undefined;
|
|
72
|
+
if (/^\d+(?:\.\d+)?$/.test(text)) return clampScheduleDelay(Number(text) * 60 * 1000);
|
|
73
|
+
|
|
74
|
+
let totalMs = 0;
|
|
75
|
+
let matched = false;
|
|
76
|
+
let previousRank = Number.POSITIVE_INFINITY;
|
|
77
|
+
const seenUnits = new Set<string>();
|
|
78
|
+
const re = /(\d+(?:\.\d+)?)(ms|seconds?|secs?|s|minutes?|mins?|min|m|hours?|hrs?|hr|h|days?|d)/gy;
|
|
79
|
+
|
|
80
|
+
while (re.lastIndex < text.length) {
|
|
81
|
+
const match = re.exec(text);
|
|
82
|
+
if (!match) return undefined;
|
|
83
|
+
const amount = Number(match[1]);
|
|
84
|
+
const unit = scheduleUnit(match[2]);
|
|
85
|
+
if (!Number.isFinite(amount) || amount < 0 || !unit) return undefined;
|
|
86
|
+
if (seenUnits.has(unit.name) || unit.rank >= previousRank) return undefined;
|
|
87
|
+
seenUnits.add(unit.name);
|
|
88
|
+
previousRank = unit.rank;
|
|
89
|
+
totalMs += amount * unit.multiplier;
|
|
90
|
+
matched = true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const delayMs = Math.round(totalMs);
|
|
94
|
+
return matched ? clampScheduleDelay(delayMs) : undefined;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function clampScheduleDelay(delayMs: number): number | undefined {
|
|
84
98
|
if (!Number.isFinite(delayMs) || delayMs <= 0 || delayMs > MAX_SCHEDULE_DELAY_MS) return undefined;
|
|
85
99
|
return delayMs;
|
|
86
100
|
}
|
|
87
101
|
|
|
102
|
+
function scheduleUnit(unit: string): { multiplier: number; name: string; rank: number } | undefined {
|
|
103
|
+
if (unit === "ms") return { multiplier: 1, name: "ms", rank: 0 };
|
|
104
|
+
if (["s", "sec", "secs", "second", "seconds"].includes(unit)) return { multiplier: 1000, name: "s", rank: 1 };
|
|
105
|
+
if (["m", "min", "mins", "minute", "minutes"].includes(unit)) return { multiplier: 60 * 1000, name: "m", rank: 2 };
|
|
106
|
+
if (["h", "hr", "hrs", "hour", "hours"].includes(unit)) return { multiplier: 60 * 60 * 1000, name: "h", rank: 3 };
|
|
107
|
+
if (["d", "day", "days"].includes(unit)) return { multiplier: 24 * 60 * 60 * 1000, name: "d", rank: 4 };
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
|
|
88
111
|
export function parseScheduleCommandArgs(args: string): ParsedScheduleCommand {
|
|
89
112
|
const trimmed = args.trim();
|
|
90
113
|
if (!trimmed) return { kind: "list" };
|
|
@@ -131,7 +154,8 @@ function usageText(): string {
|
|
|
131
154
|
return [
|
|
132
155
|
"Usage: /schedule <duration> <message>",
|
|
133
156
|
"Examples: /schedule 20m retry the previous request",
|
|
134
|
-
" /schedule
|
|
157
|
+
" /schedule 1h30m retry after the rate limit reset",
|
|
158
|
+
"Durations: 20, 20m, 90s, 500ms, 1h45m30s (bare numbers mean minutes)",
|
|
135
159
|
"Manage: /schedule list, /schedule cancel <id|all>",
|
|
136
160
|
].join("\n");
|
|
137
161
|
}
|
|
@@ -39,7 +39,7 @@ export function statusMessage(ctx: ExtensionContext): string {
|
|
|
39
39
|
`Custom compaction: ${settingBoolean("compaction.customEnabled", false, ctx.cwd) ? `enabled (${settingString("compaction.model", DEFAULT_COMPACTION_MODEL, ctx.cwd)}, ${compactionProfile(ctx.cwd)})` : "disabled (Pi default)"}`,
|
|
40
40
|
`Idle compaction: ${settingBoolean("compaction.idleEnabled", false, ctx.cwd) ? `enabled after ${Math.max(1, Math.floor(settingNumber("compaction.idleTimeoutSeconds", DEFAULT_IDLE_COMPACTION_SECONDS, ctx.cwd)))}s idle` : "disabled"}`,
|
|
41
41
|
`Branch summary override: ${settingBoolean("compaction.branchSummaryEnabled", false, ctx.cwd) ? "enabled" : "disabled"}`,
|
|
42
|
-
`Notifications: ${settingBoolean("notification.enabled", true, ctx.cwd) ? `enabled (bell=${settingBoolean("notification.bell", true, ctx.cwd)}, native=${settingBoolean("notification.native", true, ctx.cwd)}, tmuxClientTty=${settingBoolean("notification.tmuxNativeClientTty", true, ctx.cwd)}, tmuxMessage=${settingBoolean("notification.tmux", false, ctx.cwd)})` : "disabled"}`,
|
|
42
|
+
`Notifications: ${settingBoolean("notification.enabled", true, ctx.cwd) ? `enabled (bell=${settingBoolean("notification.bell", true, ctx.cwd)}, muteBell=${settingBoolean("notification.muteBellSound", false, ctx.cwd)}, native=${settingBoolean("notification.native", true, ctx.cwd)}, tmuxClientTty=${settingBoolean("notification.tmuxNativeClientTty", true, ctx.cwd)}, tmuxMessage=${settingBoolean("notification.tmux", false, ctx.cwd)})` : "disabled"}`,
|
|
43
43
|
`Permission gate: ${settingBoolean("permissionGate.enabled", false, ctx.cwd) ? `enabled (${permissionGateCommands(ctx.cwd).join(", ") || "none configured"}; preview ${boundedSettingNumber("permissionGate.previewLines", DEFAULT_PERMISSION_GATE_PREVIEW_LINES, 4, 40, ctx.cwd)} lines/${boundedSettingNumber("permissionGate.previewChars", DEFAULT_PERMISSION_GATE_PREVIEW_CHARS, 200, 5000, ctx.cwd)} chars)` : "disabled"}`,
|
|
44
44
|
`Thinking timer: ${settingBoolean("thinkingTimer.enabled", true, ctx.cwd) ? "enabled" : "disabled"}`,
|
|
45
45
|
"If shift+enter still submits, configure your terminal/tmux to send a distinct shift+enter sequence or use the fallback key.",
|
package/extensions/qol.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionCommandContext, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import type
|
|
2
|
+
import { truncateToWidth, 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
5
|
import { BudgetGuardDriver } from "./qol/budget-guard-runtime.js";
|
|
6
|
+
import { ansiGreen } from "./qol/ansi.js";
|
|
6
7
|
import {
|
|
7
8
|
budgetGuardTrigger,
|
|
8
9
|
compactionTriggerReason,
|
|
@@ -262,9 +263,19 @@ export default function qol(pi: ExtensionAPI): void {
|
|
|
262
263
|
};
|
|
263
264
|
|
|
264
265
|
const budgetGuardDriver = new BudgetGuardDriver();
|
|
266
|
+
let budgetGuardStatus: string | undefined;
|
|
267
|
+
|
|
268
|
+
const setBudgetGuardStatus = (ctx: ExtensionContext, message: string | undefined) => {
|
|
269
|
+
budgetGuardStatus = message;
|
|
270
|
+
if (ctx.hasUI) {
|
|
271
|
+
ctx.ui.setStatus("qol-budget-guard", message ? ctx.ui.theme.fg("accent", message) : undefined);
|
|
272
|
+
requestRender();
|
|
273
|
+
}
|
|
274
|
+
};
|
|
265
275
|
|
|
266
276
|
const resetBudgetGuard = () => {
|
|
267
277
|
budgetGuardDriver.reset();
|
|
278
|
+
budgetGuardStatus = undefined;
|
|
268
279
|
};
|
|
269
280
|
|
|
270
281
|
const maybeFireBudgetGuard = (ctx: ExtensionContext) => {
|
|
@@ -286,6 +297,7 @@ export default function qol(pi: ExtensionAPI): void {
|
|
|
286
297
|
budgetGuardDriver.dispatch({
|
|
287
298
|
compact: typeof ctx.compact === "function" ? ctx.compact.bind(ctx) : undefined,
|
|
288
299
|
notify: notifySafely,
|
|
300
|
+
onStatus: (message) => setBudgetGuardStatus(ctx, message),
|
|
289
301
|
trigger,
|
|
290
302
|
});
|
|
291
303
|
};
|
|
@@ -584,6 +596,7 @@ export default function qol(pi: ExtensionAPI): void {
|
|
|
584
596
|
render(width: number): string[] {
|
|
585
597
|
const lines = rateLimitAutoResumeController.enabled(ctx) ? rateLimitAutoResumeController.renderPreviewLines(width) : [];
|
|
586
598
|
if (scheduleEnabled) lines.push(...scheduleController.renderPreviewLines(width));
|
|
599
|
+
if (budgetGuardStatus) lines.push(truncateToWidth(ansiGreen(`┃ ${budgetGuardStatus}`), width, ""));
|
|
587
600
|
if (showStatusline) lines.push(renderStatusLine(width, ctx, gitState ?? makeFallbackGitState(ctx.cwd), pi, theme));
|
|
588
601
|
return lines;
|
|
589
602
|
},
|
|
@@ -635,10 +648,12 @@ export default function qol(pi: ExtensionAPI): void {
|
|
|
635
648
|
questionUnsubscribe?.();
|
|
636
649
|
questionUnsubscribe = undefined;
|
|
637
650
|
currentCtx = undefined;
|
|
651
|
+
budgetGuardStatus = undefined;
|
|
638
652
|
const host = globalThis as unknown as Record<PropertyKey, unknown>;
|
|
639
653
|
if (host[QOL_NOTIFICATION_SERVICE_SYMBOL] === notificationService) delete host[QOL_NOTIFICATION_SERVICE_SYMBOL];
|
|
640
654
|
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
641
655
|
ctx.ui.setStatus(SESSION_SEARCH_STATUS_KEY, undefined);
|
|
656
|
+
ctx.ui.setStatus("qol-budget-guard", undefined);
|
|
642
657
|
restorePendingQueueThemePatch(ctx);
|
|
643
658
|
resetStatuslineUi(ctx);
|
|
644
659
|
ctx.ui.setEditorComponent(undefined);
|
|
@@ -721,6 +736,7 @@ export default function qol(pi: ExtensionAPI): void {
|
|
|
721
736
|
sendQolNotification(ctx, "ready", settingString("notification.readyMessage", "Ready for input", ctx.cwd), "info", "ready");
|
|
722
737
|
});
|
|
723
738
|
pi.on("session_compact", (_event, ctx) => {
|
|
739
|
+
if (budgetGuardStatus) setBudgetGuardStatus(ctx, "QOL budget guard finalizing compaction…");
|
|
724
740
|
// After a successful compaction usage drops below the budget, so reset the
|
|
725
741
|
// crossing key so the next threshold crossing re-fires the guard.
|
|
726
742
|
budgetGuardDriver.noteSessionCompacted();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vanillagreen/pi-qol",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
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": [
|
|
@@ -582,6 +582,15 @@
|
|
|
582
582
|
"category": "Notifications Channels",
|
|
583
583
|
"apply": "live"
|
|
584
584
|
},
|
|
585
|
+
{
|
|
586
|
+
"key": "notification.muteBellSound",
|
|
587
|
+
"label": "Mute bell sound",
|
|
588
|
+
"description": "Keep notifications enabled while suppressing QOL-emitted BEL bytes; OSC terminal notifications use ST terminators where supported.",
|
|
589
|
+
"type": "boolean",
|
|
590
|
+
"default": false,
|
|
591
|
+
"category": "Notifications Channels",
|
|
592
|
+
"apply": "live"
|
|
593
|
+
},
|
|
585
594
|
{
|
|
586
595
|
"key": "notification.bellWhenActive",
|
|
587
596
|
"label": "Bell when tmux window active",
|