pi-goosedump 0.12.40 → 0.12.41
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 +4 -3
- package/dist/index.ts +179 -120
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -9,10 +9,11 @@ management, and compaction. Tools use the `goose_` prefix.
|
|
|
9
9
|
pi install npm:pi-goosedump
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
Bundled native binaries: Linux x64, macOS arm64, Windows x64.
|
|
12
|
+
Bundled native binaries: Linux x64 and arm64, macOS arm64, and Windows x64.
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
Unsupported platforms are rejected when the extension loads. A missing packaged binary is treated as
|
|
15
|
+
a broken installation instead of falling back to a `goosedump` executable on `PATH`. Local models
|
|
16
|
+
download on first use.
|
|
16
17
|
|
|
17
18
|
## Tools
|
|
18
19
|
|
package/dist/index.ts
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// Copyright (C) Jarkko Sakkinen 2026
|
|
3
3
|
|
|
4
|
-
// src/integration.ts
|
|
5
|
-
import { execFileSync } from "node:child_process";
|
|
6
|
-
|
|
7
4
|
// src/cli.ts
|
|
8
5
|
import { execFile } from "node:child_process";
|
|
9
6
|
import { existsSync } from "node:fs";
|
|
@@ -34,7 +31,6 @@ var TURNS_BEFORE_COMPACT_MIN = 0;
|
|
|
34
31
|
var TURNS_BEFORE_COMPACT_MAX = 255;
|
|
35
32
|
var SUMMARY_MAX_TOKENS_MIN = 512;
|
|
36
33
|
var SUMMARY_MAX_TOKENS_MAX = 65536;
|
|
37
|
-
var GOOSEDUMP_UNAVAILABLE_MESSAGE = "goosedump is not available. Install with: npm install @jarkkojs/goosedump";
|
|
38
34
|
var GOOSE_PROVIDERS = [
|
|
39
35
|
"pi",
|
|
40
36
|
"claude",
|
|
@@ -63,9 +59,6 @@ function missingSessionId(tool, provider = "pi") {
|
|
|
63
59
|
}
|
|
64
60
|
return textResult(`session is required for ${tool} when no current session is available`);
|
|
65
61
|
}
|
|
66
|
-
function goosedumpUnavailableResult() {
|
|
67
|
-
return textResult(GOOSEDUMP_UNAVAILABLE_MESSAGE);
|
|
68
|
-
}
|
|
69
62
|
function compactStartedMessage(keep, queued = false) {
|
|
70
63
|
const action = queued ? "Compaction queued with goosedump" : "Compacting with goosedump";
|
|
71
64
|
return keep !== null ? `${action}; keeping the last ${keep} user turn${keep === 1 ? "" : "s"}.` : `${action} up to Pi's token-based cut.`;
|
|
@@ -181,6 +174,12 @@ function parsePiMessages(output) {
|
|
|
181
174
|
role: "user",
|
|
182
175
|
content: flattenPiContent(entry.content)
|
|
183
176
|
});
|
|
177
|
+
} else if (entry.type === "compaction") {
|
|
178
|
+
messages.push({
|
|
179
|
+
id: entry.id ?? "",
|
|
180
|
+
role: "compactionSummary",
|
|
181
|
+
content: entry.summary ?? ""
|
|
182
|
+
});
|
|
184
183
|
} else if (entry.type === "branch_summary") {
|
|
185
184
|
messages.push({
|
|
186
185
|
id: entry.id ?? "",
|
|
@@ -283,33 +282,40 @@ ${m.content}`).join(`
|
|
|
283
282
|
|
|
284
283
|
// src/cli.ts
|
|
285
284
|
var require2 = createRequire(import.meta.url);
|
|
286
|
-
function
|
|
287
|
-
return require2.resolve("@jarkkojs/goosedump/bin/goosedump.js");
|
|
288
|
-
}
|
|
289
|
-
function resolveNativeGoosedumpBinary() {
|
|
290
|
-
const target = `${process.platform}-${process.arch}`;
|
|
291
|
-
let packageName;
|
|
292
|
-
let binaryName = "goosedump";
|
|
285
|
+
function nativeGoosedumpPackage(target) {
|
|
293
286
|
switch (target) {
|
|
294
287
|
case "linux-x64":
|
|
295
|
-
packageName
|
|
296
|
-
|
|
288
|
+
return { packageName: "@jarkkojs/goosedump-linux-x64", binaryName: "goosedump" };
|
|
289
|
+
case "linux-arm64":
|
|
290
|
+
return { packageName: "@jarkkojs/goosedump-linux-arm64", binaryName: "goosedump" };
|
|
297
291
|
case "darwin-arm64":
|
|
298
|
-
packageName
|
|
299
|
-
break;
|
|
292
|
+
return { packageName: "@jarkkojs/goosedump-darwin-arm64", binaryName: "goosedump" };
|
|
300
293
|
case "win32-x64":
|
|
301
|
-
packageName
|
|
302
|
-
binaryName = "goosedump.exe";
|
|
303
|
-
break;
|
|
294
|
+
return { packageName: "@jarkkojs/goosedump-win32-x64", binaryName: "goosedump.exe" };
|
|
304
295
|
default:
|
|
305
296
|
throw new Error(`unsupported platform architecture: ${target}`);
|
|
306
297
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
298
|
+
}
|
|
299
|
+
function resolveGoosedumpBinary() {
|
|
300
|
+
try {
|
|
301
|
+
return require2.resolve("@jarkkojs/goosedump/bin/goosedump.js");
|
|
302
|
+
} catch (cause) {
|
|
303
|
+
throw new Error("broken pi-goosedump installation: cannot resolve the @jarkkojs/goosedump dependency", { cause });
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
function resolveNativeGoosedumpBinary() {
|
|
307
|
+
const target = `${process.platform}-${process.arch}`;
|
|
308
|
+
const { packageName, binaryName } = nativeGoosedumpPackage(target);
|
|
309
|
+
try {
|
|
310
|
+
const wrapperRequire = createRequire(resolveGoosedumpBinary());
|
|
311
|
+
const packageDir = dirname(wrapperRequire.resolve(`${packageName}/package.json`));
|
|
312
|
+
const binaryPath = join(packageDir, "bin", binaryName);
|
|
313
|
+
if (!existsSync(binaryPath))
|
|
314
|
+
throw new Error(`binary not found: ${binaryPath}`);
|
|
315
|
+
return binaryPath;
|
|
316
|
+
} catch (cause) {
|
|
317
|
+
throw new Error(`broken pi-goosedump installation: cannot resolve the packaged binary ${packageName} for ${target}`, { cause });
|
|
318
|
+
}
|
|
313
319
|
}
|
|
314
320
|
function runGoosedumpAsync(args, signal, executablePath = resolveNativeGoosedumpBinary()) {
|
|
315
321
|
return new Promise((resolve, reject) => {
|
|
@@ -384,12 +390,16 @@ async function goosedumpMemoryStatus(signal, executablePath) {
|
|
|
384
390
|
async function goosedumpListSessions(signal, executablePath) {
|
|
385
391
|
const args = ["session", "list", "--json"];
|
|
386
392
|
const output = await runGoosedumpAsync(args, signal, executablePath);
|
|
393
|
+
let parsed;
|
|
387
394
|
try {
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
395
|
+
parsed = JSON.parse(output.stdout);
|
|
396
|
+
} catch (error) {
|
|
397
|
+
throw new Error("invalid goosedump session list JSON", { cause: error });
|
|
398
|
+
}
|
|
399
|
+
if (!Array.isArray(parsed)) {
|
|
400
|
+
throw new Error("invalid goosedump session list JSON: expected an array");
|
|
392
401
|
}
|
|
402
|
+
return parsed;
|
|
393
403
|
}
|
|
394
404
|
async function goosedumpRemoveSession(target, signal, executablePath) {
|
|
395
405
|
const args = ["session", "remove", target, "--yes"];
|
|
@@ -520,13 +530,32 @@ function readJsonObjectStrict(path) {
|
|
|
520
530
|
throw new Error(`${path} must contain a JSON object`);
|
|
521
531
|
return parsed;
|
|
522
532
|
}
|
|
533
|
+
function replaceFileAtomically(temporaryPath, path) {
|
|
534
|
+
if (process.platform !== "win32" || !existsSync2(path)) {
|
|
535
|
+
renameSync(temporaryPath, path);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
const backupPath = `${path}.${randomUUID()}.bak`;
|
|
539
|
+
renameSync(path, backupPath);
|
|
540
|
+
try {
|
|
541
|
+
renameSync(temporaryPath, path);
|
|
542
|
+
} catch (error) {
|
|
543
|
+
try {
|
|
544
|
+
renameSync(backupPath, path);
|
|
545
|
+
} catch {}
|
|
546
|
+
throw error;
|
|
547
|
+
}
|
|
548
|
+
try {
|
|
549
|
+
unlinkSync(backupPath);
|
|
550
|
+
} catch {}
|
|
551
|
+
}
|
|
523
552
|
function writeJsonObjectAtomically(path, value) {
|
|
524
553
|
mkdirSync(dirname2(path), { recursive: true });
|
|
525
554
|
const temporaryPath = `${path}.${randomUUID()}.tmp`;
|
|
526
555
|
try {
|
|
527
556
|
writeFileSync(temporaryPath, `${JSON.stringify(value, null, 2)}
|
|
528
557
|
`, { flag: "wx" });
|
|
529
|
-
|
|
558
|
+
replaceFileAtomically(temporaryPath, path);
|
|
530
559
|
} catch (error) {
|
|
531
560
|
try {
|
|
532
561
|
unlinkSync(temporaryPath);
|
|
@@ -784,10 +813,10 @@ function isCompactionCancellation(error) {
|
|
|
784
813
|
class CompactionRuntime {
|
|
785
814
|
overrideDefaultCompaction;
|
|
786
815
|
nativeExecutablePath;
|
|
787
|
-
isAvailable;
|
|
788
816
|
enqueueRemember;
|
|
789
817
|
compactSettings = DEFAULT_GOOSEDUMP_SETTINGS;
|
|
790
818
|
piAutoCompactionEnabled = DEFAULT_COMPACTION_SETTINGS2.enabled;
|
|
819
|
+
piCompactionReserveTokens = DEFAULT_COMPACTION_SETTINGS2.reserveTokens;
|
|
791
820
|
turnsRemaining = 0;
|
|
792
821
|
counterTriggered = false;
|
|
793
822
|
compactionState = { phase: "idle" };
|
|
@@ -800,7 +829,6 @@ class CompactionRuntime {
|
|
|
800
829
|
constructor(deps) {
|
|
801
830
|
this.overrideDefaultCompaction = deps.overrideDefaultCompaction;
|
|
802
831
|
this.nativeExecutablePath = deps.nativeExecutablePath;
|
|
803
|
-
this.isAvailable = deps.isAvailable;
|
|
804
832
|
this.enqueueRemember = deps.enqueueRemember;
|
|
805
833
|
}
|
|
806
834
|
setActivePi(pi) {
|
|
@@ -818,7 +846,8 @@ class CompactionRuntime {
|
|
|
818
846
|
origin: "manual",
|
|
819
847
|
sessionGeneration: this.sessionGeneration,
|
|
820
848
|
sessionFile: this.activeSessionFile,
|
|
821
|
-
eligibleSettlement: this.settlementGeneration + 1
|
|
849
|
+
eligibleSettlement: this.settlementGeneration + 1,
|
|
850
|
+
resumeAfter: false
|
|
822
851
|
}
|
|
823
852
|
});
|
|
824
853
|
return this.startQueuedCompaction(ctx) ? "started" : "queued";
|
|
@@ -827,13 +856,12 @@ class CompactionRuntime {
|
|
|
827
856
|
const projectTrusted = projectSettingsTrusted(ctx);
|
|
828
857
|
this.compactSettings = resolveGoosedumpSettings(ctx.cwd, projectTrusted);
|
|
829
858
|
this.piAutoCompactionEnabled = piCompactionEnabled(ctx.cwd, projectTrusted);
|
|
859
|
+
this.piCompactionReserveTokens = piReserveTokens(ctx.cwd, projectTrusted);
|
|
830
860
|
const completedTurns = completedTurnsSinceCompaction(ctx.sessionManager.getBranch());
|
|
831
861
|
this.turnsRemaining = Math.max(0, this.compactSettings.turnsBeforeCompact - completedTurns);
|
|
832
862
|
this.counterTriggered = this.compactSettings.turnsBeforeCompact > 0 && completedTurns >= this.compactSettings.turnsBeforeCompact;
|
|
833
863
|
}
|
|
834
864
|
updateStatus(ctx) {
|
|
835
|
-
if (!this.isAvailable())
|
|
836
|
-
return;
|
|
837
865
|
const active = this.turnCompactionEnabled();
|
|
838
866
|
let dotColor = "success";
|
|
839
867
|
if (active) {
|
|
@@ -859,7 +887,7 @@ class CompactionRuntime {
|
|
|
859
887
|
this.syncCompactCounters(ctx);
|
|
860
888
|
this.updateStatus(ctx);
|
|
861
889
|
this.queueTurnCompaction(ctx, 1);
|
|
862
|
-
this.
|
|
890
|
+
this.startQueuedCompaction(ctx);
|
|
863
891
|
}
|
|
864
892
|
endSession() {
|
|
865
893
|
this.sessionGeneration += 1;
|
|
@@ -879,12 +907,16 @@ class CompactionRuntime {
|
|
|
879
907
|
onTurnEnd(event, ctx) {
|
|
880
908
|
if (!isCompletedAssistant(event.message))
|
|
881
909
|
return;
|
|
882
|
-
|
|
883
|
-
this.
|
|
910
|
+
const resumeAfter = event.toolResults.length > 0 && !ctx.hasPendingMessages();
|
|
911
|
+
if (this.overrideDefaultCompaction) {
|
|
912
|
+
this.noteCompletedTurn(ctx);
|
|
913
|
+
const eligibleSettlement = this.settlementGeneration + 1;
|
|
914
|
+
this.queueTurnCompaction(ctx, eligibleSettlement, resumeAfter);
|
|
915
|
+
this.queueThresholdCompaction(ctx, eligibleSettlement, resumeAfter);
|
|
916
|
+
}
|
|
917
|
+
this.interruptToolLoopForQueuedCompaction(ctx, resumeAfter);
|
|
884
918
|
}
|
|
885
919
|
onAgentSettled(ctx) {
|
|
886
|
-
if (!this.isAvailable() || !this.overrideDefaultCompaction)
|
|
887
|
-
return;
|
|
888
920
|
this.settlementGeneration += 1;
|
|
889
921
|
this.syncCompactCounters(ctx);
|
|
890
922
|
this.updateStatus(ctx);
|
|
@@ -900,11 +932,11 @@ class CompactionRuntime {
|
|
|
900
932
|
this.queueTurnCompaction(ctx, this.settlementGeneration);
|
|
901
933
|
}
|
|
902
934
|
if (this.compactionState.phase === "queued" && this.compactionState.request.eligibleSettlement <= this.settlementGeneration) {
|
|
903
|
-
this.
|
|
935
|
+
this.startQueuedCompaction(ctx);
|
|
904
936
|
}
|
|
905
937
|
}
|
|
906
938
|
async onSessionBeforeCompact(event, ctx) {
|
|
907
|
-
if (!this.overrideDefaultCompaction
|
|
939
|
+
if (!this.overrideDefaultCompaction)
|
|
908
940
|
return;
|
|
909
941
|
const signal = event.signal;
|
|
910
942
|
if (signal?.aborted) {
|
|
@@ -1048,8 +1080,6 @@ class CompactionRuntime {
|
|
|
1048
1080
|
this.compactionState = { phase: "idle" };
|
|
1049
1081
|
this.resetCompactCounters(ctx);
|
|
1050
1082
|
this.updateStatus(ctx);
|
|
1051
|
-
if (!this.isAvailable())
|
|
1052
|
-
return;
|
|
1053
1083
|
const compactionEntry = event.compactionEntry;
|
|
1054
1084
|
if (!event.fromExtension || !compactionEntry || !isRecord(compactionEntry.details) || compactionEntry.details.source !== "goosedump") {
|
|
1055
1085
|
return;
|
|
@@ -1074,6 +1104,7 @@ class CompactionRuntime {
|
|
|
1074
1104
|
const projectTrusted = projectSettingsTrusted(ctx);
|
|
1075
1105
|
this.compactSettings = resolveGoosedumpSettings(ctx.cwd, projectTrusted);
|
|
1076
1106
|
this.piAutoCompactionEnabled = piCompactionEnabled(ctx.cwd, projectTrusted);
|
|
1107
|
+
this.piCompactionReserveTokens = piReserveTokens(ctx.cwd, projectTrusted);
|
|
1077
1108
|
this.turnsRemaining = this.compactSettings.turnsBeforeCompact;
|
|
1078
1109
|
this.counterTriggered = false;
|
|
1079
1110
|
}
|
|
@@ -1092,11 +1123,10 @@ class CompactionRuntime {
|
|
|
1092
1123
|
this.updateStatus(ctx);
|
|
1093
1124
|
}
|
|
1094
1125
|
noteCompletedTurn(ctx) {
|
|
1095
|
-
if (!this.
|
|
1126
|
+
if (!this.overrideDefaultCompaction)
|
|
1096
1127
|
return;
|
|
1097
1128
|
this.syncCompactCounters(ctx);
|
|
1098
1129
|
this.updateStatus(ctx);
|
|
1099
|
-
this.queueTurnCompaction(ctx, this.settlementGeneration + 1);
|
|
1100
1130
|
}
|
|
1101
1131
|
resumeAfterSuccessfulCompaction(ctx, resume) {
|
|
1102
1132
|
if (!resume || !this.activePi || ctx.hasPendingMessages())
|
|
@@ -1107,21 +1137,33 @@ class CompactionRuntime {
|
|
|
1107
1137
|
display: false
|
|
1108
1138
|
}, { triggerTurn: true });
|
|
1109
1139
|
}
|
|
1110
|
-
|
|
1111
|
-
if (!
|
|
1140
|
+
interruptToolLoopForQueuedCompaction(ctx, resumeAfter) {
|
|
1141
|
+
if (!resumeAfter || this.compactionState.phase !== "queued")
|
|
1112
1142
|
return;
|
|
1113
|
-
|
|
1143
|
+
const request = this.compactionState.request;
|
|
1144
|
+
if (!this.requestIsCurrent(request))
|
|
1114
1145
|
return;
|
|
1115
|
-
|
|
1146
|
+
this.compactionState = {
|
|
1147
|
+
phase: "queued",
|
|
1148
|
+
request: { ...request, resumeAfter: true }
|
|
1149
|
+
};
|
|
1150
|
+
ctx.abort();
|
|
1151
|
+
}
|
|
1152
|
+
queueThresholdCompaction(ctx, eligibleSettlement, resumeAfter) {
|
|
1153
|
+
const usage = ctx.getContextUsage();
|
|
1154
|
+
if (!this.piAutoCompactionEnabled || this.piCompactionReserveTokens <= 0 || usage?.tokens === null || usage?.tokens === undefined || usage.tokens < usage.contextWindow - this.piCompactionReserveTokens || this.compactionState.phase !== "idle" || !hasCompactableHistory(ctx)) {
|
|
1116
1155
|
return;
|
|
1117
1156
|
}
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1157
|
+
this.setCompactionState(ctx, {
|
|
1158
|
+
phase: "queued",
|
|
1159
|
+
request: {
|
|
1160
|
+
keep: null,
|
|
1161
|
+
origin: "threshold",
|
|
1162
|
+
sessionGeneration: this.sessionGeneration,
|
|
1163
|
+
sessionFile: this.activeSessionFile,
|
|
1164
|
+
eligibleSettlement,
|
|
1165
|
+
resumeAfter
|
|
1122
1166
|
}
|
|
1123
|
-
const runStillActive = this.settlementGeneration < request.eligibleSettlement;
|
|
1124
|
-
this.startQueuedCompaction(ctx, runStillActive ? { allowActiveRun: true, resumeAfter } : undefined);
|
|
1125
1167
|
});
|
|
1126
1168
|
}
|
|
1127
1169
|
requestIsCurrent(request) {
|
|
@@ -1143,7 +1185,7 @@ class CompactionRuntime {
|
|
|
1143
1185
|
requestHasCompactableHistory(ctx, keep) {
|
|
1144
1186
|
return keep === null ? hasCompactableHistory(ctx) : keepBoundaryId(ctx.sessionManager.getBranch(), keep) !== undefined;
|
|
1145
1187
|
}
|
|
1146
|
-
queueTurnCompaction(ctx, eligibleSettlement) {
|
|
1188
|
+
queueTurnCompaction(ctx, eligibleSettlement, resumeAfter = false) {
|
|
1147
1189
|
if (!this.turnCompactionEnabled() || !this.counterTriggered || this.compactionState.phase !== "idle") {
|
|
1148
1190
|
return;
|
|
1149
1191
|
}
|
|
@@ -1156,11 +1198,12 @@ class CompactionRuntime {
|
|
|
1156
1198
|
origin: "turn",
|
|
1157
1199
|
sessionGeneration: this.sessionGeneration,
|
|
1158
1200
|
sessionFile: this.activeSessionFile,
|
|
1159
|
-
eligibleSettlement
|
|
1201
|
+
eligibleSettlement,
|
|
1202
|
+
resumeAfter
|
|
1160
1203
|
}
|
|
1161
1204
|
});
|
|
1162
1205
|
}
|
|
1163
|
-
startQueuedCompaction(ctx
|
|
1206
|
+
startQueuedCompaction(ctx) {
|
|
1164
1207
|
if (this.compactionState.phase !== "queued")
|
|
1165
1208
|
return false;
|
|
1166
1209
|
const request = this.compactionState.request;
|
|
@@ -1172,7 +1215,7 @@ class CompactionRuntime {
|
|
|
1172
1215
|
}
|
|
1173
1216
|
if (request.origin !== "turn" && ctx.hasPendingMessages())
|
|
1174
1217
|
return false;
|
|
1175
|
-
if (!
|
|
1218
|
+
if (!ctx.isIdle())
|
|
1176
1219
|
return false;
|
|
1177
1220
|
if (!this.requestHasCompactableHistory(ctx, request.keep)) {
|
|
1178
1221
|
this.setCompactionState(ctx, { phase: "idle" });
|
|
@@ -1181,7 +1224,7 @@ class CompactionRuntime {
|
|
|
1181
1224
|
const operation = ++this.operationGeneration;
|
|
1182
1225
|
const generation = request.sessionGeneration;
|
|
1183
1226
|
const sessionFile = request.sessionFile;
|
|
1184
|
-
const resumeAfter = request.
|
|
1227
|
+
const resumeAfter = request.resumeAfter;
|
|
1185
1228
|
this.setCompactionState(ctx, {
|
|
1186
1229
|
phase: "running",
|
|
1187
1230
|
request,
|
|
@@ -1213,17 +1256,6 @@ class CompactionRuntime {
|
|
|
1213
1256
|
});
|
|
1214
1257
|
return true;
|
|
1215
1258
|
}
|
|
1216
|
-
deferQueuedCompaction(ctx) {
|
|
1217
|
-
if (this.compactionState.phase !== "queued")
|
|
1218
|
-
return;
|
|
1219
|
-
const request = this.compactionState.request;
|
|
1220
|
-
setImmediate(() => {
|
|
1221
|
-
if (this.compactionState.phase !== "queued" || this.compactionState.request !== request || !this.contextIsCurrent(ctx)) {
|
|
1222
|
-
return;
|
|
1223
|
-
}
|
|
1224
|
-
this.startQueuedCompaction(ctx);
|
|
1225
|
-
});
|
|
1226
|
-
}
|
|
1227
1259
|
}
|
|
1228
1260
|
|
|
1229
1261
|
// src/policy.ts
|
|
@@ -1377,7 +1409,12 @@ function renderToolRoutingPolicy(options = {}) {
|
|
|
1377
1409
|
}
|
|
1378
1410
|
|
|
1379
1411
|
// src/tools.ts
|
|
1380
|
-
import {
|
|
1412
|
+
import {
|
|
1413
|
+
DEFAULT_MAX_BYTES,
|
|
1414
|
+
DEFAULT_MAX_LINES,
|
|
1415
|
+
defineTool,
|
|
1416
|
+
truncateHead
|
|
1417
|
+
} from "@earendil-works/pi-coding-agent";
|
|
1381
1418
|
import { Type } from "@sinclair/typebox";
|
|
1382
1419
|
function historyProviderParam() {
|
|
1383
1420
|
return Type.Optional(Type.Union(GOOSE_PROVIDERS.map((provider) => Type.Literal(provider)), {
|
|
@@ -1412,6 +1449,17 @@ function goosedumpErrorResult(err) {
|
|
|
1412
1449
|
const message = err instanceof Error ? err.message : String(err);
|
|
1413
1450
|
return textResult(`goosedump error: ${message}`);
|
|
1414
1451
|
}
|
|
1452
|
+
function boundedHistoryResult(text) {
|
|
1453
|
+
const output = truncateHead(text, {
|
|
1454
|
+
maxBytes: DEFAULT_MAX_BYTES,
|
|
1455
|
+
maxLines: DEFAULT_MAX_LINES
|
|
1456
|
+
});
|
|
1457
|
+
if (!output.truncated)
|
|
1458
|
+
return textResult(text);
|
|
1459
|
+
return textResult(`${output.content}
|
|
1460
|
+
|
|
1461
|
+
[Output truncated: showing ${output.outputLines} of ${output.totalLines} lines ` + `and ${output.outputBytes} of ${output.totalBytes} bytes. ` + "Use goose_get with fewer IDs or a narrower goose_grep pattern.]");
|
|
1462
|
+
}
|
|
1415
1463
|
function resolveHistoryTarget(ctx, tool, params) {
|
|
1416
1464
|
const provider = params.provider ?? "pi";
|
|
1417
1465
|
const target = resolveTarget(ctx, provider, params.session);
|
|
@@ -1435,13 +1483,11 @@ function gooseToolMeta(name) {
|
|
|
1435
1483
|
};
|
|
1436
1484
|
}
|
|
1437
1485
|
var GOOSE_MEMORY_TYPES = ["decision", "fact", "preference", "procedure", "lesson"];
|
|
1438
|
-
function defineGooseTool(
|
|
1486
|
+
function defineGooseTool(name, parameters, handler) {
|
|
1439
1487
|
return defineTool({
|
|
1440
1488
|
...gooseToolMeta(name),
|
|
1441
1489
|
parameters,
|
|
1442
1490
|
async execute(_id, params, signal, _onUpdate, ctx) {
|
|
1443
|
-
if (!isAvailable())
|
|
1444
|
-
return goosedumpUnavailableResult();
|
|
1445
1491
|
try {
|
|
1446
1492
|
return await handler(params, signal, ctx);
|
|
1447
1493
|
} catch (err) {
|
|
@@ -1451,8 +1497,8 @@ function defineGooseTool(isAvailable, name, parameters, handler) {
|
|
|
1451
1497
|
});
|
|
1452
1498
|
}
|
|
1453
1499
|
function registerGooseTools(pi, deps) {
|
|
1454
|
-
const {
|
|
1455
|
-
pi.registerTool(defineGooseTool(
|
|
1500
|
+
const { nativeExecutablePath, enqueueRemember, requestCompaction } = deps;
|
|
1501
|
+
pi.registerTool(defineGooseTool("goose_remember", Type.Object({ ...historyTargetParams() }), async (params, _signal, ctx) => {
|
|
1456
1502
|
const resolved = resolveHistoryTarget(ctx, "goose_remember", params);
|
|
1457
1503
|
if (!resolved.ok)
|
|
1458
1504
|
return resolved.result;
|
|
@@ -1463,7 +1509,7 @@ function registerGooseTools(pi, deps) {
|
|
|
1463
1509
|
});
|
|
1464
1510
|
return textResult(`Memory learning queued for ${resolved.target}.`);
|
|
1465
1511
|
}));
|
|
1466
|
-
pi.registerTool(defineGooseTool(
|
|
1512
|
+
pi.registerTool(defineGooseTool("goose_recall", Type.Object({
|
|
1467
1513
|
query: Type.String({ description: PARAM_DESCRIPTIONS.memoryQuery }),
|
|
1468
1514
|
type: Type.Optional(Type.Union(GOOSE_MEMORY_TYPES.map((type) => Type.Literal(type)), { description: PARAM_DESCRIPTIONS.memoryType })),
|
|
1469
1515
|
allProjects: Type.Optional(Type.Boolean({
|
|
@@ -1493,7 +1539,7 @@ function registerGooseTools(pi, deps) {
|
|
|
1493
1539
|
return textResult(`No durable memory for "${params.query}"`);
|
|
1494
1540
|
return textResult(formatMemoryHits(hits));
|
|
1495
1541
|
}));
|
|
1496
|
-
pi.registerTool(defineGooseTool(
|
|
1542
|
+
pi.registerTool(defineGooseTool("goose_forget", Type.Object({
|
|
1497
1543
|
target: Type.String({
|
|
1498
1544
|
description: PARAM_DESCRIPTIONS.forgetTarget
|
|
1499
1545
|
})
|
|
@@ -1504,8 +1550,8 @@ function registerGooseTools(pi, deps) {
|
|
|
1504
1550
|
const res = await goosedumpMemoryForget(params.target.trim(), signal, nativeExecutablePath);
|
|
1505
1551
|
return textResult(res);
|
|
1506
1552
|
}));
|
|
1507
|
-
pi.registerTool(defineGooseTool(
|
|
1508
|
-
pi.registerTool(defineGooseTool(
|
|
1553
|
+
pi.registerTool(defineGooseTool("goose_memory_status", Type.Object({}), async (_params, signal) => textResult(await goosedumpMemoryStatus(signal, nativeExecutablePath))));
|
|
1554
|
+
pi.registerTool(defineGooseTool("goose_search", Type.Object({
|
|
1509
1555
|
query: Type.String({ description: PARAM_DESCRIPTIONS.searchQuery }),
|
|
1510
1556
|
...historyBranchParams(),
|
|
1511
1557
|
page: Type.Optional(Type.Integer({
|
|
@@ -1527,7 +1573,7 @@ function registerGooseTools(pi, deps) {
|
|
|
1527
1573
|
|
|
1528
1574
|
${text}`);
|
|
1529
1575
|
}));
|
|
1530
|
-
pi.registerTool(defineGooseTool(
|
|
1576
|
+
pi.registerTool(defineGooseTool("goose_get", Type.Object({
|
|
1531
1577
|
ids: Type.Array(Type.String(), {
|
|
1532
1578
|
minItems: 1,
|
|
1533
1579
|
description: PARAM_DESCRIPTIONS.entryIds
|
|
@@ -1541,9 +1587,9 @@ ${text}`);
|
|
|
1541
1587
|
if (messages.length === 0) {
|
|
1542
1588
|
return textResult("No entries found for the given IDs.");
|
|
1543
1589
|
}
|
|
1544
|
-
return
|
|
1590
|
+
return boundedHistoryResult(formatMessagesFull(messages));
|
|
1545
1591
|
}));
|
|
1546
|
-
pi.registerTool(defineGooseTool(
|
|
1592
|
+
pi.registerTool(defineGooseTool("goose_grep", Type.Object({
|
|
1547
1593
|
pattern: Type.String({ description: PARAM_DESCRIPTIONS.pattern }),
|
|
1548
1594
|
...historyBranchParams()
|
|
1549
1595
|
}), async (params, signal, ctx) => {
|
|
@@ -1554,9 +1600,9 @@ ${text}`);
|
|
|
1554
1600
|
if (messages.length === 0) {
|
|
1555
1601
|
return textResult(`No matches for "${params.pattern}"`);
|
|
1556
1602
|
}
|
|
1557
|
-
return
|
|
1603
|
+
return boundedHistoryResult(formatMessagesCompact(messages));
|
|
1558
1604
|
}));
|
|
1559
|
-
pi.registerTool(defineGooseTool(
|
|
1605
|
+
pi.registerTool(defineGooseTool("goose_compact", Type.Object({
|
|
1560
1606
|
keep: Type.Optional(Type.Integer({
|
|
1561
1607
|
minimum: 1,
|
|
1562
1608
|
description: PARAM_DESCRIPTIONS.keep
|
|
@@ -1571,7 +1617,7 @@ ${text}`);
|
|
|
1571
1617
|
}
|
|
1572
1618
|
return textResult(compactStartedMessage(keep, result === "queued"));
|
|
1573
1619
|
}));
|
|
1574
|
-
pi.registerTool(defineGooseTool(
|
|
1620
|
+
pi.registerTool(defineGooseTool("goose_sessions", Type.Object({
|
|
1575
1621
|
action: Type.Optional(Type.Union([Type.Literal("list"), Type.Literal("delete")], {
|
|
1576
1622
|
default: "list",
|
|
1577
1623
|
description: PARAM_DESCRIPTIONS.sessionsAction
|
|
@@ -1584,19 +1630,22 @@ ${text}`);
|
|
|
1584
1630
|
const action = params.action ?? "list";
|
|
1585
1631
|
const provider = params.provider ?? "pi";
|
|
1586
1632
|
const currentTarget = currentSessionTarget(ctx);
|
|
1633
|
+
const sessions = await goosedumpListSessions(signal, nativeExecutablePath);
|
|
1634
|
+
const cwdSessions = sessions.filter((s) => s.cwd === ctx.cwd && s.provider === provider);
|
|
1587
1635
|
if (action === "delete") {
|
|
1588
1636
|
if (!params.session) {
|
|
1589
1637
|
return textResult("goose_sessions error: session ID is required for action: delete");
|
|
1590
1638
|
}
|
|
1591
1639
|
const target = formatTarget(provider, params.session);
|
|
1640
|
+
if (!cwdSessions.some((session) => session.id === params.session)) {
|
|
1641
|
+
return textResult(`Cannot delete session ${target}: not found for ${ctx.cwd}.`);
|
|
1642
|
+
}
|
|
1592
1643
|
if (currentTarget && target === currentTarget) {
|
|
1593
1644
|
return textResult("Cannot delete the active session.");
|
|
1594
1645
|
}
|
|
1595
1646
|
await goosedumpRemoveSession(target, signal, nativeExecutablePath);
|
|
1596
1647
|
return textResult(`Deleted session ${target}`);
|
|
1597
1648
|
}
|
|
1598
|
-
const sessions = await goosedumpListSessions(signal, nativeExecutablePath);
|
|
1599
|
-
const cwdSessions = sessions.filter((s) => s.cwd === ctx.cwd && s.provider === provider);
|
|
1600
1649
|
if (cwdSessions.length === 0) {
|
|
1601
1650
|
return textResult(`No ${provider} sessions found for ${ctx.cwd}`);
|
|
1602
1651
|
}
|
|
@@ -1900,7 +1949,7 @@ async function runSettingsEditor(ctx) {
|
|
|
1900
1949
|
}
|
|
1901
1950
|
});
|
|
1902
1951
|
}
|
|
1903
|
-
async function runSearchPanel(ctx, query) {
|
|
1952
|
+
async function runSearchPanel(ctx, query, executablePath) {
|
|
1904
1953
|
const OVERLAY_WIDTH = 84;
|
|
1905
1954
|
const VIEWPORT = 18;
|
|
1906
1955
|
const PAGE_SIZE = 5;
|
|
@@ -1917,7 +1966,7 @@ async function runSearchPanel(ctx, query) {
|
|
|
1917
1966
|
let detailScroll = 0;
|
|
1918
1967
|
async function runSearch() {
|
|
1919
1968
|
try {
|
|
1920
|
-
const first = await goosedumpSearch(target, query, { scope: "lineage", page: 1 });
|
|
1969
|
+
const first = await goosedumpSearch(target, query, { scope: "lineage", page: 1 }, undefined, executablePath);
|
|
1921
1970
|
if (first.length === 0) {
|
|
1922
1971
|
entries = [];
|
|
1923
1972
|
error = `No results for "${query}"`;
|
|
@@ -1927,7 +1976,7 @@ async function runSearchPanel(ctx, query) {
|
|
|
1927
1976
|
}
|
|
1928
1977
|
const all = [...first];
|
|
1929
1978
|
for (let page = 2;page <= 10; page += 1) {
|
|
1930
|
-
const result = await goosedumpSearch(target, query, { scope: "lineage", page });
|
|
1979
|
+
const result = await goosedumpSearch(target, query, { scope: "lineage", page }, undefined, executablePath);
|
|
1931
1980
|
if (result.length === 0)
|
|
1932
1981
|
break;
|
|
1933
1982
|
all.push(...result);
|
|
@@ -1941,6 +1990,8 @@ async function runSearchPanel(ctx, query) {
|
|
|
1941
1990
|
} catch (err) {
|
|
1942
1991
|
entries = [];
|
|
1943
1992
|
error = err instanceof Error ? err.message : String(err);
|
|
1993
|
+
} finally {
|
|
1994
|
+
tui.requestRender();
|
|
1944
1995
|
}
|
|
1945
1996
|
}
|
|
1946
1997
|
if (!target) {
|
|
@@ -1950,7 +2001,7 @@ async function runSearchPanel(ctx, query) {
|
|
|
1950
2001
|
}
|
|
1951
2002
|
async function loadDetail(id) {
|
|
1952
2003
|
try {
|
|
1953
|
-
const messages = await goosedumpShow(target, { ids: [id] });
|
|
2004
|
+
const messages = await goosedumpShow(target, { ids: [id] }, undefined, executablePath);
|
|
1954
2005
|
const content = messages.length > 0 ? messages[0].content : "No content for this entry.";
|
|
1955
2006
|
detailLines = wrapTextWithAnsi(content, wrapWidth);
|
|
1956
2007
|
detailScroll = 0;
|
|
@@ -1958,6 +2009,8 @@ async function runSearchPanel(ctx, query) {
|
|
|
1958
2009
|
} catch (err) {
|
|
1959
2010
|
const message = err instanceof Error ? err.message : String(err);
|
|
1960
2011
|
ctx.ui.notify(`goosedump error: ${message}`, "error");
|
|
2012
|
+
} finally {
|
|
2013
|
+
tui.requestRender();
|
|
1961
2014
|
}
|
|
1962
2015
|
}
|
|
1963
2016
|
return {
|
|
@@ -2053,7 +2106,6 @@ async function runSearchPanel(ctx, query) {
|
|
|
2053
2106
|
if (!entry)
|
|
2054
2107
|
return;
|
|
2055
2108
|
loadDetail(entry.id);
|
|
2056
|
-
tui.requestRender();
|
|
2057
2109
|
return;
|
|
2058
2110
|
}
|
|
2059
2111
|
},
|
|
@@ -2068,6 +2120,21 @@ async function runSearchPanel(ctx, query) {
|
|
|
2068
2120
|
}
|
|
2069
2121
|
});
|
|
2070
2122
|
}
|
|
2123
|
+
function countSessionDescendants(childrenMap, target) {
|
|
2124
|
+
const seen = new Set([target]);
|
|
2125
|
+
const pending = [...childrenMap.get(target) ?? []];
|
|
2126
|
+
while (pending.length > 0) {
|
|
2127
|
+
const session = pending.pop();
|
|
2128
|
+
if (!session)
|
|
2129
|
+
continue;
|
|
2130
|
+
const key = `${session.provider}:${session.id}`;
|
|
2131
|
+
if (seen.has(key))
|
|
2132
|
+
continue;
|
|
2133
|
+
seen.add(key);
|
|
2134
|
+
pending.push(...childrenMap.get(key) ?? []);
|
|
2135
|
+
}
|
|
2136
|
+
return seen.size - 1;
|
|
2137
|
+
}
|
|
2071
2138
|
async function runSessionsPanel(ctx, executablePath) {
|
|
2072
2139
|
const OVERLAY_WIDTH = 84;
|
|
2073
2140
|
const VIEWPORT = 18;
|
|
@@ -2116,6 +2183,7 @@ async function runSessionsPanel(ctx, executablePath) {
|
|
|
2116
2183
|
let error = null;
|
|
2117
2184
|
let selected = 0;
|
|
2118
2185
|
let scroll = 0;
|
|
2186
|
+
let deletionPending = false;
|
|
2119
2187
|
function rebuildDisplayList() {
|
|
2120
2188
|
displayList = flattenTree(roots, childrenMap, expandedNodes, 0);
|
|
2121
2189
|
}
|
|
@@ -2132,7 +2200,7 @@ async function runSessionsPanel(ctx, executablePath) {
|
|
|
2132
2200
|
sessions = cwdSessions;
|
|
2133
2201
|
childrenMap = buildChildrenMap(sessions);
|
|
2134
2202
|
const sessionKeys = new Set(sessions.map((s) => `${s.provider}:${s.id}`));
|
|
2135
|
-
roots = sessions.filter((s) => s.parent === null);
|
|
2203
|
+
roots = sessions.filter((s) => s.parent === null || !sessionKeys.has(`${s.provider}:${s.parent}`));
|
|
2136
2204
|
expandedNodes = new Set([...expandedNodes].filter((key) => sessionKeys.has(key)));
|
|
2137
2205
|
rebuildDisplayList();
|
|
2138
2206
|
if (sessions.length === 0) {
|
|
@@ -2154,6 +2222,8 @@ async function runSessionsPanel(ctx, executablePath) {
|
|
|
2154
2222
|
}
|
|
2155
2223
|
loadSessions();
|
|
2156
2224
|
async function deleteSelected() {
|
|
2225
|
+
if (deletionPending)
|
|
2226
|
+
return;
|
|
2157
2227
|
const row = displayList[selected];
|
|
2158
2228
|
if (!row)
|
|
2159
2229
|
return;
|
|
@@ -2163,13 +2233,21 @@ async function runSessionsPanel(ctx, executablePath) {
|
|
|
2163
2233
|
ctx.ui.notify("Cannot delete the active session.", "error");
|
|
2164
2234
|
return;
|
|
2165
2235
|
}
|
|
2236
|
+
deletionPending = true;
|
|
2166
2237
|
try {
|
|
2238
|
+
const descendants = countSessionDescendants(childrenMap, target);
|
|
2239
|
+
const confirmed = await ctx.ui.confirm("Permanently delete session?", `Permanently remove ${target} and all descendants (${descendants} shown)? This cannot be undone.`);
|
|
2240
|
+
tui.requestRender();
|
|
2241
|
+
if (!confirmed)
|
|
2242
|
+
return;
|
|
2167
2243
|
await goosedumpRemoveSession(target, undefined, executablePath);
|
|
2168
2244
|
ctx.ui.notify(`Deleted session ${s.id}`, "info");
|
|
2169
2245
|
await loadSessions();
|
|
2170
2246
|
} catch (err) {
|
|
2171
2247
|
const message = err instanceof Error ? err.message : String(err);
|
|
2172
2248
|
ctx.ui.notify(`Failed to delete session: ${message}`, "error");
|
|
2249
|
+
} finally {
|
|
2250
|
+
deletionPending = false;
|
|
2173
2251
|
}
|
|
2174
2252
|
}
|
|
2175
2253
|
return {
|
|
@@ -2294,8 +2372,7 @@ async function runSessionsPanel(ctx, executablePath) {
|
|
|
2294
2372
|
function createGoosedumpIntegration(options = {}) {
|
|
2295
2373
|
const shouldRegisterTool = options.registerTool ?? true;
|
|
2296
2374
|
const shouldOverrideDefaultCompaction = options.overrideDefaultCompaction ?? true;
|
|
2297
|
-
const nativeExecutablePath = options.nativeExecutablePath;
|
|
2298
|
-
let goosedumpAvailable = false;
|
|
2375
|
+
const nativeExecutablePath = options.nativeExecutablePath ?? resolveNativeGoosedumpBinary();
|
|
2299
2376
|
const rememberQueue = [];
|
|
2300
2377
|
let rememberRunning = null;
|
|
2301
2378
|
function enqueueRemember(target, reportError) {
|
|
@@ -2331,32 +2408,22 @@ function createGoosedumpIntegration(options = {}) {
|
|
|
2331
2408
|
while (rememberRunning)
|
|
2332
2409
|
await rememberRunning;
|
|
2333
2410
|
}
|
|
2334
|
-
function checkGoosedump() {
|
|
2335
|
-
try {
|
|
2336
|
-
execFileSync(process.execPath, [resolveGoosedumpBinary(), "--version"], { stdio: "ignore" });
|
|
2337
|
-
return true;
|
|
2338
|
-
} catch {
|
|
2339
|
-
return false;
|
|
2340
|
-
}
|
|
2341
|
-
}
|
|
2342
2411
|
const compaction = new CompactionRuntime({
|
|
2343
2412
|
overrideDefaultCompaction: shouldOverrideDefaultCompaction,
|
|
2344
2413
|
nativeExecutablePath,
|
|
2345
|
-
isAvailable: () => goosedumpAvailable,
|
|
2346
2414
|
enqueueRemember
|
|
2347
2415
|
});
|
|
2348
2416
|
function register(pi) {
|
|
2349
2417
|
compaction.setActivePi(pi);
|
|
2350
2418
|
if (shouldRegisterTool) {
|
|
2351
2419
|
registerGooseTools(pi, {
|
|
2352
|
-
isAvailable: () => goosedumpAvailable,
|
|
2353
2420
|
nativeExecutablePath,
|
|
2354
2421
|
enqueueRemember,
|
|
2355
2422
|
requestCompaction: (ctx, keep) => compaction.requestCompaction(ctx, keep)
|
|
2356
2423
|
});
|
|
2357
2424
|
}
|
|
2358
2425
|
pi.on("before_agent_start", (event) => {
|
|
2359
|
-
if (!shouldRegisterTool
|
|
2426
|
+
if (!shouldRegisterTool)
|
|
2360
2427
|
return;
|
|
2361
2428
|
return {
|
|
2362
2429
|
systemPrompt: `${event.systemPrompt}
|
|
@@ -2365,10 +2432,6 @@ ${renderToolRoutingPolicy()}`
|
|
|
2365
2432
|
};
|
|
2366
2433
|
});
|
|
2367
2434
|
pi.on("session_start", async (_event, ctx) => {
|
|
2368
|
-
goosedumpAvailable = checkGoosedump();
|
|
2369
|
-
if (!goosedumpAvailable) {
|
|
2370
|
-
ctx.ui.notify("goosedump was not found. Install with: npm install @jarkkojs/goosedump", "error");
|
|
2371
|
-
}
|
|
2372
2435
|
notifyInvalidSettings(ctx);
|
|
2373
2436
|
compaction.beginSession(ctx);
|
|
2374
2437
|
});
|
|
@@ -2398,10 +2461,6 @@ ${renderToolRoutingPolicy()}`
|
|
|
2398
2461
|
maybePi.registerCommand?.(name, {
|
|
2399
2462
|
description,
|
|
2400
2463
|
handler: async (args, ctx) => {
|
|
2401
|
-
if (!goosedumpAvailable) {
|
|
2402
|
-
ctx.ui.notify(GOOSEDUMP_UNAVAILABLE_MESSAGE, "error");
|
|
2403
|
-
return;
|
|
2404
|
-
}
|
|
2405
2464
|
try {
|
|
2406
2465
|
await handler(args, ctx);
|
|
2407
2466
|
} catch (err) {
|
|
@@ -2443,7 +2502,7 @@ ${renderToolRoutingPolicy()}`
|
|
|
2443
2502
|
const query = args.trim() || (await ctx.ui.input("Search query:", ""))?.trim() || "";
|
|
2444
2503
|
if (query.length === 0)
|
|
2445
2504
|
return;
|
|
2446
|
-
await runSearchPanel(ctx, query);
|
|
2505
|
+
await runSearchPanel(ctx, query, nativeExecutablePath);
|
|
2447
2506
|
});
|
|
2448
2507
|
registerGooseCommand("goose-sessions", "List, resume, or delete sessions for the current working directory", async (_args, ctx) => {
|
|
2449
2508
|
await runSessionsPanel(ctx, nativeExecutablePath);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goosedump",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.41",
|
|
4
4
|
"description": "Pi extension for goosedump session search, durable memory, session management, 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.41",
|
|
42
42
|
"@sinclair/typebox": "^0.34.49"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|