@pi-unipi/compactor 2.0.3 → 2.0.5
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 +290 -73
- package/package.json +4 -1
- package/skills/compactor/SKILL.md +2 -3
- package/skills/compactor-detail/SKILL.md +49 -64
- package/skills/compactor-doctor/SKILL.md +28 -31
- package/skills/compactor-stats/SKILL.md +22 -20
- package/src/commands/index.ts +4 -1
- package/src/compaction/auto-trigger.ts +306 -0
- package/src/config/manager.ts +1 -0
- package/src/config/presets.ts +26 -0
- package/src/config/schema.ts +7 -0
- package/src/index.ts +74 -1
- package/src/tools/context-budget.ts +18 -2
- package/src/tools/register.ts +19 -11
- package/src/tui/settings-overlay.ts +142 -3
- package/src/types.ts +17 -0
package/src/index.ts
CHANGED
|
@@ -3,9 +3,16 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
6
|
-
import { MODULES, UNIPI_EVENTS, COMPACTOR_COMMANDS, COMPACTOR_TOOLS, emitEvent } from "@pi-unipi/core";
|
|
6
|
+
import { MODULES, UNIPI_EVENTS, COMPACTOR_COMMANDS, COMPACTOR_TOOLS, COMPACTOR_INSTRUCTION, emitEvent } from "@pi-unipi/core";
|
|
7
7
|
import { scaffoldConfig, loadConfig } from "./config/manager.js";
|
|
8
8
|
import { registerCompactionHooks } from "./compaction/hooks.js";
|
|
9
|
+
import {
|
|
10
|
+
createAutoCompactionState,
|
|
11
|
+
decideAutoCompaction,
|
|
12
|
+
markAutoCompactionComplete,
|
|
13
|
+
markAutoCompactionError,
|
|
14
|
+
type AutoCompactionState,
|
|
15
|
+
} from "./compaction/auto-trigger.js";
|
|
9
16
|
import { SessionDB, getWorktreeSuffix } from "./session/db.js";
|
|
10
17
|
import { extractEventsFromToolResult } from "./session/extract.js";
|
|
11
18
|
import { injectResumeSnapshot } from "./session/resume-inject.js";
|
|
@@ -26,6 +33,12 @@ function createDebugLogger(getConfig: () => { debug: boolean }) {
|
|
|
26
33
|
};
|
|
27
34
|
}
|
|
28
35
|
|
|
36
|
+
const formatTokenCount = (n: number): string => {
|
|
37
|
+
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
|
|
38
|
+
if (n >= 1_000) return `${(n / 1_000).toFixed(1)}k`;
|
|
39
|
+
return String(n);
|
|
40
|
+
};
|
|
41
|
+
|
|
29
42
|
/** Measure byte size of a tool_result event's response content. */
|
|
30
43
|
function measureResponseBytes(event: any): number {
|
|
31
44
|
try {
|
|
@@ -59,6 +72,7 @@ export default function compactorExtension(pi: ExtensionAPI): void {
|
|
|
59
72
|
let sessionDB: SessionDB | null = null;
|
|
60
73
|
let executor: PolyglotExecutor | null = null;
|
|
61
74
|
let config = loadConfig();
|
|
75
|
+
let autoCompactionState: AutoCompactionState = createAutoCompactionState();
|
|
62
76
|
let cachedBlocks: NormalizedBlock[] = [];
|
|
63
77
|
let currentSessionId = "default";
|
|
64
78
|
const counters: RuntimeCounters = {
|
|
@@ -148,6 +162,7 @@ export default function compactorExtension(pi: ExtensionAPI): void {
|
|
|
148
162
|
runtimeStats.sessionStart = Date.now();
|
|
149
163
|
runtimeStats.cacheHits = 0;
|
|
150
164
|
runtimeStats.cacheBytesSaved = 0;
|
|
165
|
+
autoCompactionState = createAutoCompactionState();
|
|
151
166
|
|
|
152
167
|
sessionDB?.ensureSession(fullSessionId, projectDir);
|
|
153
168
|
|
|
@@ -284,6 +299,64 @@ export default function compactorExtension(pi: ExtensionAPI): void {
|
|
|
284
299
|
}
|
|
285
300
|
});
|
|
286
301
|
|
|
302
|
+
pi.on("turn_end", async (_event, ctx) => {
|
|
303
|
+
const cwd = (ctx as any).cwd ?? process.cwd();
|
|
304
|
+
config = loadConfig(cwd);
|
|
305
|
+
|
|
306
|
+
const decision = decideAutoCompaction({
|
|
307
|
+
config: config.autoCompaction,
|
|
308
|
+
usage: ctx.getContextUsage?.(),
|
|
309
|
+
state: autoCompactionState,
|
|
310
|
+
nowMs: Date.now(),
|
|
311
|
+
});
|
|
312
|
+
autoCompactionState = decision.state;
|
|
313
|
+
|
|
314
|
+
debug("auto_compaction_decision", {
|
|
315
|
+
enabled: config.autoCompaction.enabled,
|
|
316
|
+
reason: decision.reason,
|
|
317
|
+
shouldTrigger: decision.shouldTrigger,
|
|
318
|
+
percent: decision.usage?.percent,
|
|
319
|
+
tokens: decision.usage?.tokens,
|
|
320
|
+
thresholdPercent: decision.thresholdPercent,
|
|
321
|
+
cooldownRemainingMs: decision.cooldownRemainingMs,
|
|
322
|
+
tokenGrowth: decision.tokenGrowth,
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
if (!decision.shouldTrigger) return;
|
|
326
|
+
|
|
327
|
+
const notify = config.autoCompaction.notify;
|
|
328
|
+
if (notify && decision.usage) {
|
|
329
|
+
ctx.ui.notify(
|
|
330
|
+
`Auto-compacting at ${decision.usage.percent.toFixed(1)}% context (~${formatTokenCount(decision.usage.tokens)} tokens; threshold ${decision.thresholdPercent}%).`,
|
|
331
|
+
"info",
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
try {
|
|
336
|
+
ctx.compact({
|
|
337
|
+
customInstructions: COMPACTOR_INSTRUCTION,
|
|
338
|
+
onComplete: () => {
|
|
339
|
+
autoCompactionState = markAutoCompactionComplete(autoCompactionState);
|
|
340
|
+
if (notify) {
|
|
341
|
+
ctx.ui.notify("Auto-compaction completed.", "info");
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
onError: (err: Error) => {
|
|
345
|
+
autoCompactionState = markAutoCompactionError(autoCompactionState, Date.now());
|
|
346
|
+
const benign = err.message === "Compaction cancelled" || err.message === "Already compacted";
|
|
347
|
+
if (notify && !benign) {
|
|
348
|
+
ctx.ui.notify(`Auto-compaction failed: ${err.message}`, "warning");
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
});
|
|
352
|
+
} catch (err) {
|
|
353
|
+
autoCompactionState = markAutoCompactionError(autoCompactionState, Date.now());
|
|
354
|
+
if (notify) {
|
|
355
|
+
ctx.ui.notify(`Auto-compaction failed: ${err instanceof Error ? err.message : String(err)}`, "warning");
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
|
|
287
360
|
pi.on("session_before_compact", async (event, _ctx) => {
|
|
288
361
|
if (sessionDB) {
|
|
289
362
|
// Use closure currentSessionId — Pi's session_before_compact event
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* context_budget tool — estimate remaining context window
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import type { AutoCompactionConfig } from "../types.js";
|
|
6
|
+
|
|
5
7
|
export interface ContextBudgetResult {
|
|
6
8
|
percentFull: number;
|
|
7
9
|
remainingTokens: number;
|
|
@@ -13,6 +15,7 @@ export interface ContextBudgetResult {
|
|
|
13
15
|
export function estimateContextBudget(
|
|
14
16
|
tokensBefore?: number,
|
|
15
17
|
contextWindowSize?: number,
|
|
18
|
+
autoCompaction?: Pick<AutoCompactionConfig, "enabled" | "thresholdPercent">,
|
|
16
19
|
): ContextBudgetResult | null {
|
|
17
20
|
const windowSize = contextWindowSize ?? 200000; // Default 200K context
|
|
18
21
|
const used = tokensBefore ?? 0;
|
|
@@ -33,6 +36,15 @@ export function estimateContextBudget(
|
|
|
33
36
|
advice = "Context has plenty of room. No compaction needed yet.";
|
|
34
37
|
}
|
|
35
38
|
|
|
39
|
+
if (autoCompaction?.enabled) {
|
|
40
|
+
const threshold = autoCompaction.thresholdPercent;
|
|
41
|
+
if (percentFull >= threshold) {
|
|
42
|
+
advice += ` UniPi percentage auto-compaction is enabled at ${threshold}% and usage is above that threshold, subject to cooldown/repeat safeguards.`;
|
|
43
|
+
} else {
|
|
44
|
+
advice += ` UniPi percentage auto-compaction is enabled at ${threshold}%.`;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
36
48
|
const message = `Context: ~${percentFull}% full (estimated ${remaining.toLocaleString()} tokens remaining)`;
|
|
37
49
|
|
|
38
50
|
return { percentFull, remainingTokens: remaining, totalTokens: windowSize, message, advice };
|
|
@@ -42,8 +54,12 @@ export function estimateContextBudget(
|
|
|
42
54
|
* The context_budget tool handler.
|
|
43
55
|
* Called from the tool registration — receives tokensBefore from Pi context.
|
|
44
56
|
*/
|
|
45
|
-
export function contextBudgetTool(
|
|
46
|
-
|
|
57
|
+
export function contextBudgetTool(
|
|
58
|
+
tokensBefore?: number,
|
|
59
|
+
contextWindowSize?: number,
|
|
60
|
+
autoCompaction?: Pick<AutoCompactionConfig, "enabled" | "thresholdPercent">,
|
|
61
|
+
): string {
|
|
62
|
+
const budget = estimateContextBudget(tokensBefore, contextWindowSize, autoCompaction);
|
|
47
63
|
if (!budget) return "Context budget: Unknown (no token data available from session).";
|
|
48
64
|
|
|
49
65
|
return `${budget.message}\nAdvice: ${budget.advice}`;
|
package/src/tools/register.ts
CHANGED
|
@@ -291,17 +291,25 @@ export function registerCompactorTools(pi: ExtensionAPI, deps: CompactorToolDeps
|
|
|
291
291
|
label: "Context Budget",
|
|
292
292
|
description: "Estimate remaining context window (% full, tokens left) and get advice on whether to compact.",
|
|
293
293
|
parameters: Type.Object({}),
|
|
294
|
-
async execute(): Promise<import("@mariozechner/pi-coding-agent").AgentToolResult<unknown>> {
|
|
295
|
-
const
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
294
|
+
async execute(_toolCallId: string, _params: any, _signal?: AbortSignal, _onUpdate?: unknown, ctx?: ExtensionContext): Promise<import("@mariozechner/pi-coding-agent").AgentToolResult<unknown>> {
|
|
295
|
+
const config = loadConfig(ctx?.cwd ?? process.cwd());
|
|
296
|
+
const liveUsage = ctx?.getContextUsage?.();
|
|
297
|
+
let estimatedTokens: number | undefined = liveUsage?.tokens ?? undefined;
|
|
298
|
+
let contextWindow = liveUsage?.contextWindow;
|
|
299
|
+
|
|
300
|
+
if (estimatedTokens === undefined) {
|
|
301
|
+
const blocks = deps.getBlocks();
|
|
302
|
+
estimatedTokens = blocks.reduce((sum, b) => {
|
|
303
|
+
const text = b.kind === "tool_call"
|
|
304
|
+
? `${b.name} ${JSON.stringify((b as any).args ?? {})}`
|
|
305
|
+
: b.kind === "tool_result"
|
|
306
|
+
? `${b.name} ${(b as any).text ?? ""}`
|
|
307
|
+
: (b as any).text ?? "";
|
|
308
|
+
return sum + Math.ceil(text.length / 4);
|
|
309
|
+
}, 0);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const message = contextBudgetTool(estimatedTokens, contextWindow, config.autoCompaction);
|
|
305
313
|
return textResult(message);
|
|
306
314
|
},
|
|
307
315
|
} as any));
|
|
@@ -18,8 +18,8 @@ import { existsSync, unlinkSync } from "node:fs";
|
|
|
18
18
|
|
|
19
19
|
// ─── Section types ─────────────────────────────────────────────────────
|
|
20
20
|
|
|
21
|
-
type Section = "presets" | "strategies" | "pipeline";
|
|
22
|
-
const SECTIONS: Section[] = ["presets", "strategies", "pipeline"];
|
|
21
|
+
type Section = "presets" | "strategies" | "auto" | "pipeline";
|
|
22
|
+
const SECTIONS: Section[] = ["presets", "strategies", "auto", "pipeline"];
|
|
23
23
|
|
|
24
24
|
// ─── Strategy item definition ──────────────────────────────────────────
|
|
25
25
|
|
|
@@ -160,6 +160,10 @@ const PIPELINE_ITEMS: PipelineDef[] = [
|
|
|
160
160
|
|
|
161
161
|
const PRESETS: CompactorPreset[] = ["precise", "balanced", "thorough", "lean"];
|
|
162
162
|
|
|
163
|
+
const THRESHOLD_VALUES = ["60%", "70%", "75%", "80%", "85%", "90%", "95%"];
|
|
164
|
+
const COOLDOWN_VALUES = ["0s", "30s", "60s", "5m", "10m"];
|
|
165
|
+
const REPEAT_GROWTH_VALUES = ["0", "1k", "4k", "8k", "16k", "32k"];
|
|
166
|
+
|
|
163
167
|
const PRESET_DESCRIPTIONS: Record<string, { summary: string; detail: string }> = {
|
|
164
168
|
precise: {
|
|
165
169
|
summary: "Code-heavy, minimal waste — compaction: full, sandbox: safe-only",
|
|
@@ -207,6 +211,40 @@ function borderLine(innerWidth: number, edge: "top" | "bottom"): string {
|
|
|
207
211
|
return `\x1b[90m${left}${"─".repeat(innerWidth)}${right}\x1b[0m`;
|
|
208
212
|
}
|
|
209
213
|
|
|
214
|
+
function uniqueValues(values: string[]): string[] {
|
|
215
|
+
return [...new Set(values)];
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function formatPercent(value: number): string {
|
|
219
|
+
return `${Math.round(value)}%`;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function parsePercent(value: string): number {
|
|
223
|
+
return Number(value.replace("%", ""));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function formatCooldown(ms: number): string {
|
|
227
|
+
if (ms === 0) return "0s";
|
|
228
|
+
if (ms % 60_000 === 0) return `${ms / 60_000}m`;
|
|
229
|
+
return `${Math.round(ms / 1000)}s`;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function parseCooldown(value: string): number {
|
|
233
|
+
if (value.endsWith("m")) return Number(value.slice(0, -1)) * 60_000;
|
|
234
|
+
if (value.endsWith("s")) return Number(value.slice(0, -1)) * 1000;
|
|
235
|
+
return Number(value);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function formatGrowthTokens(tokens: number): string {
|
|
239
|
+
if (tokens >= 1000 && tokens % 1000 === 0) return `${tokens / 1000}k`;
|
|
240
|
+
return String(tokens);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function parseGrowthTokens(value: string): number {
|
|
244
|
+
if (value.endsWith("k")) return Number(value.slice(0, -1)) * 1000;
|
|
245
|
+
return Number(value);
|
|
246
|
+
}
|
|
247
|
+
|
|
210
248
|
// ─── Main component ────────────────────────────────────────────────────
|
|
211
249
|
|
|
212
250
|
/**
|
|
@@ -224,6 +262,7 @@ export class CompactorSettingsOverlay implements Component {
|
|
|
224
262
|
// Per-section SettingsList instances
|
|
225
263
|
private presetList!: SettingsList;
|
|
226
264
|
private strategyList!: SettingsList;
|
|
265
|
+
private autoList!: SettingsList;
|
|
227
266
|
private pipelineList!: SettingsList;
|
|
228
267
|
|
|
229
268
|
constructor(opts?: { cwd?: string }) {
|
|
@@ -290,6 +329,53 @@ export class CompactorSettingsOverlay implements Component {
|
|
|
290
329
|
{ enableSearch: true },
|
|
291
330
|
);
|
|
292
331
|
|
|
332
|
+
// ── Auto-compaction trigger list ──────────────────────────────────
|
|
333
|
+
const autoItems: SettingItem[] = [
|
|
334
|
+
{
|
|
335
|
+
id: "auto:enabled",
|
|
336
|
+
label: "Percentage Trigger",
|
|
337
|
+
description: "UniPi-managed auto-compaction based on context percentage",
|
|
338
|
+
currentValue: this.config.autoCompaction.enabled ? "on" : "off",
|
|
339
|
+
values: ["on", "off"],
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
id: "auto:thresholdPercent",
|
|
343
|
+
label: "Threshold",
|
|
344
|
+
description: "Trigger when Pi reports context usage at or above this percent",
|
|
345
|
+
currentValue: formatPercent(this.config.autoCompaction.thresholdPercent),
|
|
346
|
+
values: uniqueValues([...THRESHOLD_VALUES, formatPercent(this.config.autoCompaction.thresholdPercent)]),
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
id: "auto:cooldownMs",
|
|
350
|
+
label: "Cooldown",
|
|
351
|
+
description: "Minimum delay between UniPi-triggered compaction attempts",
|
|
352
|
+
currentValue: formatCooldown(this.config.autoCompaction.cooldownMs),
|
|
353
|
+
values: uniqueValues([...COOLDOWN_VALUES, formatCooldown(this.config.autoCompaction.cooldownMs)]),
|
|
354
|
+
},
|
|
355
|
+
{
|
|
356
|
+
id: "auto:repeatMinGrowthTokens",
|
|
357
|
+
label: "Repeat Growth",
|
|
358
|
+
description: "If still above threshold after compaction, require this many new tokens",
|
|
359
|
+
currentValue: formatGrowthTokens(this.config.autoCompaction.repeatMinGrowthTokens),
|
|
360
|
+
values: uniqueValues([...REPEAT_GROWTH_VALUES, formatGrowthTokens(this.config.autoCompaction.repeatMinGrowthTokens)]),
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
id: "auto:notify",
|
|
364
|
+
label: "Notifications",
|
|
365
|
+
description: "Notify when UniPi auto-compaction triggers or fails",
|
|
366
|
+
currentValue: this.config.autoCompaction.notify ? "on" : "off",
|
|
367
|
+
values: ["on", "off"],
|
|
368
|
+
},
|
|
369
|
+
];
|
|
370
|
+
|
|
371
|
+
this.autoList = new SettingsList(
|
|
372
|
+
autoItems,
|
|
373
|
+
8,
|
|
374
|
+
THEME,
|
|
375
|
+
(id, newValue) => this.onAutoChange(id, newValue),
|
|
376
|
+
() => this.onCancel(),
|
|
377
|
+
);
|
|
378
|
+
|
|
293
379
|
// ── Pipeline list ─────────────────────────────────────────────────
|
|
294
380
|
const pipelineItems: SettingItem[] = PIPELINE_ITEMS.map((p) => ({
|
|
295
381
|
id: `pipeline:${p.key}`,
|
|
@@ -312,6 +398,7 @@ export class CompactorSettingsOverlay implements Component {
|
|
|
312
398
|
|
|
313
399
|
private get currentList(): SettingsList {
|
|
314
400
|
if (this.section === "strategies") return this.strategyList;
|
|
401
|
+
if (this.section === "auto") return this.autoList;
|
|
315
402
|
if (this.section === "pipeline") return this.pipelineList;
|
|
316
403
|
return this.presetList;
|
|
317
404
|
}
|
|
@@ -332,8 +419,9 @@ export class CompactorSettingsOverlay implements Component {
|
|
|
332
419
|
const presetName = id.replace("preset:", "") as CompactorPreset;
|
|
333
420
|
if (PRESETS.includes(presetName)) {
|
|
334
421
|
this.config = applyPreset(presetName);
|
|
335
|
-
// Update all strategy/pipeline items to reflect new config
|
|
422
|
+
// Update all strategy/auto/pipeline items to reflect new config
|
|
336
423
|
this.refreshStrategyValues();
|
|
424
|
+
this.refreshAutoValues();
|
|
337
425
|
this.refreshPipelineValues();
|
|
338
426
|
// Update preset indicators
|
|
339
427
|
for (const name of PRESETS) {
|
|
@@ -366,6 +454,39 @@ export class CompactorSettingsOverlay implements Component {
|
|
|
366
454
|
}
|
|
367
455
|
}
|
|
368
456
|
|
|
457
|
+
private onAutoChange(id: string, newValue: string): void {
|
|
458
|
+
const key = id.replace("auto:", "");
|
|
459
|
+
switch (key) {
|
|
460
|
+
case "enabled":
|
|
461
|
+
this.config.autoCompaction.enabled = newValue === "on";
|
|
462
|
+
break;
|
|
463
|
+
case "thresholdPercent":
|
|
464
|
+
this.config.autoCompaction.thresholdPercent = parsePercent(newValue);
|
|
465
|
+
break;
|
|
466
|
+
case "cooldownMs":
|
|
467
|
+
this.config.autoCompaction.cooldownMs = parseCooldown(newValue);
|
|
468
|
+
break;
|
|
469
|
+
case "repeatMinGrowthTokens":
|
|
470
|
+
this.config.autoCompaction.repeatMinGrowthTokens = parseGrowthTokens(newValue);
|
|
471
|
+
break;
|
|
472
|
+
case "notify":
|
|
473
|
+
this.config.autoCompaction.notify = newValue === "on";
|
|
474
|
+
break;
|
|
475
|
+
default:
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
this.autoList.updateValue(id, this.formatAutoValue(key));
|
|
480
|
+
|
|
481
|
+
// Update preset indicators
|
|
482
|
+
for (const name of PRESETS) {
|
|
483
|
+
this.presetList.updateValue(
|
|
484
|
+
`preset:${name}`,
|
|
485
|
+
detectPreset(this.config) === name ? "✓ active" : "",
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
369
490
|
private onPipelineChange(id: string, newValue: string): void {
|
|
370
491
|
const key = id.replace("pipeline:", "");
|
|
371
492
|
const item = PIPELINE_ITEMS.find((p) => p.key === key);
|
|
@@ -395,12 +516,30 @@ export class CompactorSettingsOverlay implements Component {
|
|
|
395
516
|
return mode;
|
|
396
517
|
}
|
|
397
518
|
|
|
519
|
+
private formatAutoValue(key: string): string {
|
|
520
|
+
const auto = this.config.autoCompaction;
|
|
521
|
+
switch (key) {
|
|
522
|
+
case "enabled": return auto.enabled ? "on" : "off";
|
|
523
|
+
case "thresholdPercent": return formatPercent(auto.thresholdPercent);
|
|
524
|
+
case "cooldownMs": return formatCooldown(auto.cooldownMs);
|
|
525
|
+
case "repeatMinGrowthTokens": return formatGrowthTokens(auto.repeatMinGrowthTokens);
|
|
526
|
+
case "notify": return auto.notify ? "on" : "off";
|
|
527
|
+
default: return "";
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
398
531
|
private refreshStrategyValues(): void {
|
|
399
532
|
for (const s of STRATEGIES) {
|
|
400
533
|
this.strategyList.updateValue(`strategy:${s.key}`, this.formatStrategyValue(s));
|
|
401
534
|
}
|
|
402
535
|
}
|
|
403
536
|
|
|
537
|
+
private refreshAutoValues(): void {
|
|
538
|
+
for (const key of ["enabled", "thresholdPercent", "cooldownMs", "repeatMinGrowthTokens", "notify"]) {
|
|
539
|
+
this.autoList.updateValue(`auto:${key}`, this.formatAutoValue(key));
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
404
543
|
private refreshPipelineValues(): void {
|
|
405
544
|
for (const p of PIPELINE_ITEMS) {
|
|
406
545
|
this.pipelineList.updateValue(`pipeline:${p.key}`, p.getValue(this.config) ? "on" : "off");
|
package/src/types.ts
CHANGED
|
@@ -88,6 +88,20 @@ export interface CompactorStrategyConfig {
|
|
|
88
88
|
autoDetect?: "git" | null;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
/** UniPi-managed percentage auto-compaction trigger settings. */
|
|
92
|
+
export interface AutoCompactionConfig {
|
|
93
|
+
/** Enable the extension-managed percentage trigger. Disabled by default for backward compatibility. */
|
|
94
|
+
enabled: boolean;
|
|
95
|
+
/** Trigger when Pi reports context usage at or above this percent (0-100 scale). */
|
|
96
|
+
thresholdPercent: number;
|
|
97
|
+
/** Minimum delay between UniPi-triggered compaction attempts. */
|
|
98
|
+
cooldownMs: number;
|
|
99
|
+
/** When usage stays above threshold after compaction, require this many new tokens before repeating. */
|
|
100
|
+
repeatMinGrowthTokens: number;
|
|
101
|
+
/** Show user notifications for UniPi-triggered compaction attempts/results. */
|
|
102
|
+
notify: boolean;
|
|
103
|
+
}
|
|
104
|
+
|
|
91
105
|
export interface CompactorConfig {
|
|
92
106
|
// Compaction strategies
|
|
93
107
|
sessionGoals: CompactorStrategyConfig & { mode: "full" | "brief" | "off" };
|
|
@@ -112,6 +126,9 @@ export interface CompactorConfig {
|
|
|
112
126
|
customNoisePatterns: string[];
|
|
113
127
|
};
|
|
114
128
|
|
|
129
|
+
// Auto compaction trigger
|
|
130
|
+
autoCompaction: AutoCompactionConfig;
|
|
131
|
+
|
|
115
132
|
// Global settings
|
|
116
133
|
overrideDefaultCompaction: boolean;
|
|
117
134
|
debug: boolean;
|