pi-goosedump 0.12.66 → 0.12.69
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/dist/index.ts +225 -97
- package/package.json +2 -2
package/dist/index.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { randomUUID as randomUUID2 } from "node:crypto";
|
|
|
7
7
|
// src/cli.ts
|
|
8
8
|
import { Type as Type2 } from "typebox";
|
|
9
9
|
import { Value as Value2 } from "typebox/value";
|
|
10
|
-
import {
|
|
10
|
+
import { spawn } from "node:child_process";
|
|
11
11
|
import { accessSync, constants as fsConstants, existsSync, readFileSync as readFileSync2, statSync } from "node:fs";
|
|
12
12
|
import { createRequire } from "node:module";
|
|
13
13
|
import { dirname, isAbsolute, join } from "node:path";
|
|
@@ -167,7 +167,8 @@ var GOOSE_PROVIDERS = [
|
|
|
167
167
|
var SUMMARIZABLE_ENTRY_TYPES = new Set(["message", "custom_message", "branch_summary"]);
|
|
168
168
|
|
|
169
169
|
// src/util.ts
|
|
170
|
-
import { readFileSync } from "node:fs";
|
|
170
|
+
import { closeSync, openSync, readFileSync, readSync } from "node:fs";
|
|
171
|
+
import { StringDecoder } from "node:string_decoder";
|
|
171
172
|
function isRecord(value) {
|
|
172
173
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
173
174
|
}
|
|
@@ -187,14 +188,56 @@ function compactStartedMessage(keep, queued = false) {
|
|
|
187
188
|
const action = queued ? "Compaction queued with goosedump" : "Compacting with goosedump";
|
|
188
189
|
return keep !== null ? `${action}; keeping the last ${keep} user turn${keep === 1 ? "" : "s"}.` : `${action} up to Pi's token-based cut.`;
|
|
189
190
|
}
|
|
191
|
+
var FIRST_LINE_BUFFER_SIZE = 4096;
|
|
192
|
+
var MAX_FIRST_LINE_BYTES = 64 * 1024;
|
|
190
193
|
function readFirstLine(path) {
|
|
194
|
+
let fd = null;
|
|
191
195
|
try {
|
|
192
|
-
|
|
193
|
-
const
|
|
196
|
+
fd = openSync(path, "r");
|
|
197
|
+
const buffer = Buffer.alloc(FIRST_LINE_BUFFER_SIZE);
|
|
198
|
+
const decoder = new StringDecoder("utf8");
|
|
199
|
+
let totalText = "";
|
|
200
|
+
let offset = 0;
|
|
201
|
+
while (offset < MAX_FIRST_LINE_BYTES) {
|
|
202
|
+
const bytesRead = readSync(fd, buffer, 0, FIRST_LINE_BUFFER_SIZE, offset);
|
|
203
|
+
if (bytesRead === 0)
|
|
204
|
+
break;
|
|
205
|
+
offset += bytesRead;
|
|
206
|
+
const chunk = decoder.write(buffer.subarray(0, bytesRead));
|
|
207
|
+
const newline = chunk.indexOf(`
|
|
194
208
|
`);
|
|
195
|
-
|
|
196
|
-
|
|
209
|
+
if (newline >= 0) {
|
|
210
|
+
totalText += chunk.slice(0, newline);
|
|
211
|
+
return totalText.endsWith("\r") ? totalText.slice(0, -1) : totalText;
|
|
212
|
+
}
|
|
213
|
+
totalText += chunk;
|
|
214
|
+
if (bytesRead < FIRST_LINE_BUFFER_SIZE)
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
totalText += decoder.end();
|
|
218
|
+
const finalNewline = totalText.indexOf(`
|
|
219
|
+
`);
|
|
220
|
+
const line = finalNewline >= 0 ? totalText.slice(0, finalNewline) : totalText;
|
|
221
|
+
if (line.length > 0) {
|
|
222
|
+
return line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
223
|
+
}
|
|
197
224
|
return null;
|
|
225
|
+
} catch {
|
|
226
|
+
try {
|
|
227
|
+
const content = readFileSync(path, "utf8");
|
|
228
|
+
const newline = content.indexOf(`
|
|
229
|
+
`);
|
|
230
|
+
const line = newline < 0 ? content : content.slice(0, newline);
|
|
231
|
+
return line.length > 0 ? line.endsWith("\r") ? line.slice(0, -1) : line : null;
|
|
232
|
+
} catch {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
} finally {
|
|
236
|
+
if (fd !== null) {
|
|
237
|
+
try {
|
|
238
|
+
closeSync(fd);
|
|
239
|
+
} catch {}
|
|
240
|
+
}
|
|
198
241
|
}
|
|
199
242
|
}
|
|
200
243
|
function formatTarget(provider, id) {
|
|
@@ -470,18 +513,66 @@ function resolveNativeGoosedumpBinary(environment = process.env) {
|
|
|
470
513
|
}
|
|
471
514
|
}
|
|
472
515
|
function runGoosedumpAsync(args, signal, executablePath = resolveNativeGoosedumpBinary()) {
|
|
516
|
+
if (signal?.aborted)
|
|
517
|
+
return Promise.reject(signal.reason);
|
|
473
518
|
return new Promise((resolve, reject) => {
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
if (
|
|
481
|
-
|
|
482
|
-
|
|
519
|
+
const processGroup = process.platform !== "win32";
|
|
520
|
+
const output = { stdout: "", stderr: "" };
|
|
521
|
+
const bytes = { stdout: 0, stderr: 0 };
|
|
522
|
+
let child;
|
|
523
|
+
let failure;
|
|
524
|
+
const abort = () => {
|
|
525
|
+
if (child?.pid === undefined)
|
|
526
|
+
return;
|
|
527
|
+
try {
|
|
528
|
+
if (processGroup)
|
|
529
|
+
process.kill(-child.pid, "SIGTERM");
|
|
530
|
+
else
|
|
531
|
+
child.kill("SIGTERM");
|
|
532
|
+
} catch (error) {
|
|
533
|
+
if (error.code !== "ESRCH") {
|
|
534
|
+
failure = new Error("failed to stop goosedump process", { cause: error });
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
const capture = (stream, chunk) => {
|
|
539
|
+
if (failure)
|
|
540
|
+
return;
|
|
541
|
+
bytes[stream] += Buffer.byteLength(chunk);
|
|
542
|
+
if (bytes[stream] > 16 * 1024 * 1024) {
|
|
543
|
+
failure = new Error(`goosedump ${stream} exceeded the 16 MiB buffer limit`);
|
|
544
|
+
abort();
|
|
545
|
+
} else {
|
|
546
|
+
output[stream] += chunk;
|
|
547
|
+
}
|
|
548
|
+
};
|
|
549
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
550
|
+
try {
|
|
551
|
+
child = spawn(executablePath, args, {
|
|
552
|
+
detached: processGroup,
|
|
553
|
+
signal,
|
|
554
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
555
|
+
...process.platform === "linux" ? { env: { ...process.env, APPIMAGE_EXTRACT_AND_RUN: "1" } } : {}
|
|
556
|
+
});
|
|
557
|
+
} catch (error) {
|
|
558
|
+
signal?.removeEventListener("abort", abort);
|
|
559
|
+
reject(error);
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
child.stdout?.setEncoding("utf8");
|
|
563
|
+
child.stderr?.setEncoding("utf8");
|
|
564
|
+
child.stdout?.on("data", (chunk) => capture("stdout", chunk));
|
|
565
|
+
child.stderr?.on("data", (chunk) => capture("stderr", chunk));
|
|
566
|
+
child.on("error", (error) => {
|
|
567
|
+
failure ??= error;
|
|
568
|
+
});
|
|
569
|
+
child.once("close", (code, exitSignal) => {
|
|
570
|
+
signal?.removeEventListener("abort", abort);
|
|
571
|
+
if (failure || code !== 0) {
|
|
572
|
+
const message = output.stderr.trim() || failure?.message || `goosedump exited with ${exitSignal ?? `code ${code}`}`;
|
|
573
|
+
reject(new Error(message, { cause: failure }));
|
|
483
574
|
} else {
|
|
484
|
-
resolve(
|
|
575
|
+
resolve(output);
|
|
485
576
|
}
|
|
486
577
|
});
|
|
487
578
|
});
|
|
@@ -597,7 +688,12 @@ async function goosedumpCompact(target, options, signal, executablePath) {
|
|
|
597
688
|
return { summary: output.stdout.trim(), warning: output.stderr.trim() };
|
|
598
689
|
}
|
|
599
690
|
function currentSessionTarget(ctx) {
|
|
600
|
-
const
|
|
691
|
+
const sessionManager = ctx.sessionManager;
|
|
692
|
+
const inMemoryId = sessionManager?.getSessionId?.() ?? sessionManager?.getHeader?.()?.id;
|
|
693
|
+
if (typeof inMemoryId === "string" && inMemoryId.trim().length > 0) {
|
|
694
|
+
return formatTarget("pi", inMemoryId.trim());
|
|
695
|
+
}
|
|
696
|
+
const sessionFile = sessionManager?.getSessionFile?.();
|
|
601
697
|
if (!sessionFile)
|
|
602
698
|
return null;
|
|
603
699
|
const line = readFirstLine(sessionFile);
|
|
@@ -605,8 +701,8 @@ function currentSessionTarget(ctx) {
|
|
|
605
701
|
return null;
|
|
606
702
|
try {
|
|
607
703
|
const entry = JSON.parse(line);
|
|
608
|
-
if (entry.type === "session" && typeof entry.id === "string") {
|
|
609
|
-
return formatTarget("pi", entry.id);
|
|
704
|
+
if (entry.type === "session" && typeof entry.id === "string" && entry.id.trim().length > 0) {
|
|
705
|
+
return formatTarget("pi", entry.id.trim());
|
|
610
706
|
}
|
|
611
707
|
} catch {
|
|
612
708
|
return null;
|
|
@@ -618,7 +714,7 @@ function currentSessionTarget(ctx) {
|
|
|
618
714
|
import { DEFAULT_COMPACTION_SETTINGS as DEFAULT_COMPACTION_SETTINGS2 } from "@earendil-works/pi-coding-agent";
|
|
619
715
|
|
|
620
716
|
// src/compaction.ts
|
|
621
|
-
import { findCutPoint } from "@earendil-works/pi-coding-agent";
|
|
717
|
+
import { findCutPoint, sessionEntryToContextMessages } from "@earendil-works/pi-coding-agent";
|
|
622
718
|
|
|
623
719
|
// src/settings.ts
|
|
624
720
|
import { DEFAULT_COMPACTION_SETTINGS } from "@earendil-works/pi-coding-agent";
|
|
@@ -733,9 +829,6 @@ function writeJsonObjectAtomically(path, value) {
|
|
|
733
829
|
throw error;
|
|
734
830
|
}
|
|
735
831
|
}
|
|
736
|
-
function readGoosedumpSettingsFrom(path) {
|
|
737
|
-
return pickValidGoosedumpSettings(readJsonObject(path).goosedump);
|
|
738
|
-
}
|
|
739
832
|
function goosedumpSettingValidationMessage(key) {
|
|
740
833
|
const spec = GOOSEDUMP_SETTING_SPECS[key];
|
|
741
834
|
if (spec.kind === "boolean")
|
|
@@ -756,13 +849,33 @@ function validateGoosedumpSettings(path) {
|
|
|
756
849
|
}
|
|
757
850
|
}
|
|
758
851
|
}
|
|
759
|
-
|
|
760
|
-
|
|
852
|
+
var isNumberValue = (value) => typeof value === "number";
|
|
853
|
+
var isBooleanValue = (value) => typeof value === "boolean";
|
|
854
|
+
function resolveWorkspaceSettings(cwd, includeProject) {
|
|
855
|
+
const globalObj = readJsonObject(globalSettingsPath());
|
|
856
|
+
const projectObj = includeProject ? readJsonObject(projectSettingsPath(cwd)) : undefined;
|
|
857
|
+
const goosedump = {
|
|
761
858
|
...DEFAULT_GOOSEDUMP_SETTINGS,
|
|
762
|
-
...
|
|
763
|
-
...
|
|
859
|
+
...pickValidGoosedumpSettings(globalObj.goosedump),
|
|
860
|
+
...projectObj ? pickValidGoosedumpSettings(projectObj.goosedump) : {}
|
|
861
|
+
};
|
|
862
|
+
const projectCompaction = isRecord(projectObj?.compaction) ? projectObj.compaction : undefined;
|
|
863
|
+
const globalCompaction = isRecord(globalObj.compaction) ? globalObj.compaction : undefined;
|
|
864
|
+
const enabled = (isBooleanValue(projectCompaction?.enabled) ? projectCompaction.enabled : undefined) ?? (isBooleanValue(globalCompaction?.enabled) ? globalCompaction.enabled : undefined) ?? DEFAULT_COMPACTION_SETTINGS.enabled;
|
|
865
|
+
const keepRecentTokens = (isNumberValue(projectCompaction?.keepRecentTokens) ? projectCompaction.keepRecentTokens : undefined) ?? (isNumberValue(globalCompaction?.keepRecentTokens) ? globalCompaction.keepRecentTokens : undefined) ?? DEFAULT_COMPACTION_SETTINGS.keepRecentTokens;
|
|
866
|
+
const reserveTokens = (isNumberValue(projectCompaction?.reserveTokens) ? projectCompaction.reserveTokens : undefined) ?? (isNumberValue(globalCompaction?.reserveTokens) ? globalCompaction.reserveTokens : undefined) ?? DEFAULT_COMPACTION_SETTINGS.reserveTokens;
|
|
867
|
+
return {
|
|
868
|
+
goosedump,
|
|
869
|
+
compaction: {
|
|
870
|
+
enabled,
|
|
871
|
+
keepRecentTokens,
|
|
872
|
+
reserveTokens
|
|
873
|
+
}
|
|
764
874
|
};
|
|
765
875
|
}
|
|
876
|
+
function resolveGoosedumpSettings(cwd, includeProject) {
|
|
877
|
+
return resolveWorkspaceSettings(cwd, includeProject).goosedump;
|
|
878
|
+
}
|
|
766
879
|
function notifyInvalidSettings(ctx) {
|
|
767
880
|
const paths = [
|
|
768
881
|
globalSettingsPath(),
|
|
@@ -819,30 +932,13 @@ function clearScopedGoosedumpSetting(cwd, scope, key) {
|
|
|
819
932
|
function formatCompactSetting(value) {
|
|
820
933
|
return value === 0 ? "disabled" : String(value);
|
|
821
934
|
}
|
|
822
|
-
var isNumberValue = (value) => typeof value === "number";
|
|
823
|
-
var isBooleanValue = (value) => typeof value === "boolean";
|
|
824
|
-
function piCompactionValue(cwd, includeProject, key, is, fallback) {
|
|
825
|
-
const paths = includeProject ? [projectSettingsPath(cwd), globalSettingsPath()] : [globalSettingsPath()];
|
|
826
|
-
for (const path of paths) {
|
|
827
|
-
const compaction = readJsonObject(path).compaction;
|
|
828
|
-
const value = isRecord(compaction) ? compaction[key] : undefined;
|
|
829
|
-
if (is(value))
|
|
830
|
-
return value;
|
|
831
|
-
}
|
|
832
|
-
return fallback;
|
|
833
|
-
}
|
|
834
935
|
function piKeepRecentTokens(cwd, includeProject) {
|
|
835
|
-
return
|
|
836
|
-
}
|
|
837
|
-
function piCompactionEnabled(cwd, includeProject) {
|
|
838
|
-
return piCompactionValue(cwd, includeProject, "enabled", isBooleanValue, DEFAULT_COMPACTION_SETTINGS.enabled);
|
|
839
|
-
}
|
|
840
|
-
function piReserveTokens(cwd, includeProject) {
|
|
841
|
-
return piCompactionValue(cwd, includeProject, "reserveTokens", isNumberValue, DEFAULT_COMPACTION_SETTINGS.reserveTokens);
|
|
936
|
+
return resolveWorkspaceSettings(cwd, includeProject).compaction.keepRecentTokens;
|
|
842
937
|
}
|
|
843
938
|
function effectiveSummaryMaxTokens(cwd, includeProject) {
|
|
844
|
-
const
|
|
845
|
-
const
|
|
939
|
+
const settings = resolveWorkspaceSettings(cwd, includeProject);
|
|
940
|
+
const configured = settings.goosedump.summaryMaxTokens;
|
|
941
|
+
const reserveTokens = settings.compaction.reserveTokens;
|
|
846
942
|
if (reserveTokens <= 0)
|
|
847
943
|
return configured;
|
|
848
944
|
return Math.max(1, Math.min(configured, Math.floor(reserveTokens * 0.8)));
|
|
@@ -858,6 +954,11 @@ function isMessageEntry(entry) {
|
|
|
858
954
|
function isCompletedAssistant(message) {
|
|
859
955
|
return message.role === "assistant" && message.stopReason !== "error" && message.stopReason !== "aborted";
|
|
860
956
|
}
|
|
957
|
+
function hasCompactionMessage(entry) {
|
|
958
|
+
if (entry.type === "compaction")
|
|
959
|
+
return false;
|
|
960
|
+
return sessionEntryToContextMessages(entry).length > 0;
|
|
961
|
+
}
|
|
861
962
|
function hasCompactableHistory(ctx) {
|
|
862
963
|
const branch = ctx.sessionManager.getBranch();
|
|
863
964
|
if (branch.length === 0)
|
|
@@ -868,17 +969,10 @@ function hasCompactableHistory(ctx) {
|
|
|
868
969
|
const cut = findCutPoint(branch, boundaryStart, branch.length, piKeepRecentTokens(ctx.cwd, projectSettingsTrusted(ctx)));
|
|
869
970
|
if (!branch[cut.firstKeptEntryIndex]?.id)
|
|
870
971
|
return false;
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
if (SUMMARIZABLE_ENTRY_TYPES.has(branch[i].type))
|
|
972
|
+
for (let i = boundaryStart;i < cut.firstKeptEntryIndex; i += 1) {
|
|
973
|
+
if (hasCompactionMessage(branch[i]))
|
|
874
974
|
return true;
|
|
875
975
|
}
|
|
876
|
-
if (cut.isSplitTurn) {
|
|
877
|
-
for (let i = cut.turnStartIndex;i < cut.firstKeptEntryIndex; i += 1) {
|
|
878
|
-
if (SUMMARIZABLE_ENTRY_TYPES.has(branch[i].type))
|
|
879
|
-
return true;
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
976
|
return false;
|
|
883
977
|
}
|
|
884
978
|
function latestCompaction(branchEntries) {
|
|
@@ -901,16 +995,11 @@ function activeHistoryStartIndex(branchEntries) {
|
|
|
901
995
|
return compaction ? compactionBoundaryStartIndex(branchEntries, compaction) : 0;
|
|
902
996
|
}
|
|
903
997
|
function completedTurnsSinceCompaction(branchEntries) {
|
|
904
|
-
let start = 0;
|
|
905
|
-
for (let i = branchEntries.length - 1;i >= 0; i -= 1) {
|
|
906
|
-
if (isCompactionEntry(branchEntries[i])) {
|
|
907
|
-
start = i + 1;
|
|
908
|
-
break;
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
998
|
let completed = 0;
|
|
912
|
-
for (let i =
|
|
999
|
+
for (let i = branchEntries.length - 1;i >= 0; i -= 1) {
|
|
913
1000
|
const entry = branchEntries[i];
|
|
1001
|
+
if (isCompactionEntry(entry))
|
|
1002
|
+
break;
|
|
914
1003
|
if (isMessageEntry(entry) && isCompletedAssistant(entry.message))
|
|
915
1004
|
completed += 1;
|
|
916
1005
|
}
|
|
@@ -929,26 +1018,21 @@ function keepBoundaryId(branchEntries, keep) {
|
|
|
929
1018
|
}
|
|
930
1019
|
return;
|
|
931
1020
|
}
|
|
932
|
-
function latestCompactionForRange(branchEntries, untilIndex) {
|
|
933
|
-
for (let i = branchEntries.length - 1;i >= 0; i -= 1) {
|
|
934
|
-
const entry = branchEntries[i];
|
|
935
|
-
if (!isCompactionEntry(entry))
|
|
936
|
-
continue;
|
|
937
|
-
if (compactionBoundaryStartIndex(branchEntries, entry) < untilIndex)
|
|
938
|
-
return entry;
|
|
939
|
-
}
|
|
940
|
-
return;
|
|
941
|
-
}
|
|
942
|
-
function compactionStartId(branchEntries, previousCompaction, untilIndex) {
|
|
943
|
-
const boundaryStart = compactionBoundaryStartIndex(branchEntries, previousCompaction);
|
|
944
|
-
return boundaryStart < untilIndex ? branchEntries[boundaryStart]?.id : undefined;
|
|
945
|
-
}
|
|
946
1021
|
function buildCompactRange(branchEntries, firstKeptEntryId) {
|
|
947
1022
|
const untilIndex = branchEntries.findIndex((entry) => entry.id === firstKeptEntryId);
|
|
948
1023
|
if (untilIndex < 0)
|
|
949
1024
|
return;
|
|
950
|
-
|
|
951
|
-
|
|
1025
|
+
let from;
|
|
1026
|
+
for (let i = Math.min(untilIndex - 1, branchEntries.length - 1);i >= 0; i -= 1) {
|
|
1027
|
+
const entry = branchEntries[i];
|
|
1028
|
+
if (!isCompactionEntry(entry))
|
|
1029
|
+
continue;
|
|
1030
|
+
const boundaryStart = compactionBoundaryStartIndex(branchEntries, entry);
|
|
1031
|
+
if (boundaryStart < untilIndex) {
|
|
1032
|
+
from = branchEntries[boundaryStart]?.id;
|
|
1033
|
+
break;
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
952
1036
|
return from && from !== firstKeptEntryId ? { scope: "lineage", from, until: firstKeptEntryId } : { scope: "lineage", until: firstKeptEntryId };
|
|
953
1037
|
}
|
|
954
1038
|
async function buildGoosedumpCompaction(target, preparation, branchEntries, summaryMaxTokens, keep, signal, executablePath) {
|
|
@@ -993,6 +1077,11 @@ function isCompactionCancellation(error) {
|
|
|
993
1077
|
const code = error.code;
|
|
994
1078
|
return error.name === "AbortError" || code === "ABORT_ERR" || isCompactionCancellationMessage(error.message);
|
|
995
1079
|
}
|
|
1080
|
+
function isSessionTooSmallError(error) {
|
|
1081
|
+
if (!(error instanceof Error))
|
|
1082
|
+
return false;
|
|
1083
|
+
return /nothing to compact|session too small|already compacted/i.test(error.message);
|
|
1084
|
+
}
|
|
996
1085
|
|
|
997
1086
|
class CompactionRuntime {
|
|
998
1087
|
overrideDefaultCompaction;
|
|
@@ -1004,6 +1093,7 @@ class CompactionRuntime {
|
|
|
1004
1093
|
piCompactionReserveTokens = DEFAULT_COMPACTION_SETTINGS2.reserveTokens;
|
|
1005
1094
|
turnsRemaining = 0;
|
|
1006
1095
|
counterTriggered = false;
|
|
1096
|
+
smallSessionDisarmed = false;
|
|
1007
1097
|
compactionState = { phase: "idle" };
|
|
1008
1098
|
sessionGeneration = 0;
|
|
1009
1099
|
settlementGeneration = 0;
|
|
@@ -1023,6 +1113,7 @@ class CompactionRuntime {
|
|
|
1023
1113
|
this.activePi = pi;
|
|
1024
1114
|
}
|
|
1025
1115
|
requestCompaction(ctx, keep) {
|
|
1116
|
+
this.smallSessionDisarmed = false;
|
|
1026
1117
|
if (this.compactionState.phase !== "idle")
|
|
1027
1118
|
return "busy";
|
|
1028
1119
|
if (!this.requestHasCompactableHistory(ctx, keep))
|
|
@@ -1042,14 +1133,17 @@ class CompactionRuntime {
|
|
|
1042
1133
|
}
|
|
1043
1134
|
syncCompactCounters(ctx) {
|
|
1044
1135
|
const projectTrusted = projectSettingsTrusted(ctx);
|
|
1045
|
-
|
|
1046
|
-
this.
|
|
1047
|
-
this.
|
|
1136
|
+
const resolved = resolveWorkspaceSettings(ctx.cwd, projectTrusted);
|
|
1137
|
+
this.compactSettings = resolved.goosedump;
|
|
1138
|
+
this.piAutoCompactionEnabled = resolved.compaction.enabled;
|
|
1139
|
+
this.piCompactionReserveTokens = resolved.compaction.reserveTokens;
|
|
1048
1140
|
const completedTurns = completedTurnsSinceCompaction(ctx.sessionManager.getBranch());
|
|
1049
1141
|
this.turnsRemaining = Math.max(0, this.compactSettings.turnsBeforeCompact - completedTurns);
|
|
1050
1142
|
this.counterTriggered = this.compactSettings.turnsBeforeCompact > 0 && completedTurns >= this.compactSettings.turnsBeforeCompact;
|
|
1051
1143
|
}
|
|
1052
1144
|
updateStatus(ctx) {
|
|
1145
|
+
if (!ctx.hasUI)
|
|
1146
|
+
return;
|
|
1053
1147
|
const active = this.turnCompactionEnabled();
|
|
1054
1148
|
let dotColor = "success";
|
|
1055
1149
|
if (active) {
|
|
@@ -1060,10 +1154,17 @@ class CompactionRuntime {
|
|
|
1060
1154
|
dotColor = "warning";
|
|
1061
1155
|
}
|
|
1062
1156
|
}
|
|
1063
|
-
const dot = ctx.ui.theme.fg(dotColor, "●");
|
|
1064
1157
|
const counter = active ? Math.max(0, this.turnsRemaining).toString(16).toUpperCase().padStart(2, "0") : "--";
|
|
1065
1158
|
const activity = this.compactionState.phase === "queued" ? " queued" : this.compactionState.phase === "running" ? " compacting" : "";
|
|
1066
|
-
|
|
1159
|
+
let dot = "●";
|
|
1160
|
+
let label = `GD:${counter}${activity}`;
|
|
1161
|
+
try {
|
|
1162
|
+
if (ctx.ui?.theme) {
|
|
1163
|
+
dot = ctx.ui.theme.fg(dotColor, "●");
|
|
1164
|
+
const boldGd = ctx.ui.theme.bold?.("GD") ?? "GD";
|
|
1165
|
+
label = ctx.ui.theme.fg("text", `${boldGd}:${counter}${activity}`);
|
|
1166
|
+
}
|
|
1167
|
+
} catch {}
|
|
1067
1168
|
ctx.ui.setStatus("goosedump", `${dot} ${label}`);
|
|
1068
1169
|
}
|
|
1069
1170
|
beginSession(ctx) {
|
|
@@ -1072,6 +1173,7 @@ class CompactionRuntime {
|
|
|
1072
1173
|
this.activeSessionFile = ctx.sessionManager.getSessionFile?.() ?? null;
|
|
1073
1174
|
this.sessionActive = true;
|
|
1074
1175
|
this.compactionState = { phase: "idle" };
|
|
1176
|
+
this.smallSessionDisarmed = false;
|
|
1075
1177
|
this.expectedTurnBoundaryAbort = false;
|
|
1076
1178
|
this.syncCompactCounters(ctx);
|
|
1077
1179
|
this.updateStatus(ctx);
|
|
@@ -1084,6 +1186,7 @@ class CompactionRuntime {
|
|
|
1084
1186
|
this.activeSessionFile = null;
|
|
1085
1187
|
this.sessionActive = false;
|
|
1086
1188
|
this.compactionState = { phase: "idle" };
|
|
1189
|
+
this.smallSessionDisarmed = false;
|
|
1087
1190
|
this.expectedTurnBoundaryAbort = false;
|
|
1088
1191
|
}
|
|
1089
1192
|
onAgentStart(ctx) {
|
|
@@ -1308,11 +1411,13 @@ class CompactionRuntime {
|
|
|
1308
1411
|
}
|
|
1309
1412
|
resetCompactCounters(ctx) {
|
|
1310
1413
|
const projectTrusted = projectSettingsTrusted(ctx);
|
|
1311
|
-
|
|
1312
|
-
this.
|
|
1313
|
-
this.
|
|
1414
|
+
const resolved = resolveWorkspaceSettings(ctx.cwd, projectTrusted);
|
|
1415
|
+
this.compactSettings = resolved.goosedump;
|
|
1416
|
+
this.piAutoCompactionEnabled = resolved.compaction.enabled;
|
|
1417
|
+
this.piCompactionReserveTokens = resolved.compaction.reserveTokens;
|
|
1314
1418
|
this.turnsRemaining = this.compactSettings.turnsBeforeCompact;
|
|
1315
1419
|
this.counterTriggered = false;
|
|
1420
|
+
this.smallSessionDisarmed = false;
|
|
1316
1421
|
}
|
|
1317
1422
|
turnCompactionEnabled() {
|
|
1318
1423
|
return this.overrideDefaultCompaction && this.piAutoCompactionEnabled && this.compactSettings.turnsBeforeCompact > 0;
|
|
@@ -1331,6 +1436,7 @@ class CompactionRuntime {
|
|
|
1331
1436
|
noteCompletedTurn(ctx) {
|
|
1332
1437
|
if (!this.overrideDefaultCompaction)
|
|
1333
1438
|
return;
|
|
1439
|
+
this.smallSessionDisarmed = false;
|
|
1334
1440
|
this.syncCompactCounters(ctx);
|
|
1335
1441
|
this.updateStatus(ctx);
|
|
1336
1442
|
}
|
|
@@ -1390,14 +1496,19 @@ class CompactionRuntime {
|
|
|
1390
1496
|
}, { once: true });
|
|
1391
1497
|
}
|
|
1392
1498
|
requestHasCompactableHistory(ctx, keep) {
|
|
1393
|
-
|
|
1499
|
+
if (!hasCompactableHistory(ctx))
|
|
1500
|
+
return false;
|
|
1501
|
+
return keep === null || keepBoundaryId(ctx.sessionManager.getBranch(), keep) !== undefined;
|
|
1394
1502
|
}
|
|
1395
1503
|
queueTurnCompaction(ctx, eligibleSettlement, resumeAfter = false) {
|
|
1396
|
-
if (!this.turnCompactionEnabled() || !this.counterTriggered || this.compactionState.phase !== "idle") {
|
|
1504
|
+
if (!this.turnCompactionEnabled() || !this.counterTriggered || this.smallSessionDisarmed || this.compactionState.phase !== "idle") {
|
|
1397
1505
|
return;
|
|
1398
1506
|
}
|
|
1399
|
-
if (!hasCompactableHistory(ctx))
|
|
1507
|
+
if (!hasCompactableHistory(ctx)) {
|
|
1508
|
+
this.counterTriggered = false;
|
|
1509
|
+
this.smallSessionDisarmed = true;
|
|
1400
1510
|
return;
|
|
1511
|
+
}
|
|
1401
1512
|
this.setCompactionState(ctx, {
|
|
1402
1513
|
phase: "queued",
|
|
1403
1514
|
request: {
|
|
@@ -1425,6 +1536,10 @@ class CompactionRuntime {
|
|
|
1425
1536
|
if (!ctx.isIdle())
|
|
1426
1537
|
return false;
|
|
1427
1538
|
if (!this.requestHasCompactableHistory(ctx, request.keep)) {
|
|
1539
|
+
if (request.origin === "turn") {
|
|
1540
|
+
this.counterTriggered = false;
|
|
1541
|
+
this.smallSessionDisarmed = true;
|
|
1542
|
+
}
|
|
1428
1543
|
this.setCompactionState(ctx, { phase: "idle" });
|
|
1429
1544
|
return false;
|
|
1430
1545
|
}
|
|
@@ -1453,8 +1568,12 @@ class CompactionRuntime {
|
|
|
1453
1568
|
return;
|
|
1454
1569
|
this.setCompactionState(ctx, { phase: "idle" });
|
|
1455
1570
|
this.syncCompactCounters(ctx);
|
|
1571
|
+
if (isSessionTooSmallError(error)) {
|
|
1572
|
+
this.counterTriggered = false;
|
|
1573
|
+
this.smallSessionDisarmed = true;
|
|
1574
|
+
}
|
|
1456
1575
|
this.updateStatus(ctx);
|
|
1457
|
-
if (isCompactionCancellation(error))
|
|
1576
|
+
if (isCompactionCancellation(error) || isSessionTooSmallError(error))
|
|
1458
1577
|
return;
|
|
1459
1578
|
const message = error instanceof Error ? error.message : String(error);
|
|
1460
1579
|
if (ctx.hasUI)
|
|
@@ -1576,13 +1695,22 @@ var MEMORY_ACTIVITY_STATUS_KEY = "goosedump-memory-activity";
|
|
|
1576
1695
|
function showMemoryActivity(ctx) {
|
|
1577
1696
|
if (!ctx.hasUI)
|
|
1578
1697
|
return;
|
|
1579
|
-
|
|
1580
|
-
|
|
1698
|
+
let dot = "●";
|
|
1699
|
+
let label = "remembering";
|
|
1700
|
+
try {
|
|
1701
|
+
if (ctx.ui?.theme) {
|
|
1702
|
+
dot = ctx.ui.theme.fg("accent", "●");
|
|
1703
|
+
label = ctx.ui.theme.fg("muted", "remembering");
|
|
1704
|
+
}
|
|
1705
|
+
} catch {}
|
|
1581
1706
|
ctx.ui.setStatus(MEMORY_ACTIVITY_STATUS_KEY, `${dot} ${label}`);
|
|
1582
1707
|
}
|
|
1583
1708
|
function hideMemoryActivity(ctx) {
|
|
1584
|
-
if (ctx.hasUI)
|
|
1585
|
-
|
|
1709
|
+
if (ctx.hasUI) {
|
|
1710
|
+
try {
|
|
1711
|
+
ctx.ui.setStatus(MEMORY_ACTIVITY_STATUS_KEY, undefined);
|
|
1712
|
+
} catch {}
|
|
1713
|
+
}
|
|
1586
1714
|
}
|
|
1587
1715
|
|
|
1588
1716
|
// src/policy.ts
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goosedump",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.69",
|
|
4
4
|
"description": "Pi extension for goosedump session search, durable memory, session management, and compaction",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"compaction",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"ci:test": "npm test"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@jarkkojs/goosedump-api": "^0.12.
|
|
40
|
+
"@jarkkojs/goosedump-api": "^0.12.69"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@earendil-works/pi-coding-agent": "^0.84.2",
|