@henryqw/pi-auto-compact 1.1.9 → 2.0.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 CHANGED
@@ -1,11 +1,11 @@
1
1
  # `@henryqw/pi-auto-compact`
2
2
 
3
- Compact context before it hits the configured threshold, then resume the current task.
3
+ Compact context before it reaches the configured threshold. Then resume the current task.
4
4
 
5
5
  ## Why
6
6
 
7
- - **Created for**: Compacting session context automatically before it reaches the configured threshold.
8
- - **Advantage**: Triggers early, uses a dedicated model, and resumes the interrupted task without manual intervention.
7
+ - **Created for**: Compact session context automatically before it reaches the configured threshold.
8
+ - **Advantage**: Trigger early, use a dedicated model, and resume the interrupted task without manual work.
9
9
 
10
10
  ## Install
11
11
 
@@ -14,14 +14,6 @@ pi install npm:@henryqw/pi-task-models
14
14
  pi install npm:@henryqw/pi-auto-compact
15
15
  ```
16
16
 
17
- Requires Pi Coding Agent 0.84.4+.
18
-
19
- ## With
20
-
21
- | Package | Why |
22
- | --- | --- |
23
- | `@henryqw/pi-task-models` | Required. Shared model profiles for compaction routes. |
24
-
25
17
  Disable Pi's built-in auto-compaction in `~/.pi/agent/settings.json`:
26
18
 
27
19
  ```json
@@ -34,17 +26,27 @@ Disable Pi's built-in auto-compaction in `~/.pi/agent/settings.json`:
34
26
 
35
27
  Restart Pi after install or settings changes. Trusted project settings in `.pi/settings.json` must not set `compaction.enabled` back to `true`.
36
28
 
29
+ ## With
30
+
31
+ This package requires `@henryqw/pi-task-models` for shared compaction routes.
32
+
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
+
35
+ Malformed shared config is reported and left unchanged. Compaction then uses the current session model.
36
+
37
37
  ## Use
38
38
 
39
- | Surface | Type | Purpose |
40
- | --- | --- | --- |
41
- | `/auto-compact` | command | Set the compaction threshold. |
39
+ Use `/auto-compact` to set the compaction threshold.
42
40
 
43
- Refuses to activate unless effective `compaction.enabled` is `false`. Checks `turn_start`, tool-call `turn_end`, `agent_end`, `context`, and resumed or forked `session_start`. Its consumer-owned `pi-auto-compact/autoCompact` task defaults to `fast`. Tries the effective profile primary, then fallback; if neither route works, the current session model still compacts. After mid-task compaction, a follow-up message continues the current task.
41
+ - It refuses to activate unless effective `compaction.enabled` is `false`.
42
+ - It checks `turn_start`, tool-call `turn_end`, `agent_end`, `context`, and resumed or forked `session_start`.
43
+ - Its consumer-owned `pi-auto-compact/autoCompact` task defaults to `fast`.
44
+ - It tries the effective profile primary, then fallback. If neither route works, the current session model still compacts.
45
+ - After mid-task compaction, a follow-up message continues the current task.
44
46
 
45
47
  ## Config
46
48
 
47
- Package-owned: `~/.pi/agent/config/pi-auto-compact.json`
49
+ Package-owned: `~/.pi/agent/config/pi-auto-compact/config.json`
48
50
 
49
51
  ```json
50
52
  {
@@ -52,10 +54,10 @@ Package-owned: `~/.pi/agent/config/pi-auto-compact.json`
52
54
  }
53
55
  ```
54
56
 
55
- | Field | Required | Possible values | Default |
56
- | --- | --- | --- | --- |
57
- | `autoCompactThreshold` | No | Number, at least 25, below 100 | `50` |
57
+ `autoCompactThreshold` is optional. It must be a number that is at least 25 and below 100. The default is `50`.
58
+
59
+ Unknown fields are ignored. Legacy model fields are obsolete. `/auto-compact` writes this file.
58
60
 
59
- Unknown fields are ignored (legacy model fields are obsolete). `/auto-compact` writes this file. 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 50%. Reads do not create it. A malformed or invalid file fails visibly at session start, falls back to 50%, and stays unchanged.
60
62
 
61
- Shared: `~/.pi/agent/config/pi-task-models.json`, owned by `@henryqw/pi-task-models`. The local `pi-auto-compact/autoCompact` declaration defaults to `fast`; a task entry is an explicit user override. Malformed shared task-model config is reported and left unchanged; compaction then uses the current session model.
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,
@@ -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,7 +283,8 @@ 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
290
  ctx.ui.notify("Couldn't read pi-auto-compact config; using 50%.", "error");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@henryqw/pi-auto-compact",
3
- "version": "1.1.9",
3
+ "version": "2.0.0",
4
4
  "description": "Proactively compact Pi context at a configurable threshold and resume the current task.",
5
5
  "keywords": [
6
6
  "pi-package",
@@ -14,9 +14,9 @@
14
14
  },
15
15
  "license": "MIT",
16
16
  "files": [
17
+ "LICENSE",
17
18
  "extensions",
18
- "README.md",
19
- "LICENSE"
19
+ "README.md"
20
20
  ],
21
21
  "scripts": {
22
22
  "test": "node --test test/*.test.ts",
@@ -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
  }