@henryqw/pi-auto-compact 1.1.10 → 2.0.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 CHANGED
@@ -30,9 +30,9 @@ Restart Pi after install or settings changes. Trusted project settings in `.pi/s
30
30
 
31
31
  This package requires `@henryqw/pi-task-models` for shared compaction routes.
32
32
 
33
- `~/.pi/agent/config/pi-task-models.json` is shared and owned by `@henryqw/pi-task-models`. The local `pi-auto-compact/autoCompact` declaration defaults to `fast`. A task entry is an explicit user override.
33
+ `~/.pi/agent/config/pi-task-models/config.json` is shared and owned by `@henryqw/pi-task-models`. The local `pi-auto-compact/autoCompact` declaration defaults to `fast`. A task entry is an explicit user override.
34
34
 
35
- Malformed shared task-model config is reported and left unchanged. Compaction then uses the current session model.
35
+ Malformed shared config is reported and left unchanged. Compaction then uses the current session model.
36
36
 
37
37
  ## Use
38
38
 
@@ -46,16 +46,18 @@ Use `/auto-compact` to set the compaction threshold.
46
46
 
47
47
  ## Config
48
48
 
49
- Package-owned: `~/.pi/agent/config/pi-auto-compact.json`
49
+ Package-owned: `~/.pi/agent/config/pi-auto-compact/config.json`
50
50
 
51
51
  ```json
52
52
  {
53
- "autoCompactThreshold": 50
53
+ "autoCompactThreshold": 70
54
54
  }
