pi-goosedump 0.12.13 → 0.12.15
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 +12 -8
- package/dist/index.ts +430 -223
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -23,8 +23,8 @@ report that they are unavailable.
|
|
|
23
23
|
2. **Recall:** lexical and entity matches come first. Cosine-similar BGE vectors
|
|
24
24
|
fill the remaining slots. There is no separate vector database.
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
Explicit `goose_remember` requests and learning after successful compactions are
|
|
27
|
+
queued in the background, so model extraction and embedding do not block Pi.
|
|
28
28
|
|
|
29
29
|
## Tools
|
|
30
30
|
|
|
@@ -32,8 +32,8 @@ runs in the background and does not delay compaction.
|
|
|
32
32
|
entry IDs.
|
|
33
33
|
- **`goose_get`** expands entry IDs returned by `goose_search` or `goose_grep`.
|
|
34
34
|
- **`goose_grep`** filters session messages with a glob pattern.
|
|
35
|
-
- **`goose_remember`**
|
|
36
|
-
current Pi session.
|
|
35
|
+
- **`goose_remember`** queues durable memory learning from a session. It defaults
|
|
36
|
+
to the current Pi session and returns before extraction and embedding finish.
|
|
37
37
|
- **`goose_recall`** recalls typed memories with source provenance. It searches
|
|
38
38
|
active memories in the current project by default; `history` includes
|
|
39
39
|
superseded statements and `allProjects` searches every project.
|
|
@@ -97,10 +97,12 @@ compaction. Each completed model response counts as one turn, including response
|
|
|
97
97
|
that call tools. The countdown is restored when a session resumes and appears in
|
|
98
98
|
the footer with a status dot (green when >25% turns remain, yellow when ≤25%, red
|
|
99
99
|
when ≤12.5%) followed by a hexadecimal countdown. `● GD:0A` means ten turns
|
|
100
|
-
remain, `● GD:00` is ready to compact, and `● GD:--` is disabled.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
100
|
+
remain, `● GD:00` is ready to compact, and `● GD:--` is disabled. Pi remains the
|
|
101
|
+
sole scheduler for its normal context threshold. Whichever limit is reached first
|
|
102
|
+
compacts once, and every successful compaction resets the countdown. A turn or
|
|
103
|
+
tool request waits until the agent run is fully settled. A failed or cancelled
|
|
104
|
+
compaction leaves the counter armed at zero and is not retried in the same
|
|
105
|
+
settlement.
|
|
104
106
|
|
|
105
107
|
`summaryMaxTokens` is an integer from 512 to 65536 (default 4096). The effective
|
|
106
108
|
summary budget is the lower of this value and 80% of Pi's `compaction.reserveTokens`,
|
|
@@ -112,6 +114,8 @@ global setting.
|
|
|
112
114
|
The extension uses `goosedump session compact` for manual, scheduled, and automatic
|
|
113
115
|
compaction. It preserves the previous summary, summarizes only the new range, and
|
|
114
116
|
passes the effective token budget. `keep:N` leaves the last N user turns verbatim.
|
|
117
|
+
Tool requests are queued until Pi is idle; if Pi reaches its context threshold
|
|
118
|
+
first, the queued request is coalesced into that compaction.
|
|
115
119
|
|
|
116
120
|
After success, memory learning is queued in the background. A learning failure
|
|
117
121
|
keeps the summary and displays a warning. Custom instructions use Pi's normal
|
package/dist/index.ts
CHANGED
|
@@ -27,9 +27,7 @@ import { dirname, join } from "node:path";
|
|
|
27
27
|
import {
|
|
28
28
|
Input,
|
|
29
29
|
Key,
|
|
30
|
-
SettingsList,
|
|
31
30
|
decodeKittyPrintable,
|
|
32
|
-
getKeybindings,
|
|
33
31
|
matchesKey,
|
|
34
32
|
truncateToWidth,
|
|
35
33
|
visibleWidth,
|
|
@@ -66,9 +64,6 @@ function isTurnsBeforeCompact(value) {
|
|
|
66
64
|
function isSummaryMaxTokens(value) {
|
|
67
65
|
return typeof value === "number" && Number.isSafeInteger(value) && value >= SUMMARY_MAX_TOKENS_MIN && value <= SUMMARY_MAX_TOKENS_MAX;
|
|
68
66
|
}
|
|
69
|
-
function isGoosedumpSettingValue(key, value) {
|
|
70
|
-
return key === "turnsBeforeCompact" ? isTurnsBeforeCompact(value) : isSummaryMaxTokens(value);
|
|
71
|
-
}
|
|
72
67
|
function piAgentDir() {
|
|
73
68
|
return process.env.PI_CODING_AGENT_DIR ?? join(homedir(), ".pi", "agent");
|
|
74
69
|
}
|
|
@@ -81,6 +76,10 @@ function projectSettingsPath(cwd) {
|
|
|
81
76
|
function isRecord(value) {
|
|
82
77
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
83
78
|
}
|
|
79
|
+
function projectSettingsTrusted(ctx) {
|
|
80
|
+
const trustContext = ctx;
|
|
81
|
+
return trustContext.isProjectTrusted?.() ?? true;
|
|
82
|
+
}
|
|
84
83
|
function readJsonObject(path) {
|
|
85
84
|
if (!existsSync(path))
|
|
86
85
|
return {};
|
|
@@ -148,15 +147,19 @@ function validateGoosedumpSettings(path) {
|
|
|
148
147
|
throw new Error(`${path}: goosedump.summaryMaxTokens must be an integer from ` + `${SUMMARY_MAX_TOKENS_MIN} to ${SUMMARY_MAX_TOKENS_MAX}`);
|
|
149
148
|
}
|
|
150
149
|
}
|
|
151
|
-
function resolveGoosedumpSettings(cwd) {
|
|
150
|
+
function resolveGoosedumpSettings(cwd, includeProject) {
|
|
152
151
|
return {
|
|
153
152
|
...DEFAULT_GOOSEDUMP_SETTINGS,
|
|
154
153
|
...readGoosedumpSettingsFrom(globalSettingsPath()),
|
|
155
|
-
...readGoosedumpSettingsFrom(projectSettingsPath(cwd))
|
|
154
|
+
...includeProject ? readGoosedumpSettingsFrom(projectSettingsPath(cwd)) : {}
|
|
156
155
|
};
|
|
157
156
|
}
|
|
158
157
|
function notifyInvalidSettings(ctx) {
|
|
159
|
-
|
|
158
|
+
const paths = [
|
|
159
|
+
globalSettingsPath(),
|
|
160
|
+
...projectSettingsTrusted(ctx) ? [projectSettingsPath(ctx.cwd)] : []
|
|
161
|
+
];
|
|
162
|
+
for (const path of paths) {
|
|
160
163
|
try {
|
|
161
164
|
validateGoosedumpSettings(path);
|
|
162
165
|
} catch (error) {
|
|
@@ -400,8 +403,9 @@ var GOOSEDUMP_UNAVAILABLE_MESSAGE = "goosedump is not available. Install with: n
|
|
|
400
403
|
function goosedumpUnavailableResult() {
|
|
401
404
|
return textResult(GOOSEDUMP_UNAVAILABLE_MESSAGE);
|
|
402
405
|
}
|
|
403
|
-
function compactStartedMessage(keep) {
|
|
404
|
-
|
|
406
|
+
function compactStartedMessage(keep, queued = false) {
|
|
407
|
+
const action = queued ? "Compaction queued with goosedump" : "Compacting with goosedump";
|
|
408
|
+
return keep !== null ? `${action}; keeping the last ${keep} user turn${keep === 1 ? "" : "s"}.` : `${action} up to Pi's token-based cut.`;
|
|
405
409
|
}
|
|
406
410
|
function runGoosedumpAsync(args, signal, executablePath = resolveNativeGoosedumpBinary()) {
|
|
407
411
|
return new Promise((resolve, reject) => {
|
|
@@ -593,8 +597,9 @@ function isCompletedAssistant(message) {
|
|
|
593
597
|
return message.role === "assistant" && message.stopReason !== "error" && message.stopReason !== "aborted";
|
|
594
598
|
}
|
|
595
599
|
var SUMMARIZABLE_ENTRY_TYPES = new Set(["message", "custom_message", "branch_summary"]);
|
|
596
|
-
function piKeepRecentTokens(cwd) {
|
|
597
|
-
|
|
600
|
+
function piKeepRecentTokens(cwd, includeProject) {
|
|
601
|
+
const paths = includeProject ? [projectSettingsPath(cwd), globalSettingsPath()] : [globalSettingsPath()];
|
|
602
|
+
for (const path of paths) {
|
|
598
603
|
const compaction = readJsonObject(path).compaction;
|
|
599
604
|
if (isRecord(compaction) && typeof compaction.keepRecentTokens === "number") {
|
|
600
605
|
return compaction.keepRecentTokens;
|
|
@@ -602,8 +607,9 @@ function piKeepRecentTokens(cwd) {
|
|
|
602
607
|
}
|
|
603
608
|
return DEFAULT_COMPACTION_SETTINGS.keepRecentTokens;
|
|
604
609
|
}
|
|
605
|
-
function piReserveTokens(cwd) {
|
|
606
|
-
|
|
610
|
+
function piReserveTokens(cwd, includeProject) {
|
|
611
|
+
const paths = includeProject ? [projectSettingsPath(cwd), globalSettingsPath()] : [globalSettingsPath()];
|
|
612
|
+
for (const path of paths) {
|
|
607
613
|
const compaction = readJsonObject(path).compaction;
|
|
608
614
|
if (isRecord(compaction) && typeof compaction.reserveTokens === "number") {
|
|
609
615
|
return compaction.reserveTokens;
|
|
@@ -611,9 +617,9 @@ function piReserveTokens(cwd) {
|
|
|
611
617
|
}
|
|
612
618
|
return DEFAULT_COMPACTION_SETTINGS.reserveTokens;
|
|
613
619
|
}
|
|
614
|
-
function effectiveSummaryMaxTokens(cwd) {
|
|
615
|
-
const configured = resolveGoosedumpSettings(cwd).summaryMaxTokens;
|
|
616
|
-
const reserveTokens = piReserveTokens(cwd);
|
|
620
|
+
function effectiveSummaryMaxTokens(cwd, includeProject) {
|
|
621
|
+
const configured = resolveGoosedumpSettings(cwd, includeProject).summaryMaxTokens;
|
|
622
|
+
const reserveTokens = piReserveTokens(cwd, includeProject);
|
|
617
623
|
if (reserveTokens <= 0)
|
|
618
624
|
return configured;
|
|
619
625
|
return Math.max(1, Math.min(configured, Math.floor(reserveTokens * 0.8)));
|
|
@@ -625,7 +631,7 @@ function hasCompactableHistory(ctx) {
|
|
|
625
631
|
if (branch[branch.length - 1].type === "compaction")
|
|
626
632
|
return false;
|
|
627
633
|
const boundaryStart = activeHistoryStartIndex(branch);
|
|
628
|
-
const cut = findCutPoint(branch, boundaryStart, branch.length, piKeepRecentTokens(ctx.cwd));
|
|
634
|
+
const cut = findCutPoint(branch, boundaryStart, branch.length, piKeepRecentTokens(ctx.cwd, projectSettingsTrusted(ctx)));
|
|
629
635
|
if (!branch[cut.firstKeptEntryIndex]?.id)
|
|
630
636
|
return false;
|
|
631
637
|
const historyEnd = cut.isSplitTurn ? cut.turnStartIndex : cut.firstKeptEntryIndex;
|
|
@@ -782,8 +788,7 @@ class NumberInputSubmenu {
|
|
|
782
788
|
this.hint = getSettingsListTheme().hint;
|
|
783
789
|
}
|
|
784
790
|
handleInput(data) {
|
|
785
|
-
|
|
786
|
-
if (kb.matches(data, "tui.select.confirm") || data === `
|
|
791
|
+
if (matchesKey(data, Key.enter) || matchesKey(data, Key.return) || data === `
|
|
787
792
|
`) {
|
|
788
793
|
const value = this.input.getValue();
|
|
789
794
|
if (value === "") {
|
|
@@ -805,7 +810,7 @@ class NumberInputSubmenu {
|
|
|
805
810
|
this.onSubmitValue(parsed);
|
|
806
811
|
return;
|
|
807
812
|
}
|
|
808
|
-
if (
|
|
813
|
+
if (matchesKey(data, Key.escape)) {
|
|
809
814
|
this.onCancelEdit();
|
|
810
815
|
return;
|
|
811
816
|
}
|
|
@@ -836,109 +841,190 @@ function makeRow(content, innerW, border) {
|
|
|
836
841
|
async function runSettingsEditor(ctx) {
|
|
837
842
|
const OVERLAY_WIDTH = 84;
|
|
838
843
|
const location = ctx.cwd;
|
|
844
|
+
const projectTrusted = projectSettingsTrusted(ctx);
|
|
845
|
+
const specs = [
|
|
846
|
+
{
|
|
847
|
+
key: "turnsBeforeCompact",
|
|
848
|
+
description: "LLM turns before compaction; 0 disables.",
|
|
849
|
+
min: TURNS_BEFORE_COMPACT_MIN,
|
|
850
|
+
max: TURNS_BEFORE_COMPACT_MAX
|
|
851
|
+
},
|
|
852
|
+
{
|
|
853
|
+
key: "summaryMaxTokens",
|
|
854
|
+
description: "Maximum estimated tokens in each summary.",
|
|
855
|
+
min: SUMMARY_MAX_TOKENS_MIN,
|
|
856
|
+
max: SUMMARY_MAX_TOKENS_MAX
|
|
857
|
+
}
|
|
858
|
+
];
|
|
859
|
+
const helpRows = [
|
|
860
|
+
["Tab", "Next tab"],
|
|
861
|
+
["Shift+Tab / S", "Switch scope in Settings"],
|
|
862
|
+
["↑ / ↓", "Select setting"],
|
|
863
|
+
["Enter", "Edit setting or submit value"],
|
|
864
|
+
["Esc", "Cancel edit or close"],
|
|
865
|
+
["Ctrl+C", "Close"],
|
|
866
|
+
["Empty + Enter", "Use the Global value in Project scope"]
|
|
867
|
+
];
|
|
839
868
|
await ctx.ui.custom((tui, theme, _kb, done) => {
|
|
840
|
-
let
|
|
841
|
-
|
|
842
|
-
|
|
869
|
+
let tab = "settings";
|
|
870
|
+
let scope = projectTrusted ? "project" : "global";
|
|
871
|
+
let selectedSetting = 0;
|
|
872
|
+
let editor;
|
|
873
|
+
let globalSettings = { ...DEFAULT_GOOSEDUMP_SETTINGS };
|
|
874
|
+
let projectSettings = {};
|
|
875
|
+
function settingLabel(editScope, key) {
|
|
876
|
+
const scopeLabel = editScope === "global" ? "Global" : "Project";
|
|
843
877
|
return `${scopeLabel} ${key}`;
|
|
844
878
|
}
|
|
845
|
-
function
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
const
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
description: "Maximum estimated tokens in each summary.",
|
|
860
|
-
min: SUMMARY_MAX_TOKENS_MIN,
|
|
861
|
-
max: SUMMARY_MAX_TOKENS_MAX
|
|
862
|
-
}
|
|
863
|
-
];
|
|
864
|
-
return ["global", "project"].flatMap((scope) => specs.map(({ key, description, min, max }) => {
|
|
865
|
-
const projectValue = isGoosedumpSettingValue(key, project[key]) ? project[key] : undefined;
|
|
866
|
-
return {
|
|
867
|
-
id: `${scope}:${key}`,
|
|
868
|
-
label: settingLabel(scope, key),
|
|
869
|
-
description: scope === "project" ? `${description} Empty inherits Global.` : description,
|
|
870
|
-
currentValue: String(scope === "project" ? projectValue ?? "" : global[key]),
|
|
871
|
-
submenu: (currentValue, subDone) => new NumberInputSubmenu(settingLabel(scope, key), currentValue === "" ? null : Number(currentValue), (value) => subDone(String(value)), () => subDone(), {
|
|
872
|
-
min,
|
|
873
|
-
max,
|
|
874
|
-
onClear: scope === "project" ? () => subDone("") : undefined,
|
|
875
|
-
onInvalid: (message) => ctx.ui.notify(message, "error")
|
|
876
|
-
})
|
|
877
|
-
};
|
|
878
|
-
}));
|
|
879
|
+
function reloadSettings() {
|
|
880
|
+
globalSettings = readScopedGoosedumpSettings(location, "global");
|
|
881
|
+
projectSettings = {};
|
|
882
|
+
if (!projectTrusted)
|
|
883
|
+
return;
|
|
884
|
+
const projectRaw = readJsonObjectStrict(projectSettingsPath(location)).goosedump;
|
|
885
|
+
if (!isRecord(projectRaw))
|
|
886
|
+
return;
|
|
887
|
+
if (isTurnsBeforeCompact(projectRaw.turnsBeforeCompact)) {
|
|
888
|
+
projectSettings.turnsBeforeCompact = projectRaw.turnsBeforeCompact;
|
|
889
|
+
}
|
|
890
|
+
if (isSummaryMaxTokens(projectRaw.summaryMaxTokens)) {
|
|
891
|
+
projectSettings.summaryMaxTokens = projectRaw.summaryMaxTokens;
|
|
892
|
+
}
|
|
879
893
|
}
|
|
880
|
-
function
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
if (!isGoosedumpSettingValue(key, parsed))
|
|
897
|
-
return;
|
|
894
|
+
function finishEdit() {
|
|
895
|
+
editor = undefined;
|
|
896
|
+
reloadSettings();
|
|
897
|
+
tui.requestRender();
|
|
898
|
+
}
|
|
899
|
+
function reportSettingsError(error) {
|
|
900
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
901
|
+
ctx.ui.notify(`goosedump settings error: ${message}`, "error");
|
|
902
|
+
}
|
|
903
|
+
function openEditor() {
|
|
904
|
+
const spec = specs[selectedSetting];
|
|
905
|
+
if (!spec)
|
|
906
|
+
return;
|
|
907
|
+
const editScope = scope;
|
|
908
|
+
const currentValue = editScope === "global" ? globalSettings[spec.key] : projectSettings[spec.key] ?? null;
|
|
909
|
+
editor = new NumberInputSubmenu(settingLabel(editScope, spec.key), currentValue, (value) => {
|
|
898
910
|
try {
|
|
899
|
-
writeScopedGoosedumpSetting(location,
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
tui.requestRender();
|
|
911
|
+
writeScopedGoosedumpSetting(location, editScope, spec.key, value);
|
|
912
|
+
ctx.ui.notify(`${settingLabel(editScope, spec.key)} = ${spec.key === "turnsBeforeCompact" ? formatCompactSetting(value) : value}`, "info");
|
|
913
|
+
finishEdit();
|
|
903
914
|
} catch (error) {
|
|
904
|
-
|
|
905
|
-
ctx.ui.notify(`goosedump settings error: ${message}`, "error");
|
|
915
|
+
reportSettingsError(error);
|
|
906
916
|
}
|
|
907
|
-
tui.requestRender();
|
|
908
917
|
}, () => {
|
|
909
|
-
|
|
918
|
+
editor = undefined;
|
|
919
|
+
tui.requestRender();
|
|
920
|
+
}, {
|
|
921
|
+
min: spec.min,
|
|
922
|
+
max: spec.max,
|
|
923
|
+
onClear: editScope === "project" ? () => {
|
|
924
|
+
try {
|
|
925
|
+
clearScopedGoosedumpSetting(location, editScope, spec.key);
|
|
926
|
+
ctx.ui.notify(`${settingLabel(editScope, spec.key)} inherits Global`, "info");
|
|
927
|
+
finishEdit();
|
|
928
|
+
} catch (error) {
|
|
929
|
+
reportSettingsError(error);
|
|
930
|
+
}
|
|
931
|
+
} : undefined,
|
|
932
|
+
onInvalid: (message) => ctx.ui.notify(message, "error")
|
|
910
933
|
});
|
|
911
934
|
}
|
|
912
|
-
|
|
935
|
+
reloadSettings();
|
|
913
936
|
return {
|
|
914
937
|
render(width) {
|
|
915
|
-
const innerW = width - 4;
|
|
938
|
+
const innerW = Math.max(1, width - 4);
|
|
916
939
|
const border = theme.fg("border", "│");
|
|
917
|
-
const borderFg = (
|
|
918
|
-
const accent = (
|
|
919
|
-
const dim = (
|
|
920
|
-
const
|
|
940
|
+
const borderFg = (text2) => theme.fg("border", text2);
|
|
941
|
+
const accent = (text2) => theme.fg("accent", text2);
|
|
942
|
+
const dim = (text2) => theme.fg("dim", text2);
|
|
943
|
+
const text = (value) => theme.fg("text", value);
|
|
944
|
+
const pad = (value, cellWidth) => {
|
|
945
|
+
const clipped = truncateToWidth(value, cellWidth);
|
|
946
|
+
return `${clipped}${" ".repeat(Math.max(0, cellWidth - visibleWidth(clipped)))}`;
|
|
947
|
+
};
|
|
948
|
+
const tabLabel = (label, active) => active ? accent(theme.bold(`[${label}]`)) : theme.fg("muted", label);
|
|
949
|
+
const tabs = `${tabLabel("Settings", tab === "settings")} ${tabLabel("Help", tab === "help")}${dim(` · Scope: ${scope === "project" ? "Project" : "Global"}`)}`;
|
|
950
|
+
const content = [tabs, ""];
|
|
951
|
+
if (editor) {
|
|
952
|
+
content.push(...editor.render(innerW));
|
|
953
|
+
} else if (tab === "help") {
|
|
954
|
+
const shortcutWidth = Math.max(visibleWidth("Shortcut"), ...helpRows.map(([shortcut]) => visibleWidth(shortcut)));
|
|
955
|
+
const descriptionWidth = Math.max(1, innerW - shortcutWidth - 4);
|
|
956
|
+
content.push(dim(` ${pad("Shortcut", shortcutWidth)} Description`), ...helpRows.map(([shortcut, description]) => ` ${accent(pad(shortcut, shortcutWidth))} ${text(truncateToWidth(description, descriptionWidth))}`));
|
|
957
|
+
} else {
|
|
958
|
+
for (const [index, spec] of specs.entries()) {
|
|
959
|
+
const selected = index === selectedSetting;
|
|
960
|
+
const inherited = scope === "project" && projectSettings[spec.key] === undefined;
|
|
961
|
+
const value = scope === "global" ? globalSettings[spec.key] : projectSettings[spec.key] ?? globalSettings[spec.key];
|
|
962
|
+
const valueText = `[ ${value} ]`;
|
|
963
|
+
const renderedValue = inherited ? dim(valueText) : selected ? accent(valueText) : text(valueText);
|
|
964
|
+
const cursor = selected ? accent("›") : " ";
|
|
965
|
+
content.push(`${cursor} ${renderedValue} ${text(spec.key)}`);
|
|
966
|
+
}
|
|
967
|
+
const selectedSpec = specs[selectedSetting];
|
|
968
|
+
if (selectedSpec) {
|
|
969
|
+
const inherited = scope === "project" && projectSettings[selectedSpec.key] === undefined;
|
|
970
|
+
content.push("", dim(inherited ? `${selectedSpec.description} Using the Global value.` : selectedSpec.description));
|
|
971
|
+
}
|
|
972
|
+
const effective = resolveGoosedumpSettings(location, projectTrusted);
|
|
973
|
+
content.push("", dim(`Effective: turns=${formatCompactSetting(effective.turnsBeforeCompact)} · summary=${effectiveSummaryMaxTokens(location, projectTrusted)} tokens`));
|
|
974
|
+
}
|
|
921
975
|
const title = accent(" Goosedump Settings ");
|
|
922
976
|
const topFill = borderFg("─".repeat(Math.max(0, width - 4 - visibleWidth(title))));
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
lines.push(makeRow(dim(`Effective: turns=${formatCompactSetting(effective.turnsBeforeCompact)} · summary=${effectiveSummaryMaxTokens(location)} tokens`), innerW, border));
|
|
929
|
-
lines.push(makeRow(dim("↑↓ select enter/space edit esc close"), innerW, border));
|
|
930
|
-
lines.push(`${borderFg("╰")}${borderFg("─".repeat(width - 2))}${borderFg("╯")}`);
|
|
931
|
-
return lines;
|
|
977
|
+
return [
|
|
978
|
+
`${borderFg("╭─")}${title}${topFill}${borderFg("─╮")}`,
|
|
979
|
+
...content.map((line) => makeRow(line, innerW, border)),
|
|
980
|
+
`${borderFg("╰")}${borderFg("─".repeat(Math.max(0, width - 2)))}${borderFg("╯")}`
|
|
981
|
+
];
|
|
932
982
|
},
|
|
933
983
|
handleInput(data) {
|
|
934
|
-
if (matchesKey(data, Key.
|
|
984
|
+
if (matchesKey(data, Key.ctrl("c"))) {
|
|
985
|
+
done(undefined);
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
if (editor) {
|
|
989
|
+
editor.handleInput(data);
|
|
990
|
+
tui.requestRender();
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
if (matchesKey(data, Key.tab)) {
|
|
994
|
+
tab = tab === "settings" ? "help" : "settings";
|
|
995
|
+
tui.requestRender();
|
|
996
|
+
return;
|
|
997
|
+
}
|
|
998
|
+
if (tab === "settings" && (matchesKey(data, Key.shift(Key.tab)) || data.toLowerCase() === "s")) {
|
|
999
|
+
if (!projectTrusted && scope === "global") {
|
|
1000
|
+
ctx.ui.notify("Project settings require a trusted project", "warning");
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
1003
|
+
scope = scope === "project" ? "global" : "project";
|
|
1004
|
+
reloadSettings();
|
|
1005
|
+
tui.requestRender();
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
if (matchesKey(data, Key.escape)) {
|
|
935
1009
|
done(undefined);
|
|
936
1010
|
return;
|
|
937
1011
|
}
|
|
938
|
-
|
|
1012
|
+
if (tab === "help")
|
|
1013
|
+
return;
|
|
1014
|
+
if (matchesKey(data, Key.up)) {
|
|
1015
|
+
selectedSetting = selectedSetting === 0 ? specs.length - 1 : Math.max(0, selectedSetting - 1);
|
|
1016
|
+
} else if (matchesKey(data, Key.down)) {
|
|
1017
|
+
selectedSetting = (selectedSetting + 1) % specs.length;
|
|
1018
|
+
} else if (matchesKey(data, Key.enter) || matchesKey(data, Key.return)) {
|
|
1019
|
+
openEditor();
|
|
1020
|
+
} else {
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
939
1023
|
tui.requestRender();
|
|
940
1024
|
},
|
|
941
|
-
invalidate() {
|
|
1025
|
+
invalidate() {
|
|
1026
|
+
editor?.invalidate();
|
|
1027
|
+
}
|
|
942
1028
|
};
|
|
943
1029
|
}, {
|
|
944
1030
|
overlay: true,
|
|
@@ -1287,19 +1373,16 @@ async function runSessionsPanel(ctx, executablePath) {
|
|
|
1287
1373
|
ctx.ui.notify(`Only Pi sessions can be resumed (session ${s.id} is ${s.provider})`, "warning");
|
|
1288
1374
|
return;
|
|
1289
1375
|
}
|
|
1290
|
-
const
|
|
1291
|
-
const sessionPath = join(sessionDir, s.id.endsWith(".jsonl") ? s.id : `${s.id}.jsonl`);
|
|
1376
|
+
const sessionPath = s.path;
|
|
1292
1377
|
if (!existsSync(sessionPath)) {
|
|
1293
1378
|
ctx.ui.notify(`Session transcript file not found: ${sessionPath}`, "error");
|
|
1294
1379
|
return;
|
|
1295
1380
|
}
|
|
1296
1381
|
done(undefined);
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
}
|
|
1301
|
-
ctx.ui.notify(`Session switched to ${sessionPath}`, "info");
|
|
1302
|
-
}
|
|
1382
|
+
ctx.switchSession(sessionPath).catch((err) => {
|
|
1383
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1384
|
+
ctx.ui.notify(`Failed to switch session: ${message}`, "error");
|
|
1385
|
+
});
|
|
1303
1386
|
return;
|
|
1304
1387
|
}
|
|
1305
1388
|
},
|
|
@@ -1322,10 +1405,12 @@ function createGoosedumpIntegration(options = {}) {
|
|
|
1322
1405
|
let compactSettings = DEFAULT_GOOSEDUMP_SETTINGS;
|
|
1323
1406
|
let turnsRemaining = 0;
|
|
1324
1407
|
let counterTriggered = false;
|
|
1325
|
-
let
|
|
1326
|
-
let
|
|
1327
|
-
let
|
|
1328
|
-
let
|
|
1408
|
+
let compactionState = { phase: "idle" };
|
|
1409
|
+
let sessionGeneration = 0;
|
|
1410
|
+
let settlementGeneration = 0;
|
|
1411
|
+
let operationGeneration = 0;
|
|
1412
|
+
let activeSessionFile = null;
|
|
1413
|
+
let sessionActive = false;
|
|
1329
1414
|
const rememberQueue = [];
|
|
1330
1415
|
let rememberRunning = null;
|
|
1331
1416
|
function enqueueRemember(target, reportError) {
|
|
@@ -1361,13 +1446,11 @@ function createGoosedumpIntegration(options = {}) {
|
|
|
1361
1446
|
while (rememberRunning)
|
|
1362
1447
|
await rememberRunning;
|
|
1363
1448
|
}
|
|
1364
|
-
function syncCompactCounters(ctx
|
|
1365
|
-
compactSettings = resolveGoosedumpSettings(ctx.cwd);
|
|
1449
|
+
function syncCompactCounters(ctx) {
|
|
1450
|
+
compactSettings = resolveGoosedumpSettings(ctx.cwd, projectSettingsTrusted(ctx));
|
|
1366
1451
|
const completedTurns = completedTurnsSinceCompaction(ctx.sessionManager.getBranch());
|
|
1367
1452
|
turnsRemaining = Math.max(0, compactSettings.turnsBeforeCompact - completedTurns);
|
|
1368
1453
|
counterTriggered = compactSettings.turnsBeforeCompact > 0 && completedTurns >= compactSettings.turnsBeforeCompact;
|
|
1369
|
-
if (clearPending)
|
|
1370
|
-
pendingKeep = null;
|
|
1371
1454
|
}
|
|
1372
1455
|
function updateStatus(ctx) {
|
|
1373
1456
|
if (!goosedumpAvailable)
|
|
@@ -1392,18 +1475,7 @@ function createGoosedumpIntegration(options = {}) {
|
|
|
1392
1475
|
return;
|
|
1393
1476
|
syncCompactCounters(ctx);
|
|
1394
1477
|
updateStatus(ctx);
|
|
1395
|
-
|
|
1396
|
-
function interruptActiveRunForCompaction(ctx, hasToolResults) {
|
|
1397
|
-
if (!counterTriggered || !hasToolResults || ctx.isIdle())
|
|
1398
|
-
return;
|
|
1399
|
-
if (counterInterruptPending)
|
|
1400
|
-
return;
|
|
1401
|
-
if (compactionQueued || compactionHookActive || ctx.hasPendingMessages())
|
|
1402
|
-
return;
|
|
1403
|
-
if (!hasCompactableHistory(ctx))
|
|
1404
|
-
return;
|
|
1405
|
-
counterInterruptPending = true;
|
|
1406
|
-
ctx.abort();
|
|
1478
|
+
queueTurnCompaction(ctx, settlementGeneration + 1);
|
|
1407
1479
|
}
|
|
1408
1480
|
function checkGoosedump(ctx) {
|
|
1409
1481
|
try {
|
|
@@ -1416,34 +1488,100 @@ function createGoosedumpIntegration(options = {}) {
|
|
|
1416
1488
|
return false;
|
|
1417
1489
|
}
|
|
1418
1490
|
}
|
|
1419
|
-
function
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1491
|
+
function requestIsCurrent(request) {
|
|
1492
|
+
return request.sessionGeneration === sessionGeneration && request.sessionFile === activeSessionFile;
|
|
1493
|
+
}
|
|
1494
|
+
function contextIsCurrent(ctx) {
|
|
1495
|
+
return sessionActive && (ctx.sessionManager.getSessionFile?.() ?? null) === activeSessionFile;
|
|
1496
|
+
}
|
|
1497
|
+
function runningOperationIsCurrent(operation, generation, sessionFile) {
|
|
1498
|
+
return generation === sessionGeneration && sessionFile === activeSessionFile && compactionState.phase === "running" && compactionState.operation === operation;
|
|
1499
|
+
}
|
|
1500
|
+
function requestHasCompactableHistory(ctx, keep) {
|
|
1501
|
+
return keep === null ? hasCompactableHistory(ctx) : keepBoundaryId(ctx.sessionManager.getBranch(), keep) !== undefined;
|
|
1502
|
+
}
|
|
1503
|
+
function queueTurnCompaction(ctx, eligibleSettlement) {
|
|
1504
|
+
if (!counterTriggered || compactionState.phase !== "idle")
|
|
1505
|
+
return;
|
|
1506
|
+
if (!hasCompactableHistory(ctx))
|
|
1507
|
+
return;
|
|
1508
|
+
compactionState = {
|
|
1509
|
+
phase: "queued",
|
|
1510
|
+
request: {
|
|
1511
|
+
keep: null,
|
|
1512
|
+
origin: "turn",
|
|
1513
|
+
sessionGeneration,
|
|
1514
|
+
sessionFile: activeSessionFile,
|
|
1515
|
+
eligibleSettlement
|
|
1516
|
+
}
|
|
1517
|
+
};
|
|
1518
|
+
}
|
|
1519
|
+
function startQueuedCompaction(ctx) {
|
|
1520
|
+
if (compactionState.phase !== "queued")
|
|
1521
|
+
return false;
|
|
1522
|
+
const request = compactionState.request;
|
|
1523
|
+
if (!requestIsCurrent(request)) {
|
|
1524
|
+
compactionState = { phase: "idle" };
|
|
1525
|
+
return false;
|
|
1526
|
+
}
|
|
1527
|
+
if (!ctx.isIdle() || ctx.hasPendingMessages())
|
|
1528
|
+
return false;
|
|
1529
|
+
if (!requestHasCompactableHistory(ctx, request.keep)) {
|
|
1530
|
+
compactionState = { phase: "idle" };
|
|
1531
|
+
return false;
|
|
1532
|
+
}
|
|
1533
|
+
const operation = ++operationGeneration;
|
|
1534
|
+
const generation = request.sessionGeneration;
|
|
1535
|
+
const sessionFile = request.sessionFile;
|
|
1536
|
+
compactionState = {
|
|
1537
|
+
phase: "running",
|
|
1538
|
+
request,
|
|
1539
|
+
owner: "extension",
|
|
1540
|
+
operation,
|
|
1541
|
+
sessionGeneration: generation,
|
|
1542
|
+
sessionFile,
|
|
1543
|
+
hookActive: false
|
|
1544
|
+
};
|
|
1426
1545
|
ctx.compact({
|
|
1427
1546
|
onComplete: () => {
|
|
1428
|
-
|
|
1429
|
-
|
|
1547
|
+
if (!runningOperationIsCurrent(operation, generation, sessionFile))
|
|
1548
|
+
return;
|
|
1549
|
+
compactionState = { phase: "idle" };
|
|
1430
1550
|
},
|
|
1431
1551
|
onError: (error) => {
|
|
1432
|
-
|
|
1433
|
-
|
|
1552
|
+
if (!runningOperationIsCurrent(operation, generation, sessionFile))
|
|
1553
|
+
return;
|
|
1554
|
+
compactionState = { phase: "idle" };
|
|
1434
1555
|
const message = error instanceof Error ? error.message : String(error);
|
|
1435
1556
|
if (ctx.hasUI)
|
|
1436
1557
|
ctx.ui.notify(`goosedump compaction failed: ${message}`, "error");
|
|
1437
1558
|
}
|
|
1438
1559
|
});
|
|
1439
|
-
return
|
|
1560
|
+
return true;
|
|
1561
|
+
}
|
|
1562
|
+
function requestCompaction(ctx, keep, origin) {
|
|
1563
|
+
if (compactionState.phase !== "idle")
|
|
1564
|
+
return "busy";
|
|
1565
|
+
if (!requestHasCompactableHistory(ctx, keep))
|
|
1566
|
+
return "nothing-to-compact";
|
|
1567
|
+
compactionState = {
|
|
1568
|
+
phase: "queued",
|
|
1569
|
+
request: {
|
|
1570
|
+
keep,
|
|
1571
|
+
origin,
|
|
1572
|
+
sessionGeneration,
|
|
1573
|
+
sessionFile: activeSessionFile,
|
|
1574
|
+
eligibleSettlement: settlementGeneration + 1
|
|
1575
|
+
}
|
|
1576
|
+
};
|
|
1577
|
+
return startQueuedCompaction(ctx) ? "started" : "queued";
|
|
1440
1578
|
}
|
|
1441
1579
|
function register(pi) {
|
|
1442
1580
|
if (shouldRegisterTool) {
|
|
1443
1581
|
pi.registerTool(defineTool({
|
|
1444
1582
|
name: "goose_remember",
|
|
1445
1583
|
label: "goose_remember",
|
|
1446
|
-
description: "
|
|
1584
|
+
description: "Queue local gpt-oss-20b memory extraction and BGE embedding in the background. Defaults to the current Pi session.",
|
|
1447
1585
|
promptSnippet: "Persist important context from the current or another session into durable memory",
|
|
1448
1586
|
promptGuidelines: [
|
|
1449
1587
|
"Use goose_remember after a milestone when important facts, decisions, preferences, procedures, or lessons should be retained.",
|
|
@@ -1453,20 +1591,19 @@ function createGoosedumpIntegration(options = {}) {
|
|
|
1453
1591
|
provider: historyProviderParam("read"),
|
|
1454
1592
|
session: historySessionParam()
|
|
1455
1593
|
}),
|
|
1456
|
-
async execute(_id, params,
|
|
1594
|
+
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
1457
1595
|
if (!goosedumpAvailable)
|
|
1458
1596
|
return goosedumpUnavailableResult();
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
}
|
|
1597
|
+
const provider = params.provider ?? "pi";
|
|
1598
|
+
const target = resolveTarget(ctx, provider, params.session);
|
|
1599
|
+
if (!target)
|
|
1600
|
+
return missingSessionId("goose_remember", provider);
|
|
1601
|
+
enqueueRemember(target, (message) => {
|
|
1602
|
+
if (ctx.hasUI) {
|
|
1603
|
+
ctx.ui.notify(`goosedump memory learning failed: ${message}`, "warning");
|
|
1604
|
+
}
|
|
1605
|
+
});
|
|
1606
|
+
return textResult(`Memory learning queued for ${target}.`);
|
|
1470
1607
|
}
|
|
1471
1608
|
}));
|
|
1472
1609
|
pi.registerTool(defineTool({
|
|
@@ -1711,13 +1848,13 @@ function createGoosedumpIntegration(options = {}) {
|
|
|
1711
1848
|
if (!goosedumpAvailable)
|
|
1712
1849
|
return goosedumpUnavailableResult();
|
|
1713
1850
|
const keep = params.keep ?? null;
|
|
1714
|
-
const result = requestCompaction(ctx, keep);
|
|
1851
|
+
const result = requestCompaction(ctx, keep, "tool");
|
|
1715
1852
|
if (result === "busy")
|
|
1716
1853
|
return textResult("Compaction already in progress.");
|
|
1717
1854
|
if (result === "nothing-to-compact") {
|
|
1718
1855
|
return textResult("Nothing to compact (session too small).");
|
|
1719
1856
|
}
|
|
1720
|
-
return textResult(compactStartedMessage(keep));
|
|
1857
|
+
return textResult(compactStartedMessage(keep, result === "queued"));
|
|
1721
1858
|
}
|
|
1722
1859
|
}));
|
|
1723
1860
|
pi.registerTool(defineTool({
|
|
@@ -1787,112 +1924,182 @@ ${lines.join(`
|
|
|
1787
1924
|
}
|
|
1788
1925
|
}));
|
|
1789
1926
|
}
|
|
1790
|
-
function
|
|
1927
|
+
function handleAgentSettled(ctx) {
|
|
1791
1928
|
if (!goosedumpAvailable || !shouldOverrideDefaultCompaction)
|
|
1792
1929
|
return;
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1930
|
+
settlementGeneration += 1;
|
|
1931
|
+
if (compactionState.phase === "running" && compactionState.owner === "pi") {
|
|
1932
|
+
const request = compactionState.request;
|
|
1933
|
+
compactionState = request && requestIsCurrent(request) ? {
|
|
1934
|
+
phase: "queued",
|
|
1935
|
+
request: { ...request, eligibleSettlement: settlementGeneration + 1 }
|
|
1936
|
+
} : { phase: "idle" };
|
|
1798
1937
|
return;
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
syncCompactCounters(ctx, true);
|
|
1810
|
-
updateStatus(ctx);
|
|
1811
|
-
resumeInterruptedRun(ctx, resumeAfter);
|
|
1812
|
-
},
|
|
1813
|
-
onError: (error) => {
|
|
1814
|
-
compactionQueued = false;
|
|
1815
|
-
syncCompactCounters(ctx, true);
|
|
1816
|
-
updateStatus(ctx);
|
|
1817
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1818
|
-
if (ctx.hasUI)
|
|
1819
|
-
ctx.ui.notify(`goosedump compaction failed: ${message}`, "error");
|
|
1820
|
-
}
|
|
1821
|
-
});
|
|
1822
|
-
}
|
|
1823
|
-
function resumeInterruptedRun(ctx, resume) {
|
|
1824
|
-
if (!resume || !ctx.isIdle() || ctx.hasPendingMessages())
|
|
1825
|
-
return;
|
|
1826
|
-
pi.sendMessage({
|
|
1827
|
-
customType: "goosedump-autocompact-resume",
|
|
1828
|
-
content: "The session was compacted to reduce context size. Continue the task you were working on from where you left off.",
|
|
1829
|
-
display: false
|
|
1830
|
-
}, { triggerTurn: true });
|
|
1938
|
+
}
|
|
1939
|
+
if (compactionState.phase === "queued" && !requestIsCurrent(compactionState.request)) {
|
|
1940
|
+
compactionState = { phase: "idle" };
|
|
1941
|
+
}
|
|
1942
|
+
if (compactionState.phase === "idle") {
|
|
1943
|
+
queueTurnCompaction(ctx, settlementGeneration);
|
|
1944
|
+
}
|
|
1945
|
+
if (compactionState.phase === "queued" && compactionState.request.eligibleSettlement <= settlementGeneration) {
|
|
1946
|
+
startQueuedCompaction(ctx);
|
|
1947
|
+
}
|
|
1831
1948
|
}
|
|
1832
1949
|
pi.on("session_start", async (_event, ctx) => {
|
|
1950
|
+
sessionGeneration += 1;
|
|
1951
|
+
settlementGeneration = 0;
|
|
1952
|
+
activeSessionFile = ctx.sessionManager.getSessionFile?.() ?? null;
|
|
1953
|
+
sessionActive = true;
|
|
1954
|
+
compactionState = { phase: "idle" };
|
|
1833
1955
|
goosedumpAvailable = checkGoosedump(ctx);
|
|
1834
|
-
counterInterruptPending = false;
|
|
1835
1956
|
notifyInvalidSettings(ctx);
|
|
1836
|
-
syncCompactCounters(ctx
|
|
1957
|
+
syncCompactCounters(ctx);
|
|
1837
1958
|
updateStatus(ctx);
|
|
1959
|
+
queueTurnCompaction(ctx, 1);
|
|
1960
|
+
});
|
|
1961
|
+
pi.on("session_shutdown", async () => {
|
|
1962
|
+
sessionGeneration += 1;
|
|
1963
|
+
settlementGeneration = 0;
|
|
1964
|
+
activeSessionFile = null;
|
|
1965
|
+
sessionActive = false;
|
|
1966
|
+
compactionState = { phase: "idle" };
|
|
1838
1967
|
});
|
|
1839
1968
|
pi.on("turn_end", async (event, ctx) => {
|
|
1840
1969
|
if (isCompletedAssistant(event.message))
|
|
1841
1970
|
noteCompletedTurn(ctx);
|
|
1842
|
-
interruptActiveRunForCompaction(ctx, event.toolResults.length > 0);
|
|
1843
1971
|
});
|
|
1844
1972
|
pi.on("agent_settled", async (_event, ctx) => {
|
|
1845
|
-
|
|
1973
|
+
handleAgentSettled(ctx);
|
|
1846
1974
|
});
|
|
1847
1975
|
pi.on("session_before_compact", async (event, ctx) => {
|
|
1848
1976
|
if (!shouldOverrideDefaultCompaction || !goosedumpAvailable)
|
|
1849
1977
|
return;
|
|
1850
|
-
if (event.customInstructions?.trim())
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1978
|
+
if (event.customInstructions?.trim()) {
|
|
1979
|
+
if (compactionState.phase === "running")
|
|
1980
|
+
return { cancel: true };
|
|
1981
|
+
if (compactionState.phase === "queued") {
|
|
1982
|
+
if (compactionState.request.origin !== "turn")
|
|
1983
|
+
return { cancel: true };
|
|
1984
|
+
compactionState = {
|
|
1985
|
+
phase: "queued",
|
|
1986
|
+
request: {
|
|
1987
|
+
...compactionState.request,
|
|
1988
|
+
eligibleSettlement: Math.max(compactionState.request.eligibleSettlement, settlementGeneration + 2)
|
|
1989
|
+
}
|
|
1990
|
+
};
|
|
1991
|
+
}
|
|
1856
1992
|
return;
|
|
1857
1993
|
}
|
|
1858
|
-
|
|
1859
|
-
|
|
1994
|
+
if (event.signal.aborted) {
|
|
1995
|
+
if (compactionState.phase === "queued" && requestIsCurrent(compactionState.request)) {
|
|
1996
|
+
compactionState = event.reason === "manual" ? { phase: "idle" } : {
|
|
1997
|
+
phase: "queued",
|
|
1998
|
+
request: {
|
|
1999
|
+
...compactionState.request,
|
|
2000
|
+
eligibleSettlement: Math.max(compactionState.request.eligibleSettlement, settlementGeneration + 2)
|
|
2001
|
+
}
|
|
2002
|
+
};
|
|
2003
|
+
}
|
|
1860
2004
|
return;
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
2005
|
+
}
|
|
2006
|
+
let operation;
|
|
2007
|
+
let generation;
|
|
2008
|
+
let sessionFile;
|
|
2009
|
+
let request;
|
|
2010
|
+
let owner;
|
|
2011
|
+
if (compactionState.phase === "running") {
|
|
2012
|
+
if (compactionState.hookActive || compactionState.owner === "pi") {
|
|
2013
|
+
return { cancel: true };
|
|
2014
|
+
}
|
|
2015
|
+
operation = compactionState.operation;
|
|
2016
|
+
generation = compactionState.sessionGeneration;
|
|
2017
|
+
sessionFile = compactionState.sessionFile;
|
|
2018
|
+
request = compactionState.request;
|
|
2019
|
+
owner = "extension";
|
|
2020
|
+
compactionState = { ...compactionState, hookActive: true };
|
|
2021
|
+
} else {
|
|
2022
|
+
request = compactionState.phase === "queued" && requestIsCurrent(compactionState.request) ? compactionState.request : null;
|
|
2023
|
+
operation = ++operationGeneration;
|
|
2024
|
+
generation = sessionGeneration;
|
|
2025
|
+
sessionFile = activeSessionFile;
|
|
2026
|
+
owner = "pi";
|
|
2027
|
+
compactionState = {
|
|
2028
|
+
phase: "running",
|
|
2029
|
+
request,
|
|
2030
|
+
owner,
|
|
2031
|
+
operation,
|
|
2032
|
+
sessionGeneration: generation,
|
|
2033
|
+
sessionFile,
|
|
2034
|
+
hookActive: true
|
|
2035
|
+
};
|
|
2036
|
+
}
|
|
1864
2037
|
try {
|
|
1865
|
-
const
|
|
1866
|
-
|
|
2038
|
+
const { messagesToSummarize, turnPrefixMessages } = event.preparation;
|
|
2039
|
+
if ((request?.keep ?? null) === null && messagesToSummarize.length === 0 && turnPrefixMessages.length === 0) {
|
|
2040
|
+
return;
|
|
2041
|
+
}
|
|
2042
|
+
const target = currentSessionTarget(ctx);
|
|
2043
|
+
if (!target)
|
|
2044
|
+
return;
|
|
2045
|
+
let keep = request?.keep ?? undefined;
|
|
2046
|
+
if (keep !== undefined && keepBoundaryId(event.branchEntries, keep) === undefined) {
|
|
2047
|
+
if (ctx.hasUI) {
|
|
2048
|
+
ctx.ui.notify(`fewer than ${keep} user turns; using Pi's token-based cut`, "info");
|
|
2049
|
+
}
|
|
2050
|
+
keep = undefined;
|
|
2051
|
+
if (runningOperationIsCurrent(operation, generation, sessionFile)) {
|
|
2052
|
+
compactionState = { ...compactionState, request: null };
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
const compaction = await buildGoosedumpCompaction(target, event.preparation, event.branchEntries, effectiveSummaryMaxTokens(ctx.cwd, projectSettingsTrusted(ctx)), keep, event.signal, nativeExecutablePath);
|
|
2056
|
+
if (!runningOperationIsCurrent(operation, generation, sessionFile)) {
|
|
2057
|
+
return { cancel: true };
|
|
2058
|
+
}
|
|
1867
2059
|
if (compaction && "cancel" in compaction) {
|
|
1868
2060
|
if (ctx.hasUI)
|
|
1869
2061
|
ctx.ui.notify(compaction.reason, "info");
|
|
1870
|
-
return { cancel: true };
|
|
2062
|
+
return owner === "pi" && event.reason === "manual" ? { cancel: true } : undefined;
|
|
1871
2063
|
}
|
|
1872
2064
|
const warning = compaction?.details?.warning;
|
|
1873
2065
|
if (warning && ctx.hasUI) {
|
|
1874
2066
|
ctx.ui.notify(warning, "warning");
|
|
1875
2067
|
}
|
|
1876
|
-
|
|
2068
|
+
if (compaction)
|
|
2069
|
+
return { compaction };
|
|
2070
|
+
return owner === "pi" && event.reason === "manual" ? { cancel: true } : undefined;
|
|
1877
2071
|
} catch (err) {
|
|
2072
|
+
if (!runningOperationIsCurrent(operation, generation, sessionFile)) {
|
|
2073
|
+
return { cancel: true };
|
|
2074
|
+
}
|
|
1878
2075
|
if (event.signal.aborted)
|
|
1879
2076
|
return { cancel: true };
|
|
2077
|
+
const useFallback = owner === "extension" || event.reason !== "manual";
|
|
1880
2078
|
if (ctx.hasUI) {
|
|
1881
2079
|
const message = err instanceof Error ? err.message : String(err);
|
|
1882
|
-
|
|
2080
|
+
const action = useFallback ? "; falling back to Pi compaction" : "";
|
|
2081
|
+
ctx.ui.notify(`goosedump compaction failed${action}: ${message}`, "error");
|
|
1883
2082
|
}
|
|
1884
|
-
return;
|
|
2083
|
+
return useFallback ? undefined : { cancel: true };
|
|
1885
2084
|
} finally {
|
|
1886
|
-
|
|
1887
|
-
|
|
2085
|
+
if (runningOperationIsCurrent(operation, generation, sessionFile)) {
|
|
2086
|
+
compactionState = owner === "pi" && event.reason === "manual" ? { phase: "idle" } : { ...compactionState, hookActive: false };
|
|
2087
|
+
}
|
|
1888
2088
|
}
|
|
1889
2089
|
});
|
|
1890
2090
|
pi.on("session_compact", async (event, ctx) => {
|
|
1891
|
-
if (!
|
|
2091
|
+
if (!shouldOverrideDefaultCompaction)
|
|
2092
|
+
return;
|
|
2093
|
+
if (!contextIsCurrent(ctx))
|
|
1892
2094
|
return;
|
|
1893
|
-
|
|
2095
|
+
if (compactionState.phase === "queued" && compactionState.request.keep !== null && ctx.hasUI) {
|
|
2096
|
+
ctx.ui.notify("Queued keep request was superseded by another compaction.", "info");
|
|
2097
|
+
}
|
|
2098
|
+
compactionState = { phase: "idle" };
|
|
2099
|
+
syncCompactCounters(ctx);
|
|
1894
2100
|
updateStatus(ctx);
|
|
1895
|
-
|
|
2101
|
+
if (!goosedumpAvailable)
|
|
2102
|
+
return;
|
|
1896
2103
|
const compactionEntry = event.compactionEntry;
|
|
1897
2104
|
if (!event.fromExtension || !compactionEntry || !isRecord(compactionEntry.details) || compactionEntry.details.source !== "goosedump") {
|
|
1898
2105
|
return;
|
|
@@ -1951,13 +2158,13 @@ ${lines.join(`
|
|
|
1951
2158
|
return;
|
|
1952
2159
|
}
|
|
1953
2160
|
}
|
|
1954
|
-
const result = requestCompaction(ctx, keep);
|
|
2161
|
+
const result = requestCompaction(ctx, keep, "command");
|
|
1955
2162
|
if (result === "busy") {
|
|
1956
2163
|
ctx.ui.notify("Compaction already in progress", "info");
|
|
1957
2164
|
} else if (result === "nothing-to-compact") {
|
|
1958
2165
|
ctx.ui.notify("Nothing to compact (session too small)", "info");
|
|
1959
2166
|
} else {
|
|
1960
|
-
ctx.ui.notify(compactStartedMessage(keep), "info");
|
|
2167
|
+
ctx.ui.notify(compactStartedMessage(keep, result === "queued"), "info");
|
|
1961
2168
|
}
|
|
1962
2169
|
}
|
|
1963
2170
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goosedump",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.15",
|
|
4
4
|
"description": "Pi extension for goosedump session search, durable memory, 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.15",
|
|
42
42
|
"@sinclair/typebox": "^0.34.49"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|