@vanillagreen/pi-qol 2.0.1 → 2.1.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/README.md +2 -1
- package/extensions/qol/bridges.ts +19 -0
- package/extensions/qol/compaction.ts +18 -2
- package/extensions/qol/constants.ts +1 -0
- package/extensions/qol/settings.ts +33 -5
- package/extensions/qol/status-message.ts +20 -2
- package/extensions/qol/statusline.ts +18 -4
- package/extensions/qol.ts +2 -1
- package/package.json +12 -3
package/README.md
CHANGED
|
@@ -36,7 +36,7 @@ The settings editor writes project values to `.pi/settings.json`. The default us
|
|
|
36
36
|
Open `/extensions:settings`; settings appear under the **QOL** tab. Project settings in `.pi/settings.json` apply only after Pi marks the workspace trusted. `glyphStyle` picks `unicode` or `ascii` symbols, and `@vanillagreen/pi-tool-renderer`'s `globalGlyphStyleOverride` wins when set.
|
|
37
37
|
|
|
38
38
|
- `enabled`: package toggle for everything below.
|
|
39
|
-
- Statusline and editor: `statusline.enabled`, `replaceFooter`, `compactPrompt`, `showSessionNameTitle`, `showSessionNameWindow`, `inputBottomPaddingLines`, `gitRefreshTimeoutMs`, `showDirtyMarker`, `newlineOnShiftEnter`, `newlineFallbackKey`, `pendingQueue.asciiGreen`, `showImageChips`, `showAttachmentCountInStatus`.
|
|
39
|
+
- Statusline and editor: `statusline.enabled`, `statusline.showAccount`, `replaceFooter`, `compactPrompt`, `showSessionNameTitle`, `showSessionNameWindow`, `inputBottomPaddingLines`, `gitRefreshTimeoutMs`, `showDirtyMarker`, `newlineOnShiftEnter`, `newlineFallbackKey`, `pendingQueue.asciiGreen`, `showImageChips`, `showAttachmentCountInStatus`.
|
|
40
40
|
- Commands: `enableSessionNameCommand`, `enableHandoffCommand`, `enableScheduleCommand`, `enableContextCommand`, `handoffReviewPrompt`.
|
|
41
41
|
- Session naming: `sessionAutoRename.*` (model, fallback model, deterministic fallback, prefix, prompt, limits, notify, debug).
|
|
42
42
|
- Session search: `sessionSearch.*` (shortcut, result and row limits, snippets, overlay width, cache TTL, summary model and limits).
|
|
@@ -44,6 +44,7 @@ Open `/extensions:settings`; settings appear under the **QOL** tab. Project sett
|
|
|
44
44
|
- Permission gate: `permissionGate.enabled`, `permissionGate.commands` (comma-separated literal fragments or `/regex/flags`), `permissionGate.previewLines`, `permissionGate.previewChars`.
|
|
45
45
|
- Notifications: `notification.*` (triggers, channels, tmux options, protocol, cooldown, title and body).
|
|
46
46
|
- Compaction and budget guard: `compaction.*` (custom summaries, model, profile, remote endpoint, branch summaries, idle trigger, budget guard thresholds, chunk input cap, handoff artifact, transcript-risk budget).
|
|
47
|
+
- Pi's own `compaction.enabled` (the top-level `compaction` object in the same settings files, not a QOL key) turns off every automatic compaction: when it is `false`, neither the budget guard nor the idle trigger starts one, and `/qol` reports both as disabled by that key. A manual `/compact` still runs. While it is `true` or absent, both keep the defaults and thresholds above: the budget guard on at `compaction.budgetPercent` 85, the idle trigger off until `compaction.idleEnabled` is set.
|
|
47
48
|
- Thinking: `thinkingLabel.text`, `thinkingTimer.enabled`, `workingIndicator.mode` (`static` if the animated indicator flashes).
|
|
48
49
|
|
|
49
50
|
Maintainer notes are in [DEVELOPMENT.md](DEVELOPMENT.md).
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CAVEMAN_BRIDGE_SYMBOL,
|
|
3
|
+
CLAUDE_BILLING_IDENTITY_SYMBOL,
|
|
3
4
|
PI_AGENTS_STATUSLINE_SYMBOL,
|
|
4
5
|
QUESTION_SERVICE_SYMBOL,
|
|
5
6
|
KENDEX_MODAL_LOCK_SYMBOL,
|
|
@@ -21,6 +22,18 @@ export interface PiAgentsStatuslineBridge {
|
|
|
21
22
|
getCurrentSubagent(cwd?: string): { name: string; color?: string } | undefined;
|
|
22
23
|
}
|
|
23
24
|
|
|
25
|
+
/** The Claude bridge's published account surface. It answers with a login
|
|
26
|
+
* email only when the SDK confirmed one for the child that ran the request:
|
|
27
|
+
* an API key or a Bedrock, Vertex, Foundry, Anthropic-AWS or Mantle backend
|
|
28
|
+
* authenticates as something no config directory names, and a companion
|
|
29
|
+
* account router can rotate the profile per request without the environment
|
|
30
|
+
* changing. Reading a config directory here instead would name the wrong
|
|
31
|
+
* account in every one of those cases. */
|
|
32
|
+
export interface ClaudeBillingIdentityBridge {
|
|
33
|
+
version: 1;
|
|
34
|
+
currentLoginEmail(sessionId: string | undefined): string | undefined;
|
|
35
|
+
}
|
|
36
|
+
|
|
24
37
|
export interface QuestionRequestLike {
|
|
25
38
|
header?: string;
|
|
26
39
|
question?: string;
|
|
@@ -47,6 +60,12 @@ export function readCavemanBridge(): CavemanBridge | undefined {
|
|
|
47
60
|
return value && typeof value === "object" ? (value as CavemanBridge) : undefined;
|
|
48
61
|
}
|
|
49
62
|
|
|
63
|
+
export function readClaudeBillingIdentityBridge(): ClaudeBillingIdentityBridge | undefined {
|
|
64
|
+
const host = globalThis as unknown as Record<PropertyKey, unknown>;
|
|
65
|
+
const value = host[CLAUDE_BILLING_IDENTITY_SYMBOL] as ClaudeBillingIdentityBridge | undefined;
|
|
66
|
+
return value?.version === 1 && typeof value.currentLoginEmail === "function" ? value : undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
50
69
|
export function readPiAgentsStatuslineBridge(): PiAgentsStatuslineBridge | undefined {
|
|
51
70
|
const host = globalThis as unknown as Record<PropertyKey, unknown>;
|
|
52
71
|
const value = host[PI_AGENTS_STATUSLINE_SYMBOL];
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
type HandoffWriteResult,
|
|
28
28
|
type QolBudgetHandoff,
|
|
29
29
|
} from "./compaction-handoff.js";
|
|
30
|
-
import { settingBoolean, settingNumber, settingString } from "./settings.js";
|
|
30
|
+
import { piCompactionEnabled, settingBoolean, settingNumber, settingString } from "./settings.js";
|
|
31
31
|
import { stringifyError } from "./util.js";
|
|
32
32
|
|
|
33
33
|
export type QolSummaryProfile = "concise" | "balanced" | "exhaustive";
|
|
@@ -373,6 +373,22 @@ export function compactionTriggerReason(ctx: ExtensionContext): string | undefin
|
|
|
373
373
|
return undefined;
|
|
374
374
|
}
|
|
375
375
|
|
|
376
|
+
/** Whether a QOL automatic compaction trigger may fire, and what turned it
|
|
377
|
+
* off. Pi core's `compaction.enabled` is read first, so that one key stops
|
|
378
|
+
* every automatic compaction in Pi; a manual /compact never passes here. */
|
|
379
|
+
export type AutoCompactionGate = "enabled" | "disabled" | "pi-compaction-disabled";
|
|
380
|
+
|
|
381
|
+
const AUTO_COMPACTION_SWITCHES = {
|
|
382
|
+
budgetGuard: { key: "compaction.budgetGuardEnabled", fallback: true },
|
|
383
|
+
idle: { key: "compaction.idleEnabled", fallback: false },
|
|
384
|
+
} as const;
|
|
385
|
+
|
|
386
|
+
export function autoCompactionGate(trigger: keyof typeof AUTO_COMPACTION_SWITCHES, cwd: string): AutoCompactionGate {
|
|
387
|
+
if (!piCompactionEnabled(cwd)) return "pi-compaction-disabled";
|
|
388
|
+
const { key, fallback } = AUTO_COMPACTION_SWITCHES[trigger];
|
|
389
|
+
return settingBoolean(key, fallback, cwd) ? "enabled" : "disabled";
|
|
390
|
+
}
|
|
391
|
+
|
|
376
392
|
export type BudgetGuardTrigger = BudgetTrigger;
|
|
377
393
|
export type TranscriptRiskState = TranscriptRiskResult;
|
|
378
394
|
|
|
@@ -383,7 +399,7 @@ export type TranscriptRiskState = TranscriptRiskResult;
|
|
|
383
399
|
* repeated triggers while usage stays above the threshold.
|
|
384
400
|
*/
|
|
385
401
|
export function budgetGuardTrigger(ctx: ExtensionContext): BudgetGuardTrigger | undefined {
|
|
386
|
-
if (
|
|
402
|
+
if (autoCompactionGate("budgetGuard", ctx.cwd) !== "enabled") return undefined;
|
|
387
403
|
const usage = contextUsage(ctx);
|
|
388
404
|
if (!usage) return undefined;
|
|
389
405
|
return computeBudgetTrigger({
|
|
@@ -15,6 +15,7 @@ export const QOL_NOTIFICATION_SERVICE_SYMBOL = Symbol.for("kendex.pi-qol.notific
|
|
|
15
15
|
export const KENDEX_MODAL_LOCK_SYMBOL = Symbol.for("kendex.pi.modal-lock");
|
|
16
16
|
export const CAVEMAN_BRIDGE_SYMBOL = Symbol.for("kendex.pi.caveman");
|
|
17
17
|
export const PI_AGENTS_STATUSLINE_SYMBOL = Symbol.for("kendex.pi-agents-tmux.statusline");
|
|
18
|
+
export const CLAUDE_BILLING_IDENTITY_SYMBOL = Symbol.for("kendex.pi.claude-bridge.billing-identity.v1");
|
|
18
19
|
|
|
19
20
|
export const CAVEMAN_ICON_ACTIVE = "\uee9a";
|
|
20
21
|
export const CAVEMAN_ICON_INACTIVE = "\u{f19e0}";
|
|
@@ -69,21 +69,49 @@ export function piSettingsPaths(cwd = process.cwd()): string[] {
|
|
|
69
69
|
return projectSettingsTrusted(project) ? [user, project] : [user];
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
73
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Each settings file `piSettingsPaths` names that exists and parses, later
|
|
77
|
+
* files overriding earlier ones. A malformed file is skipped, as Pi's own
|
|
78
|
+
* settings load treats one as empty. */
|
|
79
|
+
function readPiSettingsFiles(cwd?: string): Record<string, any>[] {
|
|
80
|
+
const files: Record<string, any>[] = [];
|
|
74
81
|
for (const settingsPath of piSettingsPaths(cwd)) {
|
|
75
82
|
if (!existsSync(settingsPath)) continue;
|
|
76
83
|
try {
|
|
77
84
|
const parsed = JSON.parse(readFileSync(settingsPath, "utf8"));
|
|
78
|
-
|
|
79
|
-
if (config && typeof config === "object" && !Array.isArray(config)) Object.assign(merged, config);
|
|
85
|
+
if (isRecord(parsed)) files.push(parsed);
|
|
80
86
|
} catch {
|
|
81
|
-
// Ignore malformed
|
|
87
|
+
// Ignore a malformed settings file.
|
|
82
88
|
}
|
|
83
89
|
}
|
|
90
|
+
return files;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function readPackageConfig(packageId: string, cwd?: string): Record<string, unknown> {
|
|
94
|
+
const merged: Record<string, unknown> = {};
|
|
95
|
+
for (const settings of readPiSettingsFiles(cwd)) {
|
|
96
|
+
const config = settings.kendex?.extensionManager?.config?.[packageId];
|
|
97
|
+
if (isRecord(config)) Object.assign(merged, config);
|
|
98
|
+
}
|
|
84
99
|
return merged;
|
|
85
100
|
}
|
|
86
101
|
|
|
102
|
+
/** Pi core's `compaction.enabled` over the files QOL reads its own config
|
|
103
|
+
* from, resolved as Pi's `SettingsManager.getCompactionEnabled` resolves it:
|
|
104
|
+
* the last file that sets the key decides, a key no file sets is Pi's default,
|
|
105
|
+
* true, and any falsy value turns compaction off. */
|
|
106
|
+
export function piCompactionEnabled(cwd?: string): boolean {
|
|
107
|
+
let enabled: unknown;
|
|
108
|
+
for (const settings of readPiSettingsFiles(cwd)) {
|
|
109
|
+
const compaction = settings.compaction;
|
|
110
|
+
if (isRecord(compaction) && compaction.enabled !== undefined) enabled = compaction.enabled;
|
|
111
|
+
}
|
|
112
|
+
return Boolean(enabled ?? true);
|
|
113
|
+
}
|
|
114
|
+
|
|
87
115
|
export function readkendexConfig(cwd?: string): kendexConfig {
|
|
88
116
|
return readPackageConfig(CONFIG_ID, cwd) as kendexConfig;
|
|
89
117
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { compactionProfile } from "./compaction.js";
|
|
2
|
+
import { autoCompactionGate, compactionProfile, type AutoCompactionGate } from "./compaction.js";
|
|
3
3
|
import {
|
|
4
4
|
DEFAULT_AUTO_RENAME_MODEL,
|
|
5
|
+
DEFAULT_BUDGET_GUARD_PERCENT,
|
|
6
|
+
DEFAULT_BUDGET_GUARD_TOKENS,
|
|
5
7
|
DEFAULT_COMPACTION_MODEL,
|
|
6
8
|
DEFAULT_IDLE_COMPACTION_SECONDS,
|
|
7
9
|
DEFAULT_PERMISSION_GATE_PREVIEW_CHARS,
|
|
@@ -15,6 +17,21 @@ import { autoRenameEnabled } from "./session-rename.js";
|
|
|
15
17
|
import { sessionSearchShortcut } from "./session-search/index.js";
|
|
16
18
|
import { boundedSettingNumber, newlineFallbackKey, settingBoolean, settingNumber, settingString } from "./settings.js";
|
|
17
19
|
|
|
20
|
+
function autoCompactionStatus(gate: AutoCompactionGate, enabled: () => string): string {
|
|
21
|
+
switch (gate) {
|
|
22
|
+
case "enabled":
|
|
23
|
+
return enabled();
|
|
24
|
+
case "disabled":
|
|
25
|
+
return "disabled";
|
|
26
|
+
case "pi-compaction-disabled":
|
|
27
|
+
return "disabled by Pi compaction.enabled=false";
|
|
28
|
+
default: {
|
|
29
|
+
const unknownGate: never = gate;
|
|
30
|
+
throw new Error(`pi-qol: unknown auto-compaction gate ${String(unknownGate)}`);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
18
35
|
export function statusMessage(ctx: ExtensionContext): string {
|
|
19
36
|
const labels = attachmentLabels(currentEditorText(ctx), ctx.cwd);
|
|
20
37
|
const searchShortcut = sessionSearchShortcut(ctx.cwd);
|
|
@@ -37,7 +54,8 @@ export function statusMessage(ctx: ExtensionContext): string {
|
|
|
37
54
|
`Handoff prompt review: ${settingBoolean("handoffReviewPrompt", true, ctx.cwd) ? "enabled" : "disabled"}`,
|
|
38
55
|
`Session search: ${settingBoolean("sessionSearch.enabled", true, ctx.cwd) ? `enabled (/search${searchShortcut ? `, ${searchShortcut}` : ""})` : "disabled"}`,
|
|
39
56
|
`Custom compaction: ${settingBoolean("compaction.customEnabled", false, ctx.cwd) ? `enabled (${settingString("compaction.model", DEFAULT_COMPACTION_MODEL, ctx.cwd)}, ${compactionProfile(ctx.cwd)})` : "disabled (Pi default)"}`,
|
|
40
|
-
`
|
|
57
|
+
`Budget guard: ${autoCompactionStatus(autoCompactionGate("budgetGuard", ctx.cwd), () => `enabled (budgetPercent=${settingNumber("compaction.budgetPercent", DEFAULT_BUDGET_GUARD_PERCENT, ctx.cwd)}, budgetTokens=${settingNumber("compaction.budgetTokens", DEFAULT_BUDGET_GUARD_TOKENS, ctx.cwd)})`)}`,
|
|
58
|
+
`Idle compaction: ${autoCompactionStatus(autoCompactionGate("idle", ctx.cwd), () => `enabled after ${Math.max(1, Math.floor(settingNumber("compaction.idleTimeoutSeconds", DEFAULT_IDLE_COMPACTION_SECONDS, ctx.cwd)))}s idle`)}`,
|
|
41
59
|
`Branch summary override: ${settingBoolean("compaction.branchSummaryEnabled", false, ctx.cwd) ? "enabled" : "disabled"}`,
|
|
42
60
|
`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
61
|
`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"}`,
|
|
@@ -4,7 +4,7 @@ import type { ExtensionAPI, ExtensionContext, Theme } from "@earendil-works/pi-c
|
|
|
4
4
|
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
5
5
|
import { subagentStatuslineMarker } from "./agent-statusline.js";
|
|
6
6
|
import { stripAnsi } from "./ansi.js";
|
|
7
|
-
import { readCavemanBridge } from "./bridges.js";
|
|
7
|
+
import { readCavemanBridge, readClaudeBillingIdentityBridge } from "./bridges.js";
|
|
8
8
|
import { glyphs } from "./glyphs.js";
|
|
9
9
|
import { CAVEMAN_ICON_ACTIVE, CAVEMAN_ICON_INACTIVE } from "./constants.js";
|
|
10
10
|
import { settingBoolean, settingNumber } from "./settings.js";
|
|
@@ -89,6 +89,17 @@ function statuslineContextInfo(ctx: ExtensionContext): { label: string; percent:
|
|
|
89
89
|
return { label: formatWindow(contextWindow), percent: 100 - usedPercent };
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
/** The account segment for an active Claude-bridge model, or "" otherwise.
|
|
93
|
+
* A lane can retain its last Claude identity after the user selects another
|
|
94
|
+
* provider, which does not bill that account. The Claude bridge owns the
|
|
95
|
+
* judgement of what counts as a confirmed login; this reads its answer and
|
|
96
|
+
* displays it only beside its model. */
|
|
97
|
+
function accountLabel(ctx: ExtensionContext): string {
|
|
98
|
+
if (!settingBoolean("statusline.showAccount", true, ctx.cwd)) return "";
|
|
99
|
+
if (ctx.model?.provider !== "pi-claude") return "";
|
|
100
|
+
return readClaudeBillingIdentityBridge()?.currentLoginEmail(ctx.sessionManager.getSessionId()) ?? "";
|
|
101
|
+
}
|
|
102
|
+
|
|
92
103
|
function gitBadge(state: GitState, showDirtyMarker: boolean): string {
|
|
93
104
|
if (!state.branch) return "";
|
|
94
105
|
const icon = state.inLinkedWorktree || state.branch !== "main" ? `🌳 ${state.branch}` : "🦀";
|
|
@@ -200,6 +211,8 @@ export function renderStatusLine(width: number, ctx: ExtensionContext, git: GitS
|
|
|
200
211
|
const thinkingLevel = normalizeThinkingLevel(pi.getThinkingLevel());
|
|
201
212
|
const thinkingChunk = thinkingLevel;
|
|
202
213
|
const contextChunk = ` ${contextLabel}`;
|
|
214
|
+
const account = accountLabel(ctx);
|
|
215
|
+
const accountSegment = account ? `${statusSeparator}${account}` : "";
|
|
203
216
|
const cavemanBridge = readCavemanBridge();
|
|
204
217
|
const cavemanVisible = !!cavemanBridge && (cavemanBridge.isStatusBadgeEnabled?.(ctx.cwd) ?? true);
|
|
205
218
|
const caveman = cavemanVisible ? cavemanBridge : undefined;
|
|
@@ -208,16 +221,17 @@ export function renderStatusLine(width: number, ctx: ExtensionContext, git: GitS
|
|
|
208
221
|
const cavemanTone = cavemanIconTone(caveman?.getMode() ?? "off", cavemanActive);
|
|
209
222
|
const cavemanSegment = caveman ? `${statusSeparator}${cavemanGlyph}` : "";
|
|
210
223
|
const contextSeparator = caveman ? ` ${statusSeparator}` : "";
|
|
211
|
-
const leftPlain = `${projectChunk}${statusSeparator}${thinkingChunk}${cavemanSegment}${contextSeparator}${contextChunk.trimStart()}`;
|
|
224
|
+
const leftPlain = `${projectChunk}${statusSeparator}${thinkingChunk}${cavemanSegment}${contextSeparator}${contextChunk.trimStart()}${accountSegment}`;
|
|
212
225
|
const percentPlain = percent === null ? `${glyphs(ctx.cwd).ellipsis}%` : `${percent}%`;
|
|
213
226
|
const subagentMarker = subagentStatuslineMarker(ctx.cwd);
|
|
214
227
|
const rightPlain = subagentMarker ? `${percentPlain} ${subagentMarker.plain}` : percentPlain;
|
|
215
228
|
const percentColor = percent === null ? "muted" : percent <= 15 ? "error" : percent <= 30 ? "warning" : "success";
|
|
216
229
|
const separatorColored = theme.fg("muted", statusSeparator);
|
|
217
230
|
const thinkingToken = thinkingThemeToken(thinkingLevel) as Parameters<Theme["fg"]>[0];
|
|
231
|
+
const accountColored = account ? `${separatorColored}${theme.fg("muted", account)}` : "";
|
|
218
232
|
const leftColored = caveman
|
|
219
|
-
? `${theme.fg("accent", projectChunk)}${separatorColored}${theme.fg(thinkingToken, thinkingChunk)}${separatorColored}${theme.fg(cavemanTone, cavemanGlyph)}${theme.fg("muted", contextSeparator)}${theme.fg("accent", contextChunk.trimStart())}`
|
|
220
|
-
: `${theme.fg("accent", projectChunk)}${separatorColored}${theme.fg(thinkingToken, thinkingChunk)}${theme.fg("accent", contextChunk)}`;
|
|
233
|
+
? `${theme.fg("accent", projectChunk)}${separatorColored}${theme.fg(thinkingToken, thinkingChunk)}${separatorColored}${theme.fg(cavemanTone, cavemanGlyph)}${theme.fg("muted", contextSeparator)}${theme.fg("accent", contextChunk.trimStart())}${accountColored}`
|
|
234
|
+
: `${theme.fg("accent", projectChunk)}${separatorColored}${theme.fg(thinkingToken, thinkingChunk)}${theme.fg("accent", contextChunk)}${accountColored}`;
|
|
221
235
|
const right = subagentMarker ? `${theme.fg(percentColor, percentPlain)} ${subagentMarker.styled}` : theme.fg(percentColor, percentPlain);
|
|
222
236
|
const minimumGap = 1;
|
|
223
237
|
const gapWidth = Math.max(minimumGap, width - visibleWidth(leftPlain) - visibleWidth(rightPlain) - 2);
|
package/extensions/qol.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { getQuestionService, readCavemanBridge, type QuestionOpenedEventLike } f
|
|
|
5
5
|
import { BudgetGuardDriver } from "./qol/budget-guard-runtime.js";
|
|
6
6
|
import { ansiGreen } from "./qol/ansi.js";
|
|
7
7
|
import {
|
|
8
|
+
autoCompactionGate,
|
|
8
9
|
budgetGuardTrigger,
|
|
9
10
|
compactionTriggerReason,
|
|
10
11
|
handleQolBranchSummary,
|
|
@@ -334,7 +335,7 @@ export default function qol(pi: ExtensionAPI): void {
|
|
|
334
335
|
|
|
335
336
|
const scheduleIdleCompaction = (ctx: ExtensionContext) => {
|
|
336
337
|
clearIdleCompactionTimer();
|
|
337
|
-
if (
|
|
338
|
+
if (autoCompactionGate("idle", ctx.cwd) !== "enabled") return;
|
|
338
339
|
const reason = compactionTriggerReason(ctx);
|
|
339
340
|
if (!reason) return;
|
|
340
341
|
const delayMs = Math.max(1, Math.floor(settingNumber("compaction.idleTimeoutSeconds", DEFAULT_IDLE_COMPACTION_SECONDS, ctx.cwd))) * 1000;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vanillagreen/pi-qol",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
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": [
|
|
@@ -56,6 +56,15 @@
|
|
|
56
56
|
"apply": "reload",
|
|
57
57
|
"requiresReload": true
|
|
58
58
|
},
|
|
59
|
+
{
|
|
60
|
+
"key": "statusline.showAccount",
|
|
61
|
+
"label": "Show account email",
|
|
62
|
+
"description": "Append the Claude login this session authenticated as, as the Claude bridge reports it. Nothing is shown before the first turn, or when the request used an API key or a third-party backend such as Bedrock or Vertex.",
|
|
63
|
+
"type": "boolean",
|
|
64
|
+
"default": true,
|
|
65
|
+
"category": "Statusline",
|
|
66
|
+
"apply": "live"
|
|
67
|
+
},
|
|
59
68
|
{
|
|
60
69
|
"key": "replaceFooter",
|
|
61
70
|
"label": "Replace built-in footer",
|
|
@@ -830,7 +839,7 @@
|
|
|
830
839
|
{
|
|
831
840
|
"key": "compaction.idleEnabled",
|
|
832
841
|
"label": "Idle compaction trigger",
|
|
833
|
-
"description": "Trigger Pi compaction after the session sits idle above the configured token threshold. Inspired by oh-my-pi idle compaction.",
|
|
842
|
+
"description": "Trigger Pi compaction after the session sits idle above the configured token threshold. Never fires while Pi's own compaction.enabled is false. Inspired by oh-my-pi idle compaction.",
|
|
834
843
|
"type": "boolean",
|
|
835
844
|
"default": false,
|
|
836
845
|
"category": "Compaction Idle",
|
|
@@ -875,7 +884,7 @@
|
|
|
875
884
|
{
|
|
876
885
|
"key": "compaction.budgetGuardEnabled",
|
|
877
886
|
"label": "Long-session budget guard",
|
|
878
|
-
"description": "Start bounded compaction when an active session crosses the configured context budget, while giving Pi's built-in compaction priority. Designed for long autonomous runs so transcript growth cannot silently hit provider or buffer limits. Fires once per threshold crossing, not in a loop.",
|
|
887
|
+
"description": "Start bounded compaction when an active session crosses the configured context budget, while giving Pi's built-in compaction priority. Designed for long autonomous runs so transcript growth cannot silently hit provider or buffer limits. Fires once per threshold crossing, not in a loop. Never fires while Pi's own compaction.enabled is false.",
|
|
879
888
|
"type": "boolean",
|
|
880
889
|
"default": true,
|
|
881
890
|
"category": "Compaction Budget Guard",
|