pi-goosedump 0.12.7 → 0.12.8
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 -5
- package/dist/index.ts +79 -26
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -71,7 +71,8 @@ Optional settings live under `goosedump` in:
|
|
|
71
71
|
```json
|
|
72
72
|
{
|
|
73
73
|
"goosedump": {
|
|
74
|
-
"turnsBeforeCompact": 0
|
|
74
|
+
"turnsBeforeCompact": 0,
|
|
75
|
+
"summaryMaxTokens": 4096
|
|
75
76
|
}
|
|
76
77
|
}
|
|
77
78
|
```
|
|
@@ -80,16 +81,23 @@ Optional settings live under `goosedump` in:
|
|
|
80
81
|
compaction. The countdown is restored when a session resumes and appears in the
|
|
81
82
|
footer with a status dot (green when >25% turns remain, yellow when ≤25%, red when ≤12.5%)
|
|
82
83
|
followed by hexadecimal turn countdown: `● GD:0A` means ten turns remain, `● GD:00` is ready
|
|
83
|
-
to compact, and `● GD:--` is disabled.
|
|
84
|
+
to compact, and `● GD:--` is disabled.
|
|
85
|
+
|
|
86
|
+
`summaryMaxTokens` is an integer from 512 to 65536 (default 4096). The effective
|
|
87
|
+
summary budget is the lower of this value and 80% of Pi's `compaction.reserveTokens`,
|
|
88
|
+
leaving room for the next model response. Clearing a project value restores the
|
|
84
89
|
global setting.
|
|
85
90
|
|
|
86
91
|
## Compaction behavior
|
|
87
92
|
|
|
88
93
|
The extension replaces Pi's normal summary during manual, scheduled, and
|
|
89
94
|
automatic compaction. It invokes `goosedump session compact --remember`,
|
|
90
|
-
preserves the previous compaction summary,
|
|
91
|
-
|
|
92
|
-
|
|
95
|
+
preserves the previous compaction summary, summarizes only the newly compacted
|
|
96
|
+
range, and passes the effective summary budget to goosedump. When necessary,
|
|
97
|
+
goosedump removes older read-only paths, completed items, chronology lines, and
|
|
98
|
+
modified/created paths in that order while preserving goals, blockers, decisions,
|
|
99
|
+
preferences, and the newest actionable context. A `keep:N` request keeps the last
|
|
100
|
+
N active user turns; it is cancelled when no earlier history is available to summarize.
|
|
93
101
|
|
|
94
102
|
Compactions with custom instructions continue to use Pi's normal compactor. If
|
|
95
103
|
goosedump fails, the extension reports the error and falls back to Pi. Successful
|
package/dist/index.ts
CHANGED
|
@@ -52,13 +52,22 @@ var MEMORY_HIT_SEPARATOR = `
|
|
|
52
52
|
var MEMORY_TEXT_TRUNCATED = `
|
|
53
53
|
[Entry text truncated]`;
|
|
54
54
|
var DEFAULT_GOOSEDUMP_SETTINGS = {
|
|
55
|
-
turnsBeforeCompact: 0
|
|
55
|
+
turnsBeforeCompact: 0,
|
|
56
|
+
summaryMaxTokens: 4096
|
|
56
57
|
};
|
|
57
58
|
var TURNS_BEFORE_COMPACT_MIN = 0;
|
|
58
59
|
var TURNS_BEFORE_COMPACT_MAX = 255;
|
|
60
|
+
var SUMMARY_MAX_TOKENS_MIN = 512;
|
|
61
|
+
var SUMMARY_MAX_TOKENS_MAX = 65536;
|
|
59
62
|
function isTurnsBeforeCompact(value) {
|
|
60
63
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= TURNS_BEFORE_COMPACT_MIN && value <= TURNS_BEFORE_COMPACT_MAX;
|
|
61
64
|
}
|
|
65
|
+
function isSummaryMaxTokens(value) {
|
|
66
|
+
return typeof value === "number" && Number.isSafeInteger(value) && value >= SUMMARY_MAX_TOKENS_MIN && value <= SUMMARY_MAX_TOKENS_MAX;
|
|
67
|
+
}
|
|
68
|
+
function isGoosedumpSettingValue(key, value) {
|
|
69
|
+
return key === "turnsBeforeCompact" ? isTurnsBeforeCompact(value) : isSummaryMaxTokens(value);
|
|
70
|
+
}
|
|
62
71
|
function piAgentDir() {
|
|
63
72
|
return process.env.PI_CODING_AGENT_DIR ?? join(homedir(), ".pi", "agent");
|
|
64
73
|
}
|
|
@@ -114,9 +123,12 @@ function readGoosedumpSettingsFrom(path) {
|
|
|
114
123
|
if (!isRecord(raw))
|
|
115
124
|
return {};
|
|
116
125
|
const settings = {};
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
126
|
+
if (isTurnsBeforeCompact(raw.turnsBeforeCompact)) {
|
|
127
|
+
settings.turnsBeforeCompact = raw.turnsBeforeCompact;
|
|
128
|
+
}
|
|
129
|
+
if (isSummaryMaxTokens(raw.summaryMaxTokens)) {
|
|
130
|
+
settings.summaryMaxTokens = raw.summaryMaxTokens;
|
|
131
|
+
}
|
|
120
132
|
return settings;
|
|
121
133
|
}
|
|
122
134
|
function validateGoosedumpSettings(path) {
|
|
@@ -126,10 +138,14 @@ function validateGoosedumpSettings(path) {
|
|
|
126
138
|
if (!isRecord(root.goosedump)) {
|
|
127
139
|
throw new Error(`${path}: goosedump must be a JSON object`);
|
|
128
140
|
}
|
|
129
|
-
const
|
|
130
|
-
if (
|
|
141
|
+
const turns = root.goosedump.turnsBeforeCompact;
|
|
142
|
+
if (turns !== undefined && !isTurnsBeforeCompact(turns)) {
|
|
131
143
|
throw new Error(`${path}: goosedump.turnsBeforeCompact must be an integer from ` + `${TURNS_BEFORE_COMPACT_MIN} to ${TURNS_BEFORE_COMPACT_MAX}`);
|
|
132
144
|
}
|
|
145
|
+
const summaryTokens = root.goosedump.summaryMaxTokens;
|
|
146
|
+
if (summaryTokens !== undefined && !isSummaryMaxTokens(summaryTokens)) {
|
|
147
|
+
throw new Error(`${path}: goosedump.summaryMaxTokens must be an integer from ` + `${SUMMARY_MAX_TOKENS_MIN} to ${SUMMARY_MAX_TOKENS_MAX}`);
|
|
148
|
+
}
|
|
133
149
|
}
|
|
134
150
|
function resolveGoosedumpSettings(cwd) {
|
|
135
151
|
return {
|
|
@@ -155,9 +171,12 @@ function readScopedGoosedumpSettings(cwd, scope) {
|
|
|
155
171
|
if (!isRecord(raw))
|
|
156
172
|
return { ...DEFAULT_GOOSEDUMP_SETTINGS };
|
|
157
173
|
const settings = {};
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
174
|
+
if (isTurnsBeforeCompact(raw.turnsBeforeCompact)) {
|
|
175
|
+
settings.turnsBeforeCompact = raw.turnsBeforeCompact;
|
|
176
|
+
}
|
|
177
|
+
if (isSummaryMaxTokens(raw.summaryMaxTokens)) {
|
|
178
|
+
settings.summaryMaxTokens = raw.summaryMaxTokens;
|
|
179
|
+
}
|
|
161
180
|
return { ...DEFAULT_GOOSEDUMP_SETTINGS, ...settings };
|
|
162
181
|
}
|
|
163
182
|
function writeScopedGoosedumpSetting(cwd, scope, key, value) {
|
|
@@ -495,6 +514,7 @@ async function goosedumpCompact(target, options, signal, executablePath) {
|
|
|
495
514
|
args.push("--from", options.from);
|
|
496
515
|
if (options.previousSummary)
|
|
497
516
|
args.push("--previous-summary", options.previousSummary);
|
|
517
|
+
args.push("--summary-max-tokens", String(options.summaryMaxTokens));
|
|
498
518
|
args.push("--before", options.until);
|
|
499
519
|
const output = await runGoosedumpAsync(args, signal, executablePath);
|
|
500
520
|
for (const line of output.stdout.split(`
|
|
@@ -551,6 +571,22 @@ function piKeepRecentTokens(cwd) {
|
|
|
551
571
|
}
|
|
552
572
|
return DEFAULT_COMPACTION_SETTINGS.keepRecentTokens;
|
|
553
573
|
}
|
|
574
|
+
function piReserveTokens(cwd) {
|
|
575
|
+
for (const path of [projectSettingsPath(cwd), globalSettingsPath()]) {
|
|
576
|
+
const compaction = readJsonObject(path).compaction;
|
|
577
|
+
if (isRecord(compaction) && typeof compaction.reserveTokens === "number") {
|
|
578
|
+
return compaction.reserveTokens;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
return DEFAULT_COMPACTION_SETTINGS.reserveTokens;
|
|
582
|
+
}
|
|
583
|
+
function effectiveSummaryMaxTokens(cwd) {
|
|
584
|
+
const configured = resolveGoosedumpSettings(cwd).summaryMaxTokens;
|
|
585
|
+
const reserveTokens = piReserveTokens(cwd);
|
|
586
|
+
if (reserveTokens <= 0)
|
|
587
|
+
return configured;
|
|
588
|
+
return Math.max(1, Math.min(configured, Math.floor(reserveTokens * 0.8)));
|
|
589
|
+
}
|
|
554
590
|
function hasCompactableHistory(ctx) {
|
|
555
591
|
const branch = ctx.sessionManager.getBranch();
|
|
556
592
|
if (branch.length === 0)
|
|
@@ -656,7 +692,7 @@ function utf8Prefix(text, maxBytes) {
|
|
|
656
692
|
}
|
|
657
693
|
return prefix.join("");
|
|
658
694
|
}
|
|
659
|
-
async function buildGoosedumpCompaction(target, preparation, branchEntries, keep, signal, executablePath) {
|
|
695
|
+
async function buildGoosedumpCompaction(target, preparation, branchEntries, summaryMaxTokens, keep, signal, executablePath) {
|
|
660
696
|
let firstKeptEntryId = preparation.firstKeptEntryId;
|
|
661
697
|
if (keep !== undefined && keep > 0) {
|
|
662
698
|
const id = keepBoundaryId(branchEntries, keep);
|
|
@@ -668,7 +704,7 @@ async function buildGoosedumpCompaction(target, preparation, branchEntries, keep
|
|
|
668
704
|
const range = buildCompactRange(branchEntries, firstKeptEntryId);
|
|
669
705
|
if (!range)
|
|
670
706
|
return;
|
|
671
|
-
const generated = await goosedumpCompact(target, { ...range, previousSummary: preparation.previousSummary }, signal, executablePath);
|
|
707
|
+
const generated = await goosedumpCompact(target, { ...range, previousSummary: preparation.previousSummary, summaryMaxTokens }, signal, executablePath);
|
|
672
708
|
if (!generated.summary)
|
|
673
709
|
return;
|
|
674
710
|
const summary = generated.summary;
|
|
@@ -779,18 +815,35 @@ async function runSettingsEditor(ctx) {
|
|
|
779
815
|
const global = readScopedGoosedumpSettings(location, "global");
|
|
780
816
|
const projectPath = projectSettingsPath(location);
|
|
781
817
|
const projectRaw = readJsonObjectStrict(projectPath).goosedump;
|
|
782
|
-
const
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
currentValue: String(scope === "project" ? projectValue ?? "" : global.turnsBeforeCompact),
|
|
788
|
-
submenu: (currentValue, subDone) => new NumberInputSubmenu(settingLabel(scope, "turnsBeforeCompact"), currentValue === "" ? null : Number(currentValue), (value) => subDone(String(value)), () => subDone(), {
|
|
818
|
+
const project = isRecord(projectRaw) ? projectRaw : {};
|
|
819
|
+
const specs = [
|
|
820
|
+
{
|
|
821
|
+
key: "turnsBeforeCompact",
|
|
822
|
+
description: "LLM turns before compaction; 0 disables.",
|
|
789
823
|
min: TURNS_BEFORE_COMPACT_MIN,
|
|
790
|
-
max: TURNS_BEFORE_COMPACT_MAX
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
824
|
+
max: TURNS_BEFORE_COMPACT_MAX
|
|
825
|
+
},
|
|
826
|
+
{
|
|
827
|
+
key: "summaryMaxTokens",
|
|
828
|
+
description: "Maximum estimated tokens in each summary.",
|
|
829
|
+
min: SUMMARY_MAX_TOKENS_MIN,
|
|
830
|
+
max: SUMMARY_MAX_TOKENS_MAX
|
|
831
|
+
}
|
|
832
|
+
];
|
|
833
|
+
return ["global", "project"].flatMap((scope) => specs.map(({ key, description, min, max }) => {
|
|
834
|
+
const projectValue = isGoosedumpSettingValue(key, project[key]) ? project[key] : undefined;
|
|
835
|
+
return {
|
|
836
|
+
id: `${scope}:${key}`,
|
|
837
|
+
label: settingLabel(scope, key),
|
|
838
|
+
description: scope === "project" ? `${description} Empty inherits Global.` : description,
|
|
839
|
+
currentValue: String(scope === "project" ? projectValue ?? "" : global[key]),
|
|
840
|
+
submenu: (currentValue, subDone) => new NumberInputSubmenu(settingLabel(scope, key), currentValue === "" ? null : Number(currentValue), (value) => subDone(String(value)), () => subDone(), {
|
|
841
|
+
min,
|
|
842
|
+
max,
|
|
843
|
+
onClear: scope === "project" ? () => subDone("") : undefined,
|
|
844
|
+
onInvalid: (message) => ctx.ui.notify(message, "error")
|
|
845
|
+
})
|
|
846
|
+
};
|
|
794
847
|
}));
|
|
795
848
|
}
|
|
796
849
|
function rebuildSettingsList() {
|
|
@@ -809,12 +862,12 @@ async function runSettingsEditor(ctx) {
|
|
|
809
862
|
return;
|
|
810
863
|
}
|
|
811
864
|
const parsed = Number(newValue);
|
|
812
|
-
if (!
|
|
865
|
+
if (!isGoosedumpSettingValue(key, parsed))
|
|
813
866
|
return;
|
|
814
867
|
try {
|
|
815
868
|
writeScopedGoosedumpSetting(location, scope, key, parsed);
|
|
816
869
|
settingsList.updateValue(id, newValue);
|
|
817
|
-
ctx.ui.notify(`${settingLabel(scope, key)} = ${formatCompactSetting(parsed)}`, "info");
|
|
870
|
+
ctx.ui.notify(`${settingLabel(scope, key)} = ${key === "turnsBeforeCompact" ? formatCompactSetting(parsed) : parsed}`, "info");
|
|
818
871
|
tui.requestRender();
|
|
819
872
|
} catch (error) {
|
|
820
873
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -841,7 +894,7 @@ async function runSettingsEditor(ctx) {
|
|
|
841
894
|
lines.push(makeRow(line, innerW, border));
|
|
842
895
|
lines.push(makeRow("", innerW, border));
|
|
843
896
|
const effective = resolveGoosedumpSettings(location);
|
|
844
|
-
lines.push(makeRow(dim(`Effective: turns=${formatCompactSetting(effective.turnsBeforeCompact)}`), innerW, border));
|
|
897
|
+
lines.push(makeRow(dim(`Effective: turns=${formatCompactSetting(effective.turnsBeforeCompact)} · summary=${effectiveSummaryMaxTokens(location)} tokens`), innerW, border));
|
|
845
898
|
lines.push(makeRow(dim("↑↓ select enter/space edit esc close"), innerW, border));
|
|
846
899
|
lines.push(`${borderFg("╰")}${borderFg("─".repeat(width - 2))}${borderFg("╯")}`);
|
|
847
900
|
return lines;
|
|
@@ -1692,7 +1745,7 @@ ${lines.join(`
|
|
|
1692
1745
|
compactionHookActive = true;
|
|
1693
1746
|
try {
|
|
1694
1747
|
const keep = pendingKeep ?? undefined;
|
|
1695
|
-
const compaction = await buildGoosedumpCompaction(target, event.preparation, event.branchEntries, keep, event.signal, nativeExecutablePath);
|
|
1748
|
+
const compaction = await buildGoosedumpCompaction(target, event.preparation, event.branchEntries, effectiveSummaryMaxTokens(ctx.cwd), keep, event.signal, nativeExecutablePath);
|
|
1696
1749
|
if (compaction && "cancel" in compaction) {
|
|
1697
1750
|
if (ctx.hasUI)
|
|
1698
1751
|
ctx.ui.notify(compaction.reason, "info");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goosedump",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.8",
|
|
4
4
|
"description": "Pi extension for goosedump-backed session-history search, entry expansion, and compaction",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"compaction",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@earendil-works/pi-tui": "^0.82.1",
|
|
41
|
-
"@jarkkojs/goosedump": "^0.12.
|
|
41
|
+
"@jarkkojs/goosedump": "^0.12.8",
|
|
42
42
|
"@sinclair/typebox": "^0.34.49"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|