55
55
  ```
56
56
 
57
- `autoCompactThreshold` is optional. It must be a number that is at least 25 and below 100. The default is `50`.
57
+ `autoCompactThreshold` is optional. It must be a number that is at least 25 and below 100. The default is `70`.
58
58
 
59
59
  Unknown fields are ignored. Legacy model fields are obsolete. `/auto-compact` writes this file.
60
60
 
61
- A missing file uses the default. A malformed or invalid file fails visibly at session start, falls back to 50%, and stays unchanged.
61
+ A missing file uses 70%. Reads do not create it. A malformed or invalid file fails visibly at session start, falls back to 70%, and stays unchanged.
62
+
63
+ Only `/auto-compact` writes this file. Its write is atomic.
@@ -1,11 +1,10 @@
1
- import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
- import { dirname, join } from "node:path";
3
1
  import {
4
2
  compact,
5
3
  estimateTokens,
6
4
  getAgentDir,
7
5
  SettingsManager,
8
6
  } from "@earendil-works/pi-coding-agent";
7
+ import { createConfigStore } from "@henryqw/pi-config-store";
9
8
  import type {
10
9
  ExtensionAPI,
11
10
  ExtensionContext,
@@ -30,7 +29,7 @@ type AgentMessage = Parameters<typeof estimateTokens>[0];
30
29
  * Pi's ctx.compact() aborts active low-level run internally. Mid-task
31
30
  * compaction sends a follow-up user message to resume work after summary.
32
31
  */
33
- const DEFAULT_COMPACT_THRESHOLD_PERCENT = 50;
32
+ const DEFAULT_COMPACT_THRESHOLD_PERCENT = 70;
34
33
  const MIN_COMPACT_THRESHOLD_PERCENT = 25;
35
34
  export const AUTO_COMPACT_TASK = {
36
35
  id: "pi-auto-compact/autoCompact",
@@ -38,22 +37,13 @@ export const AUTO_COMPACT_TASK = {
38
37
  purpose: "Compact session context before it is exhausted.",
39
38
  defaultProfile: "fast",
40
39
  } as const satisfies ModelTask;
41
- const configPath = () => join(getAgentDir(), "config", "pi-auto-compact.json");
40
+ type AutoCompactConfig = { autoCompactThreshold: number };
42
41
 
43
42
  function isValidThreshold(value: unknown): value is number {
44
43
  return typeof value === "number" && Number.isFinite(value) && value >= MIN_COMPACT_THRESHOLD_PERCENT && value < 100;
45
44
  }
46
45
 
47
- function readConfig(): number {
48
- let value: unknown;
49
- try {
50
- value = JSON.parse(readFileSync(configPath(), "utf8"));
51
- } catch (error) {
52
- if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
53
- return DEFAULT_COMPACT_THRESHOLD_PERCENT;
54
- }
55
- throw error;
56
- }
46
+ function parseAutoCompactConfig(value: unknown): AutoCompactConfig {
57
47
  if (!value || typeof value !== "object" || Array.isArray(value)) {
58
48
  throw new Error("Config must be an object.");
59
49
  }
@@ -61,13 +51,15 @@ function readConfig(): number {
61
51
  if (!isValidThreshold(threshold)) {
62
52
  throw new Error(`autoCompactThreshold must be at least ${MIN_COMPACT_THRESHOLD_PERCENT} and below 100.`);
63
53
  }
64
- return threshold;
54
+ return { autoCompactThreshold: threshold };
65
55
  }
66
56
 
67
- function writeConfig(threshold: number): void {
68
- const file = configPath();
69
- mkdirSync(dirname(file), { recursive: true });
70
- writeFileSync(file, `${JSON.stringify({ autoCompactThreshold: threshold }, null, 2)}\n`);
57
+ function createAutoCompactConfigStore() {
58
+ return createConfigStore<AutoCompactConfig>({
59
+ extensionId: "pi-auto-compact",
60
+ defaults: () => ({ autoCompactThreshold: DEFAULT_COMPACT_THRESHOLD_PERCENT }),
61
+ parse: parseAutoCompactConfig,
62
+ });
71
63
  }
72
64
 
73
65
  function configuredTaskRoutes(ctx: ExtensionContext): ResolvedTaskRoute[] {
@@ -75,6 +67,7 @@ function configuredTaskRoutes(ctx: ExtensionContext): ResolvedTaskRoute[] {
75
67
  return resolveConfiguredTaskRoutes(ctx, AUTO_COMPACT_TASK);
76
68
  } catch (error) {
77
69
  const { taskRouteCode, profileName } = error as TaskRouteError;
70
+ if (taskRouteCode === "config-missing") return [];
78
71
  const cause = taskRouteCode === "profile-missing"
79
72
  ? `Task model profile ${profileName} is not configured`
80
73
  : taskRouteCode === "no-route"
@@ -155,6 +148,7 @@ function hasToolCall(message: AgentMessage): boolean {
155
148
 
156
149
  export default function (pi: ExtensionAPI) {
157
150
  registerModelTask(pi, AUTO_COMPACT_TASK);
151
+ const configStore = createAutoCompactConfigStore();
158
152
  let active = false;
159
153
  let autoCompactThreshold = DEFAULT_COMPACT_THRESHOLD_PERCENT;
160
154
  // Prevent lifecycle hooks from starting duplicate summaries.
@@ -256,7 +250,7 @@ export default function (pi: ExtensionAPI) {
256
250
 
257
251
  let currentThreshold: number;
258
252
  try {
259
- currentThreshold = readConfig();
253
+ currentThreshold = configStore.loadSync().value.autoCompactThreshold;
260
254
  } catch {
261
255
  ctx.ui.notify("Couldn't read pi-auto-compact config.", "error");
262
256
  return;
@@ -275,7 +269,7 @@ export default function (pi: ExtensionAPI) {
275
269
  }
276
270
 
277
271
  try {
278
- writeConfig(threshold);
272
+ await configStore.save({ autoCompactThreshold: threshold });
279
273
  } catch {
280
274
  ctx.ui.notify("Couldn't save pi-auto-compact config.", "error");
281
275
  return;
@@ -289,10 +283,11 @@ export default function (pi: ExtensionAPI) {
289
283
  // activation unless effective global/project settings disable it.
290
284
  pi.on("session_start", (event, ctx) => {
291
285
  try {
292
- autoCompactThreshold = readConfig();
286
+ const config = configStore.loadSync();
287
+ autoCompactThreshold = config.value.autoCompactThreshold;
293
288
  } catch {
294
289
  autoCompactThreshold = DEFAULT_COMPACT_THRESHOLD_PERCENT;
295
- ctx.ui.notify("Couldn't read pi-auto-compact config; using 50%.", "error");
290
+ ctx.ui.notify(`Couldn't read pi-auto-compact config; using ${DEFAULT_COMPACT_THRESHOLD_PERCENT}%.`, "error");
296
291
  }
297
292
 
298
293
  active = !SettingsManager.create(ctx.cwd, getAgentDir(), {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@henryqw/pi-auto-compact",
3
- "version": "1.1.10",
3
+ "version": "2.0.1",
4
4
  "description": "Proactively compact Pi context at a configurable threshold and resume the current task.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -44,6 +44,7 @@
44
44
  ]
45
45
  },
46
46
  "dependencies": {
47
- "@henryqw/pi-task-models": "^3.0.0"
47
+ "@henryqw/pi-config-store": "^0.1.0",
48
+ "@henryqw/pi-task-models": "^4.0.0"
48
49
  }
49
50
  }