@henryqw/pi-auto-compact 0.1.2 → 0.2.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 +13 -1
- package/extensions/auto-compact.ts +83 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -26,6 +26,18 @@ Remove with:
|
|
|
26
26
|
pi remove npm:@henryqw/pi-auto-compact
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
+
## Configure
|
|
30
|
+
|
|
31
|
+
Run `/auto-compact`, then enter threshold percentage. Config lives in `~/.pi/agent/config/pi-auto-compact.json`:
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"autoCompactThreshold": 50
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Threshold must be at least 25% and below 100%; lower values are not meaningful. Missing config defaults to 50%. Restart or `/reload` after manual edits; command changes apply immediately.
|
|
40
|
+
|
|
29
41
|
## Behavior
|
|
30
42
|
|
|
31
43
|
- Refuses activation with an error when Pi's effective `compaction.enabled` setting is not `false`; competing automatic compactors can start duplicate summaries.
|
|
@@ -33,7 +45,7 @@ pi remove npm:@henryqw/pi-auto-compact
|
|
|
33
45
|
- Uses Pi's default `ctx.compact()` summary and session persistence.
|
|
34
46
|
- Keeps newest 15% as temporary emergency context while compaction runs.
|
|
35
47
|
- Sends a follow-up message after compaction so task execution continues.
|
|
36
|
-
-
|
|
48
|
+
- Compacts above configured `autoCompactThreshold` percentage (50% by default).
|
|
37
49
|
|
|
38
50
|
`ctx.compact()` aborts current low-level run. Extension hides that empty internal abort message, then starts new run with current task resume message. Other aborts and provider errors remain visible.
|
|
39
51
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
1
3
|
import {
|
|
2
4
|
estimateTokens,
|
|
3
5
|
getAgentDir,
|
|
@@ -19,7 +21,39 @@ type AgentMessage = Parameters<typeof estimateTokens>[0];
|
|
|
19
21
|
* Pi's ctx.compact() aborts active low-level run internally. Its completion
|
|
20
22
|
* callback sends follow-up user message, which resumes task after summary.
|
|
21
23
|
*/
|
|
22
|
-
const
|
|
24
|
+
const DEFAULT_COMPACT_THRESHOLD_PERCENT = 50;
|
|
25
|
+
const MIN_COMPACT_THRESHOLD_PERCENT = 25;
|
|
26
|
+
const configPath = () => join(getAgentDir(), "config", "pi-auto-compact.json");
|
|
27
|
+
|
|
28
|
+
function isValidThreshold(value: unknown): value is number {
|
|
29
|
+
return typeof value === "number" && Number.isFinite(value) && value >= MIN_COMPACT_THRESHOLD_PERCENT && value < 100;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function readConfig(): { autoCompactThreshold: number } {
|
|
33
|
+
let value: unknown;
|
|
34
|
+
try {
|
|
35
|
+
value = JSON.parse(readFileSync(configPath(), "utf8"));
|
|
36
|
+
} catch (error) {
|
|
37
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
38
|
+
return { autoCompactThreshold: DEFAULT_COMPACT_THRESHOLD_PERCENT };
|
|
39
|
+
}
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
43
|
+
throw new Error("Config must be an object.");
|
|
44
|
+
}
|
|
45
|
+
const threshold = (value as Record<string, unknown>).autoCompactThreshold ?? DEFAULT_COMPACT_THRESHOLD_PERCENT;
|
|
46
|
+
if (!isValidThreshold(threshold)) {
|
|
47
|
+
throw new Error(`autoCompactThreshold must be at least ${MIN_COMPACT_THRESHOLD_PERCENT} and below 100.`);
|
|
48
|
+
}
|
|
49
|
+
return { autoCompactThreshold: threshold };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function writeConfig(autoCompactThreshold: number): void {
|
|
53
|
+
const file = configPath();
|
|
54
|
+
mkdirSync(dirname(file), { recursive: true });
|
|
55
|
+
writeFileSync(file, `${JSON.stringify({ autoCompactThreshold }, null, 2)}\n`);
|
|
56
|
+
}
|
|
23
57
|
|
|
24
58
|
// Emergency context guard keeps recent messages while default compaction runs.
|
|
25
59
|
const KEEP_RECENT_PERCENT = 15;
|
|
@@ -84,6 +118,7 @@ function hasToolCall(message: AgentMessage): boolean {
|
|
|
84
118
|
|
|
85
119
|
export default function (pi: ExtensionAPI) {
|
|
86
120
|
let active = false;
|
|
121
|
+
let autoCompactThreshold = DEFAULT_COMPACT_THRESHOLD_PERCENT;
|
|
87
122
|
// Prevent turn_start, turn_end, and context from starting duplicate summaries.
|
|
88
123
|
let compactionPending = false;
|
|
89
124
|
let compactionAbortExpected = false;
|
|
@@ -111,7 +146,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
111
146
|
if (!active || compactionPending) return;
|
|
112
147
|
|
|
113
148
|
const usage = ctx.getContextUsage();
|
|
114
|
-
if (usage?.percent == null || usage.percent <=
|
|
149
|
+
if (usage?.percent == null || usage.percent <= autoCompactThreshold) return;
|
|
115
150
|
|
|
116
151
|
compactionPending = true;
|
|
117
152
|
runCompaction(ctx);
|
|
@@ -154,7 +189,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
154
189
|
|
|
155
190
|
const contextWindow = ctx.getContextUsage()?.contextWindow ?? ctx.model?.contextWindow ?? 0;
|
|
156
191
|
const estimatedTokens = estimateTotalTokens(event.messages);
|
|
157
|
-
if (contextWindow <= 0 || estimatedTokens <= contextWindow *
|
|
192
|
+
if (contextWindow <= 0 || estimatedTokens <= contextWindow * autoCompactThreshold / 100) return;
|
|
158
193
|
|
|
159
194
|
const truncated = keepRecent(
|
|
160
195
|
event.messages,
|
|
@@ -169,9 +204,54 @@ export default function (pi: ExtensionAPI) {
|
|
|
169
204
|
return { messages: truncated };
|
|
170
205
|
});
|
|
171
206
|
|
|
207
|
+
pi.registerCommand("auto-compact", {
|
|
208
|
+
description: "set automatic compaction threshold",
|
|
209
|
+
handler: async (_args, ctx) => {
|
|
210
|
+
let current: number;
|
|
211
|
+
try {
|
|
212
|
+
current = readConfig().autoCompactThreshold;
|
|
213
|
+
} catch {
|
|
214
|
+
ctx.ui.notify("Couldn't read pi-auto-compact config.", "error");
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const input = await ctx.ui.input(
|
|
219
|
+
`Auto-compact threshold (%) · current: ${current}`,
|
|
220
|
+
"Enter a number above 0 and below 100",
|
|
221
|
+
);
|
|
222
|
+
if (input === undefined) return;
|
|
223
|
+
|
|
224
|
+
const threshold = Number(input.trim());
|
|
225
|
+
if (Number.isFinite(threshold) && threshold < MIN_COMPACT_THRESHOLD_PERCENT) {
|
|
226
|
+
ctx.ui.notify("Auto-compact threshold below 25% is not meaningful.", "error");
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
if (!isValidThreshold(threshold)) {
|
|
230
|
+
ctx.ui.notify("Threshold must be at least 25% and below 100%.", "error");
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
writeConfig(threshold);
|
|
236
|
+
} catch {
|
|
237
|
+
ctx.ui.notify("Couldn't save pi-auto-compact config.", "error");
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
autoCompactThreshold = threshold;
|
|
241
|
+
ctx.ui.notify(`Auto-compact threshold set to ${threshold}%.`, "info");
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
|
|
172
245
|
// Pi's built-in automatic compaction competes with this extension. Refuse
|
|
173
246
|
// activation unless effective global/project settings disable it.
|
|
174
247
|
pi.on("session_start", (event, ctx) => {
|
|
248
|
+
try {
|
|
249
|
+
autoCompactThreshold = readConfig().autoCompactThreshold;
|
|
250
|
+
} catch {
|
|
251
|
+
autoCompactThreshold = DEFAULT_COMPACT_THRESHOLD_PERCENT;
|
|
252
|
+
ctx.ui.notify("Couldn't read pi-auto-compact config; using 50%.", "error");
|
|
253
|
+
}
|
|
254
|
+
|
|
175
255
|
active = !SettingsManager.create(ctx.cwd, getAgentDir(), {
|
|
176
256
|
projectTrusted: ctx.isProjectTrusted(),
|
|
177
257
|
}).getCompactionEnabled();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@henryqw/pi-auto-compact",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Proactively compact Pi context at
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Proactively compact Pi context at a configurable threshold and resume the current task.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
7
7
|
"pi",
|