@vanillagreen/pi-qol 2.3.4 → 2.5.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 +18 -13
- package/extensions/qol/compaction.ts +22 -14
- package/extensions/qol/constants.ts +1 -1
- package/extensions/qol/gateway-privacy.ts +59 -0
- package/extensions/qol/handoff.ts +2 -1
- package/extensions/qol/session-rename.ts +2 -1
- package/extensions/qol/status-message.ts +4 -2
- package/extensions/qol.ts +6 -4
- package/package.json +15 -6
package/README.md
CHANGED
|
@@ -4,6 +4,15 @@ A Pi extension for session controls, prompt editing and notifications. Users can
|
|
|
4
4
|
|
|
5
5
|
  
|
|
6
6
|
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- Show repository, model and context information beside the editor, with the working spinner before the project name.
|
|
10
|
+
- Name and search sessions.
|
|
11
|
+
- Schedule prompts and prepare handoff drafts.
|
|
12
|
+
- Ask before configured shell commands run.
|
|
13
|
+
- Send terminal and desktop notifications.
|
|
14
|
+
- Configure summaries and compaction for long sessions.
|
|
15
|
+
|
|
7
16
|
## Install
|
|
8
17
|
|
|
9
18
|
- npm: `pi install npm:@vanillagreen/pi-qol`.
|
|
@@ -16,15 +25,6 @@ source = "kendex"
|
|
|
16
25
|
|
|
17
26
|
Requires Pi 0.86.0 or newer. Restart Pi after installation. Use `kendex update-pi --check` to preview the installation.
|
|
18
27
|
|
|
19
|
-
## Features
|
|
20
|
-
|
|
21
|
-
- Show repository, model and context information beside the editor, with the working spinner before the project name.
|
|
22
|
-
- Name and search sessions.
|
|
23
|
-
- Schedule prompts and prepare handoff drafts.
|
|
24
|
-
- Ask before configured shell commands run.
|
|
25
|
-
- Send terminal and desktop notifications.
|
|
26
|
-
- Configure summaries and compaction for long sessions.
|
|
27
|
-
|
|
28
28
|
## How it works
|
|
29
29
|
|
|
30
30
|
The extension reads your enabled features when Pi starts. It adds their editor controls, commands and event handlers. Session actions update the session or queue messages for the agent. Notifications report events through your selected channels. Compaction settings control when long conversations are summarized.
|
|
@@ -42,24 +42,29 @@ The extension reads your enabled features when Pi starts. It adds their editor c
|
|
|
42
42
|
|
|
43
43
|
At session start, the extension deletes budget handoff files whose recorded working directory no longer exists. It also deletes files older than 5 days. Both timestamped snapshots and `latest.json` follow this rule.
|
|
44
44
|
|
|
45
|
-
##
|
|
45
|
+
## Setup
|
|
46
46
|
|
|
47
47
|
The settings editor writes project values to `.pi/settings.json`. The default user file is `~/.pi/agent/settings.json`. `PI_CODING_AGENT_DIR` changes the user directory. Package values are stored under `kendex.extensionManager.config["@vanillagreen/pi-qol"]`.
|
|
48
48
|
|
|
49
49
|
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.
|
|
50
50
|
|
|
51
51
|
- `enabled`: package toggle for everything below.
|
|
52
|
+
- `gatewayPrivacy.enabled`: require zero data retention and no prompt training for main-agent and QOL model requests through OpenRouter and Vercel AI Gateway. Covers QOL compaction, branch summaries, imported-session summaries, handoff and session naming. Keeps other routing options and overrides weaker privacy values. Pi's built-in compaction and branch summaries cannot carry these fields, so on a gateway session model QOL writes them in place of Pi's, whatever `compaction.customEnabled` and `compaction.branchSummaryEnabled` say, and a failed or refused summary cancels the compaction or tree move instead of falling back to Pi's. Custom remote compaction endpoints keep their own privacy policy.
|
|
52
53
|
- Statusline and editor: `statusline.enabled`, `statusline.showProvider`, `statusline.showAccount`, `replaceFooter`, `compactPrompt`, `showSessionNameTitle`, `showSessionNameWindow`, `inputBottomPaddingLines`, `gitRefreshTimeoutMs`, `showDirtyMarker`, `newlineOnShiftEnter`, `newlineFallbackKey`, `pendingQueue.asciiGreen`, `showImageChips`, `showAttachmentCountInStatus`.
|
|
53
54
|
- Commands: `enableSessionNameCommand`, `enableHandoffCommand`, `enableScheduleCommand`, `enableContextCommand`, `handoffReviewPrompt`.
|
|
54
|
-
- Session naming: `sessionAutoRename.*` (model, fallback model, deterministic fallback, prefix, prompt, limits, notify, debug).
|
|
55
|
+
- Session naming: `sessionAutoRename.*` (model, fallback model, deterministic fallback, prefix, prompt, limits, notify, debug). `sessionAutoRename.maxTokens` includes the naming model's reasoning tokens. `sessionAutoRename.maxNameChars` limits the title length.
|
|
55
56
|
- Session search: `sessionSearch.*` (shortcut, result and row limits, snippets, overlay width, cache TTL, summary model and limits).
|
|
56
57
|
- Rate limits: `rateLimitAutoResume.*`.
|
|
57
58
|
- Permission gate: `permissionGate.enabled`, `permissionGate.commands` (comma-separated literal fragments or `/regex/flags`), `permissionGate.previewLines`, `permissionGate.previewChars`.
|
|
58
59
|
- Notifications: `notification.*` (triggers, channels, tmux options, protocol, cooldown, title and body).
|
|
59
|
-
- 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).
|
|
60
|
+
- 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). `compaction.maxTokens` caps budget-guard, custom and branch summaries.
|
|
60
61
|
- 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.
|
|
61
|
-
- Thinking: `thinkingLabel.text`, `thinkingTimer.enabled`, `workingIndicator.mode`
|
|
62
|
+
- Thinking: `thinkingLabel.text`, `thinkingTimer.enabled`, `workingIndicator.mode`. `workingIndicator.mode` defaults to `static`, a dot with no animation timer; `animated` restores Pi's spinner. `thinkingTimer.enabled` controls the thinking duration display.
|
|
62
63
|
|
|
63
64
|
**Show model provider** displays a readable provider name before the model, such as `Copilot / GPT 6 Astra`. It is on by default; changes apply on the next render without reloading. Disabling the QOL statusline leaves Pi's standalone working indicator in place.
|
|
64
65
|
|
|
65
66
|
Maintainer notes are in [DEVELOPMENT.md](DEVELOPMENT.md).
|
|
67
|
+
|
|
68
|
+
## Licence
|
|
69
|
+
|
|
70
|
+
[MIT](https://github.com/vanillagreencom/kendex/blob/main/LICENSE)
|
|
@@ -29,12 +29,16 @@ import {
|
|
|
29
29
|
} from "./compaction-handoff.js";
|
|
30
30
|
import { piCompactionEnabled, settingBoolean, settingNumber, settingString } from "./settings.js";
|
|
31
31
|
import { stringifyError } from "./util.js";
|
|
32
|
+
import { gatewayPrivacyBlocksNativeSummary, gatewayPrivacyOnPayload } from "./gateway-privacy.js";
|
|
32
33
|
|
|
33
34
|
export type QolSummaryProfile = "concise" | "balanced" | "exhaustive";
|
|
34
35
|
export type QolSummaryPurpose = "compaction" | "branch-summary" | "session-search";
|
|
35
36
|
|
|
36
|
-
export function compactionNotify(ctx: ExtensionContext, message: string, level: "info" | "warning" | "error" = "info"): void {
|
|
37
|
-
if (ctx.hasUI && settingBoolean("compaction.notify", true, ctx.cwd)) ctx.ui.notify(message, level);
|
|
37
|
+
export function compactionNotify(ctx: ExtensionContext, message: string, level: "info" | "warning" | "error" = "info", required = false): void {
|
|
38
|
+
if (ctx.hasUI && (required || settingBoolean("compaction.notify", true, ctx.cwd))) ctx.ui.notify(message, level);
|
|
39
|
+
// Print, JSON and RPC callers read stderr for diagnostics. A cancelled
|
|
40
|
+
// protected summary has no Pi error event to carry its provider failure.
|
|
41
|
+
if (!ctx.hasUI && required) console.error(JSON.stringify({ source: "pi-qol", level, message }));
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
export function compactionProfile(cwd: string): QolSummaryProfile {
|
|
@@ -174,7 +178,7 @@ async function singleShotSummary(ctx: ExtensionContext, request: SummarizeReques
|
|
|
174
178
|
() => complete(
|
|
175
179
|
model,
|
|
176
180
|
{ messages: [message], systemPrompt: QOL_COMPACTION_SYSTEM_PROMPT },
|
|
177
|
-
{ apiKey: auth.apiKey, env: auth.env, headers: auth.headers, maxTokens: options.maxTokens, signal: options.signal },
|
|
181
|
+
{ apiKey: auth.apiKey, env: auth.env, headers: auth.headers, maxTokens: options.maxTokens, signal: options.signal, onPayload: gatewayPrivacyOnPayload(ctx) },
|
|
178
182
|
),
|
|
179
183
|
options.signal,
|
|
180
184
|
{
|
|
@@ -261,16 +265,19 @@ export function writeBudgetHandoffArtifact(ctx: ExtensionContext, handoff: QolBu
|
|
|
261
265
|
|
|
262
266
|
export async function handleQolCompaction(event: any, ctx: ExtensionContext): Promise<any> {
|
|
263
267
|
const isBudgetGuard = isBudgetGuardCompaction(event?.customInstructions);
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
//
|
|
267
|
-
|
|
268
|
+
const nativeBlocked = gatewayPrivacyBlocksNativeSummary(ctx);
|
|
269
|
+
const customEnabled = settingBoolean("compaction.customEnabled", false, ctx.cwd);
|
|
270
|
+
// Budget-guard-triggered and privacy-covered compactions force the QOL
|
|
271
|
+
// summarizer even when compaction.customEnabled is off. The handoff writer
|
|
272
|
+
// still honors its independent compaction.handoffArtifactEnabled toggle.
|
|
273
|
+
if (!isBudgetGuard && !nativeBlocked && !customEnabled) return undefined;
|
|
268
274
|
const preparation = event.preparation ?? {};
|
|
269
275
|
const messages = [...(preparation.messagesToSummarize ?? []), ...(preparation.turnPrefixMessages ?? [])];
|
|
270
276
|
if (messages.length === 0) return undefined;
|
|
271
277
|
const tokensBefore = typeof preparation.tokensBefore === "number" ? preparation.tokensBefore : 0;
|
|
272
|
-
const
|
|
273
|
-
|
|
278
|
+
const handoffResult: HandoffWriteResult = isBudgetGuard || customEnabled
|
|
279
|
+
? writeBudgetHandoffArtifact(ctx, buildBudgetHandoff(ctx, { preparation, reason: event.customInstructions ?? "session_before_compact" }))
|
|
280
|
+
: {};
|
|
274
281
|
const sourceLabel = isBudgetGuard ? "pi-qol budget-guard" : "pi-qol";
|
|
275
282
|
compactionNotify(ctx, `QOL compaction: summarizing ${messages.length} message(s), ${tokensBefore.toLocaleString()} token(s)${isBudgetGuard ? " (budget guard)" : ""}.`, "info");
|
|
276
283
|
try {
|
|
@@ -308,8 +315,8 @@ export async function handleQolCompaction(event: any, ctx: ExtensionContext): Pr
|
|
|
308
315
|
};
|
|
309
316
|
} catch (error) {
|
|
310
317
|
if (event.signal?.aborted) return undefined;
|
|
311
|
-
compactionNotify(ctx, `QOL compaction failed: ${stringifyError(error)}`, "error");
|
|
312
|
-
return settingBoolean("compaction.fallbackToDefault", true, ctx.cwd) ? undefined : { cancel: true };
|
|
318
|
+
compactionNotify(ctx, `QOL compaction failed: ${stringifyError(error)}`, "error", nativeBlocked);
|
|
319
|
+
return !nativeBlocked && settingBoolean("compaction.fallbackToDefault", true, ctx.cwd) ? undefined : { cancel: true };
|
|
313
320
|
}
|
|
314
321
|
}
|
|
315
322
|
|
|
@@ -322,7 +329,8 @@ function summarizeEntryForBranch(entry: any): string[] {
|
|
|
322
329
|
}
|
|
323
330
|
|
|
324
331
|
export async function handleQolBranchSummary(event: any, ctx: ExtensionContext): Promise<any> {
|
|
325
|
-
|
|
332
|
+
const nativeBlocked = gatewayPrivacyBlocksNativeSummary(ctx);
|
|
333
|
+
if (!nativeBlocked && !settingBoolean("compaction.branchSummaryEnabled", false, ctx.cwd)) return undefined;
|
|
326
334
|
const preparation = event.preparation ?? {};
|
|
327
335
|
if (preparation.userWantsSummary !== true) return undefined;
|
|
328
336
|
const entries = Array.isArray(preparation.entriesToSummarize) ? preparation.entriesToSummarize : [];
|
|
@@ -345,8 +353,8 @@ export async function handleQolBranchSummary(event: any, ctx: ExtensionContext):
|
|
|
345
353
|
};
|
|
346
354
|
} catch (error) {
|
|
347
355
|
if (event.signal?.aborted) return undefined;
|
|
348
|
-
compactionNotify(ctx, `QOL branch summary failed: ${stringifyError(error)}`, "error");
|
|
349
|
-
return undefined;
|
|
356
|
+
compactionNotify(ctx, `QOL branch summary failed: ${stringifyError(error)}`, "error", nativeBlocked);
|
|
357
|
+
return nativeBlocked ? { cancel: true } : undefined;
|
|
350
358
|
}
|
|
351
359
|
}
|
|
352
360
|
|
|
@@ -92,7 +92,7 @@ export const DEFAULT_AUTO_RENAME_MODEL = "openai-codex/gpt-5.4-mini";
|
|
|
92
92
|
export const DEFAULT_AUTO_RENAME_FALLBACK_MODEL = "current";
|
|
93
93
|
export const DEFAULT_AUTO_RENAME_INPUT_CHARS = 2000;
|
|
94
94
|
export const DEFAULT_AUTO_RENAME_NAME_CHARS = 80;
|
|
95
|
-
export const DEFAULT_AUTO_RENAME_MAX_TOKENS =
|
|
95
|
+
export const DEFAULT_AUTO_RENAME_MAX_TOKENS = 128;
|
|
96
96
|
export const DEFAULT_AUTO_RENAME_TIMEOUT_MS = 12_000;
|
|
97
97
|
export const DEFAULT_INPUT_BOTTOM_PADDING_LINES = 0;
|
|
98
98
|
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { recordProjectTrust } from "./package-config.js";
|
|
3
|
+
import { settingBoolean } from "./settings.js";
|
|
4
|
+
|
|
5
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
6
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function gatewayPrivacyCovers(model: { provider: string } | undefined, cwd: string): boolean {
|
|
10
|
+
const provider = model?.provider;
|
|
11
|
+
return (provider === "openrouter" || provider === "vercel-ai-gateway") && settingBoolean("gatewayPrivacy.enabled", false, cwd);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Pi sends its own compaction and branch summaries to the session model with
|
|
15
|
+
* no payload callback, so for a covered model QOL writes the summary and never
|
|
16
|
+
* falls back to Pi's. */
|
|
17
|
+
export function gatewayPrivacyBlocksNativeSummary(ctx: ExtensionContext): boolean {
|
|
18
|
+
recordProjectTrust(ctx);
|
|
19
|
+
return gatewayPrivacyCovers(ctx.model, ctx.cwd);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function gatewayPrivacyOnPayload(ctx: ExtensionContext): (payload: unknown, model: { provider: string } | undefined) => unknown {
|
|
23
|
+
const cwd = ctx.cwd;
|
|
24
|
+
recordProjectTrust(ctx);
|
|
25
|
+
return (payload, model) => {
|
|
26
|
+
if (!gatewayPrivacyCovers(model, cwd)) return undefined;
|
|
27
|
+
const body = payload;
|
|
28
|
+
if (!isRecord(body)) return undefined;
|
|
29
|
+
|
|
30
|
+
// These request filters are opt-in package policy. Caller routing keys
|
|
31
|
+
// remain, but weaker privacy values must not disable an enabled filter.
|
|
32
|
+
if (model?.provider === "openrouter") {
|
|
33
|
+
return {
|
|
34
|
+
...body,
|
|
35
|
+
provider: {
|
|
36
|
+
...(isRecord(body.provider) ? body.provider : {}),
|
|
37
|
+
zdr: true,
|
|
38
|
+
data_collection: "deny",
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
const options = isRecord(body.providerOptions) ? body.providerOptions : {};
|
|
43
|
+
return {
|
|
44
|
+
...body,
|
|
45
|
+
providerOptions: {
|
|
46
|
+
...options,
|
|
47
|
+
gateway: {
|
|
48
|
+
...(isRecord(options.gateway) ? options.gateway : {}),
|
|
49
|
+
zeroDataRetention: true,
|
|
50
|
+
disallowPromptTraining: true,
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function registerGatewayPrivacy(pi: ExtensionAPI): void {
|
|
58
|
+
pi.on("before_provider_request", (event, ctx) => gatewayPrivacyOnPayload(ctx)(event.payload, ctx.model));
|
|
59
|
+
}
|
|
@@ -6,6 +6,7 @@ import { acquirekendexModalLock } from "./bridges.js";
|
|
|
6
6
|
import { serializeMessagesForSummary } from "./compaction.js";
|
|
7
7
|
import { HANDOFF_SYSTEM_PROMPT } from "./constants.js";
|
|
8
8
|
import { settingBoolean } from "./settings.js";
|
|
9
|
+
import { gatewayPrivacyOnPayload } from "./gateway-privacy.js";
|
|
9
10
|
|
|
10
11
|
function errorMessage(error: unknown): string {
|
|
11
12
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -66,7 +67,7 @@ export async function runHandoff(args: string, ctx: ExtensionCommandContext): Pr
|
|
|
66
67
|
const response = await complete(
|
|
67
68
|
ctx.model!,
|
|
68
69
|
{ systemPrompt: HANDOFF_SYSTEM_PROMPT, messages: [userMessage] },
|
|
69
|
-
{ apiKey: auth.apiKey, env: auth.env, headers: auth.headers, signal: loader.signal },
|
|
70
|
+
{ apiKey: auth.apiKey, env: auth.env, headers: auth.headers, signal: loader.signal, onPayload: gatewayPrivacyOnPayload(ctx) },
|
|
70
71
|
);
|
|
71
72
|
|
|
72
73
|
if (response.stopReason === "aborted") return null;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Message } from "@earendil-works/pi-ai";
|
|
2
2
|
import { complete } from "./pi-ai-compat.js";
|
|
3
|
+
import { gatewayPrivacyOnPayload } from "./gateway-privacy.js";
|
|
3
4
|
import type { ExtensionContext, SessionEntry } from "@earendil-works/pi-coding-agent";
|
|
4
5
|
import { textFromContent } from "./agent-end.js";
|
|
5
6
|
import { oneLine } from "./ansi.js";
|
|
@@ -331,7 +332,7 @@ export async function generateAutoRenameName(query: string, ctx: ExtensionContex
|
|
|
331
332
|
const response = await complete(
|
|
332
333
|
resolved.model,
|
|
333
334
|
{ messages: [message], systemPrompt: AUTO_RENAME_SYSTEM_PROMPT },
|
|
334
|
-
{ apiKey: resolved.apiKey, env: resolved.env, headers: resolved.headers, maxTokens, signal: controller.signal },
|
|
335
|
+
{ apiKey: resolved.apiKey, env: resolved.env, headers: resolved.headers, maxTokens, signal: controller.signal, onPayload: gatewayPrivacyOnPayload(ctx) },
|
|
335
336
|
);
|
|
336
337
|
if (response.stopReason === "error") {
|
|
337
338
|
autoRenameDebug(ctx, `${resolved.label} failed: ${response.errorMessage ?? "unknown error"}`, "warning");
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import { currentEditorText } from "./editor.js";
|
|
13
13
|
import { glyphs } from "./glyphs.js";
|
|
14
14
|
import { attachmentLabels } from "./images.js";
|
|
15
|
+
import { gatewayPrivacyBlocksNativeSummary } from "./gateway-privacy.js";
|
|
15
16
|
import { permissionGateCommands } from "./permission-gate.js";
|
|
16
17
|
import { autoRenameEnabled } from "./session-rename.js";
|
|
17
18
|
import { sessionSearchShortcut } from "./session-search/index.js";
|
|
@@ -36,6 +37,7 @@ export function statusMessage(ctx: ExtensionContext): string {
|
|
|
36
37
|
const labels = attachmentLabels(currentEditorText(ctx), ctx.cwd);
|
|
37
38
|
const searchShortcut = sessionSearchShortcut(ctx.cwd);
|
|
38
39
|
const statuslineEnabled = settingBoolean("statusline.enabled", true, ctx.cwd);
|
|
40
|
+
const nativeBlocked = gatewayPrivacyBlocksNativeSummary(ctx);
|
|
39
41
|
const statuslineMode = statuslineEnabled
|
|
40
42
|
? (settingBoolean("replaceFooter", true, ctx.cwd) ? "enabled, replaces footer" : "enabled, footer preserved")
|
|
41
43
|
: "disabled";
|
|
@@ -53,10 +55,10 @@ export function statusMessage(ctx: ExtensionContext): string {
|
|
|
53
55
|
`Schedule command: ${settingBoolean("enableScheduleCommand", true, ctx.cwd) ? "enabled (/schedule)" : "disabled"}`,
|
|
54
56
|
`Handoff prompt review: ${settingBoolean("handoffReviewPrompt", true, ctx.cwd) ? "enabled" : "disabled"}`,
|
|
55
57
|
`Session search: ${settingBoolean("sessionSearch.enabled", true, ctx.cwd) ? `enabled (/search${searchShortcut ? `, ${searchShortcut}` : ""})` : "disabled"}`,
|
|
56
|
-
`Custom compaction: ${settingBoolean("compaction.customEnabled", false, ctx.cwd) ? `enabled (${settingString("compaction.model", DEFAULT_COMPACTION_MODEL, ctx.cwd)}, ${compactionProfile(ctx.cwd)})` : "disabled (Pi default)"}`,
|
|
58
|
+
`Custom compaction: ${settingBoolean("compaction.customEnabled", false, ctx.cwd) ? `enabled (${settingString("compaction.model", DEFAULT_COMPACTION_MODEL, ctx.cwd)}, ${compactionProfile(ctx.cwd)})` : nativeBlocked ? "enabled by gateway privacy" : "disabled (Pi default)"}`,
|
|
57
59
|
`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
60
|
`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`)}`,
|
|
59
|
-
`Branch summary override: ${settingBoolean("compaction.branchSummaryEnabled", false, ctx.cwd) ? "enabled" : "disabled"}`,
|
|
61
|
+
`Branch summary override: ${settingBoolean("compaction.branchSummaryEnabled", false, ctx.cwd) ? "enabled" : nativeBlocked ? "enabled by gateway privacy" : "disabled"}`,
|
|
60
62
|
`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"}`,
|
|
61
63
|
`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"}`,
|
|
62
64
|
`Thinking timer: ${settingBoolean("thinkingTimer.enabled", true, ctx.cwd) ? "enabled" : "disabled"}`,
|
package/extensions/qol.ts
CHANGED
|
@@ -39,6 +39,7 @@ import {
|
|
|
39
39
|
QolEditor,
|
|
40
40
|
} from "./qol/editor.js";
|
|
41
41
|
import { runHandoff } from "./qol/handoff.js";
|
|
42
|
+
import { registerGatewayPrivacy } from "./qol/gateway-privacy.js";
|
|
42
43
|
import { imageContentsForPaths, resolveSubmittedImagePaths } from "./qol/images.js";
|
|
43
44
|
import {
|
|
44
45
|
clearTmuxWindowMark,
|
|
@@ -109,6 +110,7 @@ export default function qol(pi: ExtensionAPI): void {
|
|
|
109
110
|
if (guard[INSTALL_SYMBOL]) return;
|
|
110
111
|
guard[INSTALL_SYMBOL] = true;
|
|
111
112
|
if (!settingBoolean("enabled", true)) return;
|
|
113
|
+
registerGatewayPrivacy(pi);
|
|
112
114
|
|
|
113
115
|
installThinkingTimerPatch();
|
|
114
116
|
const thinkingTimerStore: ThinkingTimerStore = {
|
|
@@ -226,12 +228,12 @@ export default function qol(pi: ExtensionAPI): void {
|
|
|
226
228
|
thinkingTimerStore.enabled = !!ctx?.hasUI && settingBoolean("thinkingTimer.enabled", true, ctx?.cwd);
|
|
227
229
|
};
|
|
228
230
|
|
|
229
|
-
// Pi's Loader starts no animation timer for a single-frame indicator
|
|
230
|
-
//
|
|
231
|
-
//
|
|
231
|
+
// Pi's Loader starts no animation timer for a single-frame indicator, so
|
|
232
|
+
// static mode, the default, draws no frames of its own whether the
|
|
233
|
+
// indicator is embedded in QOL or in Pi's standalone row.
|
|
232
234
|
const applyWorkingIndicatorMode = (ctx: ExtensionContext): void => {
|
|
233
235
|
if (!ctx.hasUI) return;
|
|
234
|
-
const mode = settingString("workingIndicator.mode", "
|
|
236
|
+
const mode = settingString("workingIndicator.mode", "static", ctx.cwd);
|
|
235
237
|
if (mode === "static") {
|
|
236
238
|
ctx.ui.setWorkingVisible(true);
|
|
237
239
|
ctx.ui.setWorkingIndicator({ frames: [ctx.ui.theme.fg("accent", "●")] });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vanillagreen/pi-qol",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.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": [
|
|
@@ -33,6 +33,15 @@
|
|
|
33
33
|
"apply": "reload",
|
|
34
34
|
"requiresReload": true
|
|
35
35
|
},
|
|
36
|
+
{
|
|
37
|
+
"key": "gatewayPrivacy.enabled",
|
|
38
|
+
"label": "Require gateway privacy controls",
|
|
39
|
+
"description": "Require zero data retention and no prompt training for main-agent and QOL model requests through OpenRouter and Vercel AI Gateway. Overrides weaker privacy values and keeps other routing options. On a gateway session model, QOL writes compaction and branch summaries in place of Pi's built-in ones, which cannot carry these controls, and a failed summary cancels instead of falling back to Pi's.",
|
|
40
|
+
"type": "boolean",
|
|
41
|
+
"default": false,
|
|
42
|
+
"category": "Privacy",
|
|
43
|
+
"apply": "live"
|
|
44
|
+
},
|
|
36
45
|
{
|
|
37
46
|
"key": "glyphStyle",
|
|
38
47
|
"label": "Glyph style",
|
|
@@ -310,9 +319,9 @@
|
|
|
310
319
|
{
|
|
311
320
|
"key": "sessionAutoRename.maxTokens",
|
|
312
321
|
"label": "Auto-rename output tokens",
|
|
313
|
-
"description": "Maximum output tokens requested from the naming model.",
|
|
322
|
+
"description": "Maximum output tokens requested from the naming model, reasoning tokens included.",
|
|
314
323
|
"type": "number",
|
|
315
|
-
"default":
|
|
324
|
+
"default": 128,
|
|
316
325
|
"category": "Session Auto-Rename",
|
|
317
326
|
"apply": "live"
|
|
318
327
|
},
|
|
@@ -803,7 +812,7 @@
|
|
|
803
812
|
{
|
|
804
813
|
"key": "compaction.fallbackToDefault",
|
|
805
814
|
"label": "Fallback to Pi default compaction",
|
|
806
|
-
"description": "If custom compaction fails, let Pi run its default compaction instead of cancelling the compaction.",
|
|
815
|
+
"description": "If custom compaction fails, let Pi run its default compaction instead of cancelling the compaction. Ignored while gatewayPrivacy.enabled covers the session model, since Pi's compaction would send the transcript without the privacy controls.",
|
|
807
816
|
"type": "boolean",
|
|
808
817
|
"default": true,
|
|
809
818
|
"category": "Compaction",
|
|
@@ -966,13 +975,13 @@
|
|
|
966
975
|
{
|
|
967
976
|
"key": "workingIndicator.mode",
|
|
968
977
|
"label": "Working indicator mode",
|
|
969
|
-
"description": "Control Pi's built-in streaming spinner. The default animated spinner ticks every 80ms; in terminals where rendered content overflows the viewport (chat scrolled past the top, /tree open, etc.) each tick can trigger a pi-tui full-screen redraw and visible flash.
|
|
978
|
+
"description": "Control Pi's built-in streaming spinner. The default 'static' shows a single non-animating dot that keeps the working indicator visible without a timer. 'animated' restores Pi's spinner, which ticks every 80ms; in terminals where rendered content overflows the viewport (chat scrolled past the top, /tree open, etc.) each tick can trigger a pi-tui full-screen redraw and visible flash. It appears before the project name when the QOL statusline is enabled, or in Pi's working row otherwise.",
|
|
970
979
|
"type": "enum",
|
|
971
980
|
"enumValues": [
|
|
972
981
|
"animated",
|
|
973
982
|
"static"
|
|
974
983
|
],
|
|
975
|
-
"default": "
|
|
984
|
+
"default": "static",
|
|
976
985
|
"category": "Thinking",
|
|
977
986
|
"apply": "reload",
|
|
978
987
|
"requiresReload": true
|