@howaboua/pi-notes-compaction 0.0.1 → 0.0.3
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/CHANGELOG.md +8 -0
- package/README.md +7 -7
- package/package.json +1 -1
- package/src/index.ts +41 -54
- package/src/settings-screen.ts +115 -0
- package/src/settings.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @howaboua/pi-notes-compaction
|
|
2
2
|
|
|
3
|
+
## 0.0.3
|
|
4
|
+
|
|
5
|
+
- Fixed shared agent startup after package updates when the startup changelog is present.
|
|
6
|
+
|
|
7
|
+
## 0.0.2
|
|
8
|
+
|
|
9
|
+
- Added a `/notes` settings panel for normal compaction, idle rollover, continuity status and confirmed pruning, replacing the subcommands and JSON status output.
|
|
10
|
+
|
|
3
11
|
## 0.0.1
|
|
4
12
|
|
|
5
13
|
- Added Notes Compaction: local checkpoint notes, searchable session history and notes-guided context windows for any Pi model, with optional Codex Conversion integration and cached remote lookup.
|
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ pi install npm:@howaboua/pi-notes-compaction
|
|
|
12
12
|
|
|
13
13
|
Reload Pi, then ask the agent to save a checkpoint and start a new context window. It can read those notes and search earlier session history to continue.
|
|
14
14
|
|
|
15
|
-
With Pi Codex Conversion 3.0.48 or newer, local notes work alongside Codex tools, Code Mode, Notebook and normal compaction. Older PCC versions without this integration leave the extension inactive. Run `/notes
|
|
15
|
+
With Pi Codex Conversion 3.0.48 or newer, local notes work alongside Codex tools, Code Mode, Notebook and normal compaction. Older PCC versions without this integration leave the extension inactive. Run `/notes` to check activation and available capabilities. Extension authors can read the [integration contract](PCC-INTEGRATION.md).
|
|
16
16
|
|
|
17
17
|
## Notes and windows
|
|
18
18
|
|
|
@@ -22,15 +22,15 @@ By default, `new_context` starts a fresh window without generating a summary. Th
|
|
|
22
22
|
|
|
23
23
|
Reminders arrive at 85% and 90% context use. `/compact` requests a notes checkpoint and opens a new window only after a completed run with fresh saved notes. Abort, errors and failed writes retain the old context. Overflow recovery still uses Pi's normal compaction.
|
|
24
24
|
|
|
25
|
-
Idle rollover is off by default.
|
|
25
|
+
Idle rollover is off by default. Choose 5, 15, 25 or 55 minutes in `/notes`. After that interval, the next input waits for rollover only if the latest completed turn saved fresh notes. Otherwise it proceeds in the current window without requesting a checkpoint. These intervals are user preferences, not provider cache-expiry guarantees. Held input retains its original text, attachments and SDK options. A failed rollover keeps input pending. Submit another prompt to retry, or reload to cancel it.
|
|
26
26
|
|
|
27
27
|
## Management
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
-
|
|
29
|
+
`/notes` opens the settings panel in interactive Pi. It shows continuity status, storage counts and remote capabilities. Changes save immediately.
|
|
30
|
+
|
|
31
|
+
- **Normal compaction** includes the installed Pi or PCC compaction flow on `new_context`. Off keeps notes-only rollover, the default.
|
|
32
|
+
- **Idle rollover** selects the interval or disables it.
|
|
33
|
+
- **Prune missing sessions** removes records for missing session files after confirmation. Unknown paths, permission errors and the active session are retained.
|
|
34
34
|
|
|
35
35
|
Notes are stored as plaintext in `<Pi agent directory>/notes-compaction/notes.sqlite`, honoring `PI_CODING_AGENT_DIR`. Saved session snapshots preserve notes across forks and branch navigation, including after database pruning. Existing plaintext PCC notes remain readable without an import.
|
|
36
36
|
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -12,7 +12,8 @@ import {
|
|
|
12
12
|
} from "./bridge.js";
|
|
13
13
|
import { NotesLifecycle } from "./lifecycle.js";
|
|
14
14
|
import { exportNotesSnapshot, sessionRef } from "./notes.js";
|
|
15
|
-
import {
|
|
15
|
+
import { type NotesSettings, readSettings, writeSettings } from "./settings.js";
|
|
16
|
+
import { openNotesSettings } from "./settings-screen.js";
|
|
16
17
|
import { NotesStore } from "./store.js";
|
|
17
18
|
import {
|
|
18
19
|
createSharedExecutor,
|
|
@@ -26,9 +27,6 @@ import {
|
|
|
26
27
|
windowDetails,
|
|
27
28
|
} from "./windows.js";
|
|
28
29
|
|
|
29
|
-
const HELP =
|
|
30
|
-
"Use /notes status, /notes prune, /notes compact on|off, or /notes idle off|5|15|25|55 (minutes). Compaction and idle rollover default off. Compaction adds the normal installed Pi/PCC compaction flow to new_context. Idle intervals are not provider cache-expiry guarantees.";
|
|
31
|
-
|
|
32
30
|
export default function notesCompaction(pi: ExtensionAPI): void {
|
|
33
31
|
registerWindowRenderer(pi);
|
|
34
32
|
const dir = join(getAgentDir(), "notes-compaction");
|
|
@@ -49,6 +47,12 @@ export default function notesCompaction(pi: ExtensionAPI): void {
|
|
|
49
47
|
lifecycle.active &&
|
|
50
48
|
ctx.isIdle() &&
|
|
51
49
|
ctx.sessionManager.getEntries().every((entry) => {
|
|
50
|
+
// The first startup after an update records a UI-only changelog.
|
|
51
|
+
if (
|
|
52
|
+
entry.type === "custom" &&
|
|
53
|
+
entry.customType === "@howaboua/pi-stuff/changelog"
|
|
54
|
+
)
|
|
55
|
+
return true;
|
|
52
56
|
// Herdr activation persists its empty registry before first task delivery.
|
|
53
57
|
if (
|
|
54
58
|
entry.type === "custom" &&
|
|
@@ -209,66 +213,49 @@ export default function notesCompaction(pi: ExtensionAPI): void {
|
|
|
209
213
|
pi.registerCommand("notes", {
|
|
210
214
|
description: "Manage local notes and context rollover",
|
|
211
215
|
handler: async (args, ctx) => {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
ctx.ui.notify(HELP, "info");
|
|
216
|
+
if (args.trim()) {
|
|
217
|
+
ctx.ui.notify("Use /notes to open settings.", "info");
|
|
215
218
|
return;
|
|
216
219
|
}
|
|
217
|
-
if (
|
|
218
|
-
ctx.ui.notify(
|
|
219
|
-
JSON.stringify({
|
|
220
|
-
active: lifecycle.active,
|
|
221
|
-
...(unavailable ? { unavailable } : {}),
|
|
222
|
-
normalCompaction: lifecycle.normalCompaction,
|
|
223
|
-
idleMinutes: lifecycle.idleMinutes,
|
|
224
|
-
...store.status(),
|
|
225
|
-
database: store.path,
|
|
226
|
-
hybridLookup: Boolean(lifecycle.bridge?.lookup),
|
|
227
|
-
encryptedDelivery: Boolean(lifecycle.bridge?.projectResult),
|
|
228
|
-
}),
|
|
229
|
-
"info",
|
|
230
|
-
);
|
|
220
|
+
if (!ctx.hasUI) {
|
|
221
|
+
ctx.ui.notify("Notes settings require interactive Pi.", "warning");
|
|
231
222
|
return;
|
|
232
223
|
}
|
|
233
|
-
|
|
234
|
-
writeSettings(settingsPath,
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
lifecycle.normalCompaction = command === "compact on";
|
|
239
|
-
ctx.ui.notify(
|
|
240
|
-
`Normal compaction ${lifecycle.normalCompaction ? "enabled" : "disabled"}.`,
|
|
241
|
-
"info",
|
|
242
|
-
);
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
245
|
-
const idle = IDLE_MINUTES.find(
|
|
246
|
-
(minutes) => command === `idle ${minutes === 0 ? "off" : minutes}`,
|
|
247
|
-
);
|
|
248
|
-
if (idle !== undefined) {
|
|
249
|
-
writeSettings(settingsPath, {
|
|
250
|
-
normalCompaction: lifecycle.normalCompaction,
|
|
251
|
-
idleMinutes: idle,
|
|
252
|
-
});
|
|
253
|
-
lifecycle.idleMinutes = idle;
|
|
254
|
-
ctx.ui.notify(
|
|
255
|
-
idle === 0
|
|
256
|
-
? "Idle rollover disabled."
|
|
257
|
-
: `Idle rollover after ${idle} minutes, when fresh notes are available. This is not a cache-expiry guarantee.`,
|
|
258
|
-
"info",
|
|
259
|
-
);
|
|
260
|
-
return;
|
|
261
|
-
}
|
|
262
|
-
if (command === "prune") {
|
|
224
|
+
const save = (settings: NotesSettings) => {
|
|
225
|
+
writeSettings(settingsPath, settings);
|
|
226
|
+
Object.assign(lifecycle, settings);
|
|
227
|
+
};
|
|
228
|
+
const prune = async () => {
|
|
263
229
|
await ctx.waitForIdle();
|
|
264
230
|
store.updateFile(sessionRef(ctx));
|
|
231
|
+
const result = store.prune(ctx.sessionManager.getSessionId());
|
|
265
232
|
ctx.ui.notify(
|
|
266
|
-
|
|
233
|
+
`Pruned ${result.removed} sessions. Retained ${result.unknownPaths} unknown paths and ${result.inaccessible} inaccessible files.`,
|
|
267
234
|
"info",
|
|
268
235
|
);
|
|
269
|
-
|
|
236
|
+
};
|
|
237
|
+
try {
|
|
238
|
+
await openNotesSettings(ctx, {
|
|
239
|
+
settings: () => ({
|
|
240
|
+
normalCompaction: lifecycle.normalCompaction,
|
|
241
|
+
idleMinutes: lifecycle.idleMinutes,
|
|
242
|
+
}),
|
|
243
|
+
save,
|
|
244
|
+
prune,
|
|
245
|
+
status: () => {
|
|
246
|
+
const counts = store.status();
|
|
247
|
+
return [
|
|
248
|
+
lifecycle.active
|
|
249
|
+
? "Continuity active"
|
|
250
|
+
: `Continuity inactive: ${unavailable ?? "not initialized"}`,
|
|
251
|
+
`${counts.sessions} sessions · ${counts.revisions} note revisions · ${counts.cachedResponses} cached responses`,
|
|
252
|
+
`Hybrid lookup: ${lifecycle.bridge?.lookup ? "available" : "unavailable"} · Encrypted delivery: ${lifecycle.bridge?.projectResult ? "available" : "unavailable"}`,
|
|
253
|
+
];
|
|
254
|
+
},
|
|
255
|
+
});
|
|
256
|
+
} catch (error) {
|
|
257
|
+
ctx.ui.notify(`Notes settings failed: ${String(error)}`, "error");
|
|
270
258
|
}
|
|
271
|
-
ctx.ui.notify(HELP, "warning");
|
|
272
259
|
},
|
|
273
260
|
});
|
|
274
261
|
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ExtensionContext,
|
|
3
|
+
getSettingsListTheme,
|
|
4
|
+
} from "@earendil-works/pi-coding-agent";
|
|
5
|
+
import { SettingsList, truncateToWidth } from "@earendil-works/pi-tui";
|
|
6
|
+
import { IDLE_MINUTES, type NotesSettings } from "./settings.js";
|
|
7
|
+
|
|
8
|
+
export async function openNotesSettings(
|
|
9
|
+
ctx: ExtensionContext,
|
|
10
|
+
options: {
|
|
11
|
+
settings: () => NotesSettings;
|
|
12
|
+
save: (settings: NotesSettings) => void;
|
|
13
|
+
status: () => string[];
|
|
14
|
+
prune: () => Promise<void>;
|
|
15
|
+
},
|
|
16
|
+
): Promise<void> {
|
|
17
|
+
while (true) {
|
|
18
|
+
const status = options.status();
|
|
19
|
+
const action = await ctx.ui.custom<"close" | "prune">(
|
|
20
|
+
(tui, theme, _kb, done) => {
|
|
21
|
+
const settings = options.settings();
|
|
22
|
+
const idleValue = (minutes: number) =>
|
|
23
|
+
minutes === 0 ? "off" : `${minutes} min`;
|
|
24
|
+
const list = new SettingsList(
|
|
25
|
+
[
|
|
26
|
+
{
|
|
27
|
+
id: "compact",
|
|
28
|
+
label: "Normal compaction",
|
|
29
|
+
description:
|
|
30
|
+
"Include the installed Pi/PCC compaction flow on new_context. Off keeps notes-only rollover.",
|
|
31
|
+
currentValue: settings.normalCompaction ? "on" : "off",
|
|
32
|
+
values: ["off", "on"],
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: "idle",
|
|
36
|
+
label: "Idle rollover",
|
|
37
|
+
description:
|
|
38
|
+
"Roll over before the next input only with fresh notes from a completed turn. Not a cache-expiry guarantee.",
|
|
39
|
+
currentValue: idleValue(settings.idleMinutes),
|
|
40
|
+
values: IDLE_MINUTES.map(idleValue),
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "prune",
|
|
44
|
+
label: "Prune missing sessions",
|
|
45
|
+
description:
|
|
46
|
+
"Remove stored records only for missing session files. Keeps active sessions, unknown paths and inaccessible files. Confirmation required.",
|
|
47
|
+
currentValue: "select…",
|
|
48
|
+
values: ["select…", "prune…"],
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
5,
|
|
52
|
+
getSettingsListTheme(),
|
|
53
|
+
(id, value) => {
|
|
54
|
+
if (id === "prune") {
|
|
55
|
+
done("prune");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const current = options.settings();
|
|
59
|
+
try {
|
|
60
|
+
if (id === "compact")
|
|
61
|
+
options.save({ ...current, normalCompaction: value === "on" });
|
|
62
|
+
else {
|
|
63
|
+
const idleMinutes = IDLE_MINUTES.find(
|
|
64
|
+
(minutes) => idleValue(minutes) === value,
|
|
65
|
+
);
|
|
66
|
+
if (idleMinutes !== undefined)
|
|
67
|
+
options.save({ ...current, idleMinutes });
|
|
68
|
+
}
|
|
69
|
+
} catch (error) {
|
|
70
|
+
list.updateValue(
|
|
71
|
+
id,
|
|
72
|
+
id === "compact"
|
|
73
|
+
? current.normalCompaction
|
|
74
|
+
? "on"
|
|
75
|
+
: "off"
|
|
76
|
+
: idleValue(current.idleMinutes),
|
|
77
|
+
);
|
|
78
|
+
ctx.ui.notify(
|
|
79
|
+
`Could not save notes settings: ${String(error)}`,
|
|
80
|
+
"error",
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
tui.requestRender();
|
|
84
|
+
},
|
|
85
|
+
() => done("close"),
|
|
86
|
+
{ enableSearch: false },
|
|
87
|
+
);
|
|
88
|
+
return {
|
|
89
|
+
render: (width) =>
|
|
90
|
+
[
|
|
91
|
+
theme.fg("accent", "Notes & context"),
|
|
92
|
+
...status.map((line) => theme.fg("dim", line)),
|
|
93
|
+
"",
|
|
94
|
+
...list.render(width),
|
|
95
|
+
"",
|
|
96
|
+
theme.fg(
|
|
97
|
+
"dim",
|
|
98
|
+
"↑↓ navigate · Enter/Space change · Esc close · saved immediately",
|
|
99
|
+
),
|
|
100
|
+
].map((line) => truncateToWidth(line, width)),
|
|
101
|
+
invalidate: () => list.invalidate(),
|
|
102
|
+
handleInput: (data) => list.handleInput(data),
|
|
103
|
+
};
|
|
104
|
+
},
|
|
105
|
+
);
|
|
106
|
+
if (action === "close") return;
|
|
107
|
+
if (
|
|
108
|
+
await ctx.ui.confirm(
|
|
109
|
+
"Prune missing sessions?",
|
|
110
|
+
"Delete local notes and cached responses for missing session files? The active session, unknown paths and inaccessible files are retained.",
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
await options.prune();
|
|
114
|
+
}
|
|
115
|
+
}
|
package/src/settings.ts
CHANGED
|
@@ -25,7 +25,7 @@ export function readSettings(path: string): NotesSettings {
|
|
|
25
25
|
typeof settings.normalCompaction !== "boolean"
|
|
26
26
|
)
|
|
27
27
|
throw new Error(
|
|
28
|
-
"Invalid notes settings.
|
|
28
|
+
"Invalid notes settings. Save a setting in /notes, then reload Pi.",
|
|
29
29
|
);
|
|
30
30
|
const idleMinutes = "idleMinutes" in settings ? settings.idleMinutes : 0;
|
|
31
31
|
if (!IDLE_MINUTES.some((value) => value === idleMinutes))
|