pi-goosedump 0.12.55 → 0.12.57
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 +21 -89
- package/dist/index.ts +172 -82
- package/package.json +14 -6
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# pi-goosedump
|
|
2
2
|
|
|
3
|
-
Pi extension for
|
|
4
|
-
|
|
3
|
+
Pi extension for session search, durable memory, session management, and
|
|
4
|
+
compaction. Tools use the `goose_` prefix.
|
|
5
5
|
|
|
6
6
|
## Install
|
|
7
7
|
|
|
@@ -9,66 +9,30 @@ management, and compaction. Tools use the `goose_` prefix.
|
|
|
9
9
|
pi install npm:pi-goosedump
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
that packaged binary by default and does not fall back to a `goosedump`
|
|
16
|
-
executable on `PATH`. Set the shared `GOOSEDUMP_BINARY` environment variable to
|
|
17
|
-
an absolute executable path to override it. An invalid override or missing
|
|
18
|
-
packaged binary is treated as a broken installation. Local models download on
|
|
19
|
-
first use.
|
|
12
|
+
The extension includes binaries for Linux x64/arm64, macOS arm64, and Windows
|
|
13
|
+
x64. Set `GOOSEDUMP_BINARY` to an absolute executable path to override the
|
|
14
|
+
packaged binary.
|
|
20
15
|
|
|
21
16
|
## Tools
|
|
22
17
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
| `goose_grep` | Exact glob/substring filter over session messages |
|
|
30
|
-
| `goose_get` | Expand entry IDs from search/grep to full content |
|
|
31
|
-
| `goose_remember` | Queue background learning from a session after a milestone |
|
|
32
|
-
| `goose_recall` | Recall project-scoped durable memory with provenance |
|
|
33
|
-
| `goose_forget` | Remove a stale memory or a session's retained sources |
|
|
34
|
-
| `goose_memory_status` | Inspect storage health, type counts, and tombstones |
|
|
35
|
-
| `goose_compact` | Compact the current Pi session asynchronously |
|
|
36
|
-
| `goose_sessions` | List or delete sessions in the current working directory |
|
|
37
|
-
|
|
38
|
-
Defaults:
|
|
39
|
-
|
|
40
|
-
| Setting | Default |
|
|
41
|
-
| ---------------- | ------------------------------ |
|
|
42
|
-
| Session target | Current Pi session |
|
|
43
|
-
| History scope | `lineage` (active branch) |
|
|
44
|
-
| Recall limit | 5 |
|
|
45
|
-
| Non-Pi providers | Require an explicit session ID |
|
|
46
|
-
|
|
47
|
-
Use each tool's schema for full options. Typical flow:
|
|
48
|
-
|
|
49
|
-
1. `goose_search` or `goose_grep` for candidate entry IDs
|
|
50
|
-
2. `goose_get` on the interesting IDs
|
|
51
|
-
3. `goose_recall` for durable facts, decisions, preferences, procedures, lessons
|
|
52
|
-
4. `goose_remember` after milestones worth retaining
|
|
18
|
+
- `goose_search` and `goose_grep` find session entries.
|
|
19
|
+
- `goose_get` expands entry IDs to full content.
|
|
20
|
+
- `goose_remember`, `goose_recall`, and `goose_forget` manage durable memory.
|
|
21
|
+
- `goose_memory_status` reports memory storage health.
|
|
22
|
+
- `goose_compact` compacts the current Pi session.
|
|
23
|
+
- `goose_sessions` lists or deletes sessions for the current project.
|
|
53
24
|
|
|
54
|
-
|
|
25
|
+
Search and grep return entry IDs; call `goose_get` before relying on their
|
|
26
|
+
content. Non-Pi providers require an explicit session ID.
|
|
55
27
|
|
|
56
|
-
|
|
57
|
-
| --------------------------------- | ------------------------------------------------ |
|
|
58
|
-
| `/goose-search [query]` | Search the current session |
|
|
59
|
-
| `/goose-compact [keep:N]` | Compact now; keep the last N user turns verbatim |
|
|
60
|
-
| `/goose-sessions` | List, resume, or delete project sessions |
|
|
61
|
-
| `/goose-forget [id\|provider:id]` | Forget stored memory |
|
|
62
|
-
| `/goose-memory-status` | Show memory storage and indexing status |
|
|
63
|
-
| `/goose-settings` | Edit goosedump settings |
|
|
64
|
-
| `/goosedump-settings` | Edit goosedump settings (alias) |
|
|
28
|
+
## Commands
|
|
65
29
|
|
|
66
|
-
|
|
30
|
+
`/goose-search`, `/goose-compact`, `/goose-sessions`, `/goose-forget`,
|
|
31
|
+
`/goose-memory-status`, and `/goose-settings`.
|
|
67
32
|
|
|
68
33
|
## Settings
|
|
69
34
|
|
|
70
|
-
|
|
71
|
-
- Project: `.pi/settings.json`
|
|
35
|
+
Add `goosedump` to `~/.pi/agent/settings.json` or `.pi/settings.json`:
|
|
72
36
|
|
|
73
37
|
```json
|
|
74
38
|
{
|
|
@@ -80,49 +44,17 @@ Resume is available only for Pi sessions.
|
|
|
80
44
|
}
|
|
81
45
|
```
|
|
82
46
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
| `turnsBeforeCompact` | `0`–`255` | 0 | `0` disables; each completed model response counts as one turn |
|
|
86
|
-
| `summaryMaxTokens` | `512`–`65536` | 4096 | Capped to 80% of `compaction.reserveTokens` when lower |
|
|
87
|
-
| `enableMemory` | `true`/`false` | false | Controls whether durable memory tools/commands/learning are on |
|
|
88
|
-
|
|
89
|
-
The footer shows the remaining turn count in hexadecimal.
|
|
90
|
-
|
|
91
|
-
### Compaction behavior
|
|
92
|
-
|
|
93
|
-
- Pi's `compaction.enabled` controls automatic compaction.
|
|
94
|
-
- The first enabled context or turn limit starts one compaction.
|
|
95
|
-
- Successful compaction resets the turn counter.
|
|
96
|
-
- Manual and automatic compaction preserve the previous summary and summarize
|
|
97
|
-
only new entries.
|
|
98
|
-
- `keep:N` leaves the last N user turns verbatim.
|
|
99
|
-
- Manual requests wait until Pi is idle.
|
|
100
|
-
- Memory learning runs in the background after a successful compaction.
|
|
101
|
-
- If the turn limit expires during a tool loop, Pi saves the current batch,
|
|
102
|
-
compacts at that turn boundary, and resumes automatically.
|
|
103
|
-
- Goosedump failures fall back to Pi's compactor.
|
|
47
|
+
`turnsBeforeCompact: 0` disables turn-based compaction. Memory learning runs in
|
|
48
|
+
the background after successful compaction when memory is enabled.
|
|
104
49
|
|
|
105
50
|
## Development
|
|
106
51
|
|
|
107
|
-
Install dependencies and run the test suite:
|
|
108
|
-
|
|
109
52
|
```sh
|
|
110
53
|
npm install
|
|
111
54
|
npm test
|
|
112
55
|
npm run check
|
|
113
56
|
```
|
|
114
57
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
```sh
|
|
118
|
-
cargo build
|
|
119
|
-
GOOSEDUMP_BINARY="$(cd ../.. && pwd)/target/debug/goosedump" \
|
|
120
|
-
pi --no-extensions -e ./index.ts
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
The explicit override takes precedence over the packaged platform binary. It must
|
|
124
|
-
be an absolute path to an executable regular file.
|
|
125
|
-
|
|
126
|
-
## License
|
|
58
|
+
## Licensing
|
|
127
59
|
|
|
128
|
-
Apache
|
|
60
|
+
[Apache 2.0](LICENSE).
|
package/dist/index.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// Copyright (C) Jarkko Sakkinen 2026
|
|
3
3
|
|
|
4
|
+
// src/integration.ts
|
|
5
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
6
|
+
|
|
4
7
|
// src/cli.ts
|
|
5
|
-
import { Type as Type2 } from "
|
|
6
|
-
import { Value as Value2 } from "
|
|
8
|
+
import { Type as Type2 } from "typebox";
|
|
9
|
+
import { Value as Value2 } from "typebox/value";
|
|
7
10
|
import { execFile } from "node:child_process";
|
|
8
11
|
import { accessSync, constants as fsConstants, existsSync, readFileSync as readFileSync2, statSync } from "node:fs";
|
|
9
12
|
import { createRequire } from "node:module";
|
|
@@ -11,10 +14,10 @@ import { dirname, isAbsolute, join } from "node:path";
|
|
|
11
14
|
import { fileURLToPath } from "node:url";
|
|
12
15
|
|
|
13
16
|
// src/format.ts
|
|
14
|
-
import { Value } from "
|
|
17
|
+
import { Value } from "typebox/value";
|
|
15
18
|
|
|
16
19
|
// src/types.ts
|
|
17
|
-
import { Type } from "
|
|
20
|
+
import { Type } from "typebox";
|
|
18
21
|
var BRANCH_SUMMARY_PREFIX = `The following is a summary of a branch that this conversation came back from:
|
|
19
22
|
|
|
20
23
|
<summary>
|
|
@@ -136,6 +139,11 @@ var DEFAULT_GOOSEDUMP_SETTINGS = {
|
|
|
136
139
|
summaryMaxTokens: 4096,
|
|
137
140
|
enableMemory: false
|
|
138
141
|
};
|
|
142
|
+
var GOOSEDUMP_SETTING_LABELS = {
|
|
143
|
+
turnsBeforeCompact: "Turns before compact",
|
|
144
|
+
summaryMaxTokens: "Summary max tokens",
|
|
145
|
+
enableMemory: "Enable memory"
|
|
146
|
+
};
|
|
139
147
|
var GOOSEDUMP_SESSION_SCHEMA = Type.Object({
|
|
140
148
|
id: Type.String(),
|
|
141
149
|
parent: Type.Union([Type.String(), Type.Null()]),
|
|
@@ -529,8 +537,11 @@ async function goosedumpRecall(query, options = {}, signal, executablePath) {
|
|
|
529
537
|
const output = await runGoosedumpAsync(args, signal, executablePath);
|
|
530
538
|
return { hits: parseMemoryHits(output.stdout), warning: output.stderr.trim() };
|
|
531
539
|
}
|
|
532
|
-
async function goosedumpMemoryRemember(target, signal, executablePath) {
|
|
533
|
-
const
|
|
540
|
+
async function goosedumpMemoryRemember(target, signal, executablePath, leaseOwner) {
|
|
541
|
+
const args = ["memory", "learn", target, "--json"];
|
|
542
|
+
if (leaseOwner)
|
|
543
|
+
args.push("--lease-owner", leaseOwner);
|
|
544
|
+
const output = await runGoosedumpAsync(args, signal, executablePath);
|
|
534
545
|
return output.stdout.trim();
|
|
535
546
|
}
|
|
536
547
|
async function goosedumpMemoryForget(target, signal, executablePath) {
|
|
@@ -620,7 +631,7 @@ import { findCutPoint } from "@earendil-works/pi-coding-agent";
|
|
|
620
631
|
|
|
621
632
|
// src/settings.ts
|
|
622
633
|
import { DEFAULT_COMPACTION_SETTINGS } from "@earendil-works/pi-coding-agent";
|
|
623
|
-
import { Value as Value3 } from "
|
|
634
|
+
import { Value as Value3 } from "typebox/value";
|
|
624
635
|
import { randomUUID } from "node:crypto";
|
|
625
636
|
import {
|
|
626
637
|
existsSync as existsSync2,
|
|
@@ -633,9 +644,9 @@ import {
|
|
|
633
644
|
import { homedir } from "node:os";
|
|
634
645
|
import { dirname as dirname2, join as join2 } from "node:path";
|
|
635
646
|
function settingSpecFromSchema(schema) {
|
|
636
|
-
|
|
647
|
+
const { type, minimum, maximum } = schema;
|
|
648
|
+
if (type === "boolean")
|
|
637
649
|
return { kind: "boolean" };
|
|
638
|
-
const { minimum, maximum } = schema;
|
|
639
650
|
if (typeof minimum !== "number" || typeof maximum !== "number") {
|
|
640
651
|
throw new Error("goosedump setting schema is missing integer bounds");
|
|
641
652
|
}
|
|
@@ -996,6 +1007,7 @@ class CompactionRuntime {
|
|
|
996
1007
|
overrideDefaultCompaction;
|
|
997
1008
|
nativeExecutablePath;
|
|
998
1009
|
enqueueRemember;
|
|
1010
|
+
runCompactInference;
|
|
999
1011
|
compactSettings = DEFAULT_GOOSEDUMP_SETTINGS;
|
|
1000
1012
|
piAutoCompactionEnabled = DEFAULT_COMPACTION_SETTINGS2.enabled;
|
|
1001
1013
|
piCompactionReserveTokens = DEFAULT_COMPACTION_SETTINGS2.reserveTokens;
|
|
@@ -1014,6 +1026,7 @@ class CompactionRuntime {
|
|
|
1014
1026
|
this.overrideDefaultCompaction = deps.overrideDefaultCompaction;
|
|
1015
1027
|
this.nativeExecutablePath = deps.nativeExecutablePath;
|
|
1016
1028
|
this.enqueueRemember = deps.enqueueRemember;
|
|
1029
|
+
this.runCompactInference = deps.runCompactInference;
|
|
1017
1030
|
}
|
|
1018
1031
|
setActivePi(pi) {
|
|
1019
1032
|
this.activePi = pi;
|
|
@@ -1224,7 +1237,7 @@ class CompactionRuntime {
|
|
|
1224
1237
|
});
|
|
1225
1238
|
}
|
|
1226
1239
|
}
|
|
1227
|
-
const compaction = await buildGoosedumpCompaction(target, event.preparation, event.branchEntries, effectiveSummaryMaxTokens(ctx.cwd, projectSettingsTrusted(ctx)), keep, signal, this.nativeExecutablePath);
|
|
1240
|
+
const compaction = await this.runCompactInference(() => buildGoosedumpCompaction(target, event.preparation, event.branchEntries, effectiveSummaryMaxTokens(ctx.cwd, projectSettingsTrusted(ctx)), keep, signal, this.nativeExecutablePath));
|
|
1228
1241
|
if (!this.runningOperationIsCurrent(operation, generation, sessionFile)) {
|
|
1229
1242
|
return { cancel: true };
|
|
1230
1243
|
}
|
|
@@ -1461,36 +1474,124 @@ class CompactionRuntime {
|
|
|
1461
1474
|
}
|
|
1462
1475
|
}
|
|
1463
1476
|
|
|
1477
|
+
// src/inference-coordinator.ts
|
|
1478
|
+
class InferenceBusyError extends Error {
|
|
1479
|
+
constructor(message = "another inference task is already active") {
|
|
1480
|
+
super(message);
|
|
1481
|
+
this.name = "InferenceBusyError";
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
class InferenceCoordinator {
|
|
1486
|
+
activeAttempt = null;
|
|
1487
|
+
compactActive = false;
|
|
1488
|
+
idleWaiters = [];
|
|
1489
|
+
tryRunNonCompact(operation, externalSignal) {
|
|
1490
|
+
if (this.compactActive || this.activeAttempt)
|
|
1491
|
+
return null;
|
|
1492
|
+
let resolvePromise = () => {};
|
|
1493
|
+
let rejectPromise = () => {};
|
|
1494
|
+
const promise = new Promise((resolve, reject) => {
|
|
1495
|
+
resolvePromise = resolve;
|
|
1496
|
+
rejectPromise = reject;
|
|
1497
|
+
});
|
|
1498
|
+
const job = {
|
|
1499
|
+
operation,
|
|
1500
|
+
externalSignal,
|
|
1501
|
+
settled: false,
|
|
1502
|
+
resolve: (value) => {
|
|
1503
|
+
if (job.settled)
|
|
1504
|
+
return;
|
|
1505
|
+
job.settled = true;
|
|
1506
|
+
resolvePromise(value);
|
|
1507
|
+
},
|
|
1508
|
+
reject: (error) => {
|
|
1509
|
+
if (job.settled)
|
|
1510
|
+
return;
|
|
1511
|
+
job.settled = true;
|
|
1512
|
+
rejectPromise(error);
|
|
1513
|
+
}
|
|
1514
|
+
};
|
|
1515
|
+
this.startNonCompact(job);
|
|
1516
|
+
return promise;
|
|
1517
|
+
}
|
|
1518
|
+
async runCompact(operation) {
|
|
1519
|
+
if (this.compactActive)
|
|
1520
|
+
throw new InferenceBusyError("compaction inference is already active");
|
|
1521
|
+
this.compactActive = true;
|
|
1522
|
+
const interrupted = this.activeAttempt;
|
|
1523
|
+
if (interrupted) {
|
|
1524
|
+
interrupted.preempted = true;
|
|
1525
|
+
interrupted.controller.abort(new Error("inference paused for compaction"));
|
|
1526
|
+
await interrupted.completion;
|
|
1527
|
+
}
|
|
1528
|
+
try {
|
|
1529
|
+
return await operation();
|
|
1530
|
+
} finally {
|
|
1531
|
+
this.compactActive = false;
|
|
1532
|
+
if (interrupted && !interrupted.job.settled) {
|
|
1533
|
+
this.startNonCompact(interrupted.job);
|
|
1534
|
+
} else {
|
|
1535
|
+
this.resolveIdleWaiters();
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
async waitForIdle() {
|
|
1540
|
+
while (this.compactActive || this.activeAttempt) {
|
|
1541
|
+
await new Promise((resolve) => this.idleWaiters.push(resolve));
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
startNonCompact(job) {
|
|
1545
|
+
const externalSignal = job.externalSignal;
|
|
1546
|
+
if (externalSignal?.aborted) {
|
|
1547
|
+
job.reject(externalSignal.reason ?? new Error("inference cancelled"));
|
|
1548
|
+
this.resolveIdleWaiters();
|
|
1549
|
+
return;
|
|
1550
|
+
}
|
|
1551
|
+
const controller = new AbortController;
|
|
1552
|
+
const forwardExternalAbort = () => controller.abort(externalSignal?.reason);
|
|
1553
|
+
externalSignal?.addEventListener("abort", forwardExternalAbort, { once: true });
|
|
1554
|
+
const attempt = {
|
|
1555
|
+
job,
|
|
1556
|
+
controller,
|
|
1557
|
+
preempted: false,
|
|
1558
|
+
completion: Promise.resolve()
|
|
1559
|
+
};
|
|
1560
|
+
this.activeAttempt = attempt;
|
|
1561
|
+
attempt.completion = Promise.resolve().then(() => job.operation(controller.signal)).then((value) => {
|
|
1562
|
+
job.resolve(value);
|
|
1563
|
+
}, (error) => {
|
|
1564
|
+
if (!attempt.preempted)
|
|
1565
|
+
job.reject(error);
|
|
1566
|
+
}).finally(() => {
|
|
1567
|
+
externalSignal?.removeEventListener("abort", forwardExternalAbort);
|
|
1568
|
+
if (this.activeAttempt === attempt)
|
|
1569
|
+
this.activeAttempt = null;
|
|
1570
|
+
this.resolveIdleWaiters();
|
|
1571
|
+
});
|
|
1572
|
+
}
|
|
1573
|
+
resolveIdleWaiters() {
|
|
1574
|
+
if (this.compactActive || this.activeAttempt)
|
|
1575
|
+
return;
|
|
1576
|
+
const waiters = this.idleWaiters;
|
|
1577
|
+
this.idleWaiters = [];
|
|
1578
|
+
for (const resolve of waiters)
|
|
1579
|
+
resolve();
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1464
1583
|
// src/memory-activity.ts
|
|
1465
|
-
|
|
1466
|
-
var MEMORY_ACTIVITY_WIDGET_KEY = "goosedump-memory-activity";
|
|
1467
|
-
var ACTIVITY_FRAMES = ["▰▱▱▱", "▱▰▱▱", "▱▱▰▱", "▱▱▱▰", "▱▱▰▱", "▱▰▱▱"];
|
|
1584
|
+
var MEMORY_ACTIVITY_STATUS_KEY = "goosedump-memory-activity";
|
|
1468
1585
|
function showMemoryActivity(ctx) {
|
|
1469
1586
|
if (!ctx.hasUI)
|
|
1470
1587
|
return;
|
|
1471
|
-
ctx.ui.
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
frame = (frame + 1) % ACTIVITY_FRAMES.length;
|
|
1475
|
-
tui.requestRender();
|
|
1476
|
-
}, 140);
|
|
1477
|
-
timer.unref();
|
|
1478
|
-
return {
|
|
1479
|
-
render(width) {
|
|
1480
|
-
const bar = theme.fg("accent", ACTIVITY_FRAMES[frame] ?? ACTIVITY_FRAMES[0]);
|
|
1481
|
-
const label = theme.fg("muted", "Remembering...");
|
|
1482
|
-
return [truncateToWidth(`${bar} ${label}`, width, "")];
|
|
1483
|
-
},
|
|
1484
|
-
invalidate() {},
|
|
1485
|
-
dispose() {
|
|
1486
|
-
clearInterval(timer);
|
|
1487
|
-
}
|
|
1488
|
-
};
|
|
1489
|
-
});
|
|
1588
|
+
const dot = ctx.ui.theme.fg("accent", "●");
|
|
1589
|
+
const label = ctx.ui.theme.fg("muted", "remembering");
|
|
1590
|
+
ctx.ui.setStatus(MEMORY_ACTIVITY_STATUS_KEY, `${dot} ${label}`);
|
|
1490
1591
|
}
|
|
1491
1592
|
function hideMemoryActivity(ctx) {
|
|
1492
1593
|
if (ctx.hasUI)
|
|
1493
|
-
ctx.ui.
|
|
1594
|
+
ctx.ui.setStatus(MEMORY_ACTIVITY_STATUS_KEY, undefined);
|
|
1494
1595
|
}
|
|
1495
1596
|
|
|
1496
1597
|
// src/policy.ts
|
|
@@ -1650,7 +1751,7 @@ import {
|
|
|
1650
1751
|
defineTool,
|
|
1651
1752
|
truncateHead
|
|
1652
1753
|
} from "@earendil-works/pi-coding-agent";
|
|
1653
|
-
import { Type as Type3 } from "
|
|
1754
|
+
import { Type as Type3 } from "typebox";
|
|
1654
1755
|
function memoryDisabledResult() {
|
|
1655
1756
|
return textResult(MEMORY_DISABLED_MESSAGE);
|
|
1656
1757
|
}
|
|
@@ -1739,7 +1840,7 @@ function defineGooseTool(name, parameters, handler) {
|
|
|
1739
1840
|
});
|
|
1740
1841
|
}
|
|
1741
1842
|
function registerGooseTools(pi, deps) {
|
|
1742
|
-
const { nativeExecutablePath, enqueueRemember, requestCompaction } = deps;
|
|
1843
|
+
const { nativeExecutablePath, enqueueRemember, requestCompaction, runNonCompactInference } = deps;
|
|
1743
1844
|
pi.registerTool(defineGooseTool("goose_remember", Type3.Object({ ...historyTargetParams() }), async (params, _signal, ctx) => {
|
|
1744
1845
|
const disabled = requireMemoryEnabled(ctx);
|
|
1745
1846
|
if (disabled)
|
|
@@ -1747,12 +1848,12 @@ function registerGooseTools(pi, deps) {
|
|
|
1747
1848
|
const resolved = resolveHistoryTarget(ctx, "goose_remember", params);
|
|
1748
1849
|
if (!resolved.ok)
|
|
1749
1850
|
return resolved.result;
|
|
1750
|
-
enqueueRemember(resolved.target, ctx, (message) => {
|
|
1851
|
+
const accepted = enqueueRemember(resolved.target, ctx, (message) => {
|
|
1751
1852
|
if (ctx.hasUI) {
|
|
1752
1853
|
ctx.ui.notify(`goosedump memory learning failed: ${message}`, "warning");
|
|
1753
1854
|
}
|
|
1754
1855
|
});
|
|
1755
|
-
return textResult(`Memory learning
|
|
1856
|
+
return textResult(accepted ? `Memory learning started for ${resolved.target}.` : `Memory learning dropped for ${resolved.target}: another inference task is active.`);
|
|
1756
1857
|
}));
|
|
1757
1858
|
pi.registerTool(defineGooseTool("goose_recall", Type3.Object({
|
|
1758
1859
|
query: Type3.String({ description: PARAM_DESCRIPTIONS.memoryQuery }),
|
|
@@ -1775,12 +1876,16 @@ function registerGooseTools(pi, deps) {
|
|
|
1775
1876
|
const disabled = requireMemoryEnabled(ctx);
|
|
1776
1877
|
if (disabled)
|
|
1777
1878
|
return disabled;
|
|
1778
|
-
const
|
|
1879
|
+
const inference = runNonCompactInference((inferenceSignal) => goosedumpRecall(params.query, {
|
|
1779
1880
|
path: params.allProjects ? undefined : ctx.cwd,
|
|
1780
1881
|
type: params.type,
|
|
1781
1882
|
limit: params.limit,
|
|
1782
1883
|
history: params.history
|
|
1783
|
-
},
|
|
1884
|
+
}, inferenceSignal, nativeExecutablePath), signal);
|
|
1885
|
+
if (!inference) {
|
|
1886
|
+
return textResult("Memory recall dropped: another inference task is active.");
|
|
1887
|
+
}
|
|
1888
|
+
const { hits, warning } = await inference;
|
|
1784
1889
|
if (warning && ctx.hasUI)
|
|
1785
1890
|
ctx.ui.notify(warning, "warning");
|
|
1786
1891
|
if (hits.length === 0)
|
|
@@ -1927,7 +2032,7 @@ import {
|
|
|
1927
2032
|
Key,
|
|
1928
2033
|
decodeKittyPrintable,
|
|
1929
2034
|
matchesKey,
|
|
1930
|
-
truncateToWidth
|
|
2035
|
+
truncateToWidth,
|
|
1931
2036
|
visibleWidth,
|
|
1932
2037
|
wrapTextWithAnsi
|
|
1933
2038
|
} from "@earendil-works/pi-tui";
|
|
@@ -2010,7 +2115,7 @@ class NumberInputSubmenu {
|
|
|
2010
2115
|
}
|
|
2011
2116
|
}
|
|
2012
2117
|
function makeRow(content, innerW, border) {
|
|
2013
|
-
const line =
|
|
2118
|
+
const line = truncateToWidth(content, innerW);
|
|
2014
2119
|
const pad = Math.max(0, innerW - visibleWidth(line));
|
|
2015
2120
|
return `${border} ${line}${" ".repeat(pad)} ${border}`;
|
|
2016
2121
|
}
|
|
@@ -2078,7 +2183,7 @@ async function runSettingsEditor(ctx) {
|
|
|
2078
2183
|
let globalSettings = { ...DEFAULT_GOOSEDUMP_SETTINGS };
|
|
2079
2184
|
let projectSettings = {};
|
|
2080
2185
|
function settingLabel(key) {
|
|
2081
|
-
return
|
|
2186
|
+
return GOOSEDUMP_SETTING_LABELS[key];
|
|
2082
2187
|
}
|
|
2083
2188
|
function reloadSettings() {
|
|
2084
2189
|
globalSettings = readScopedGoosedumpSettings(location, "global");
|
|
@@ -2162,7 +2267,7 @@ async function runSettingsEditor(ctx) {
|
|
|
2162
2267
|
const { accent, bold, dim, muted, text } = colors;
|
|
2163
2268
|
const innerW = overlayInnerWidth(width);
|
|
2164
2269
|
const pad = (value, cellWidth) => {
|
|
2165
|
-
const clipped =
|
|
2270
|
+
const clipped = truncateToWidth(value, cellWidth);
|
|
2166
2271
|
return `${clipped}${" ".repeat(Math.max(0, cellWidth - visibleWidth(clipped)))}`;
|
|
2167
2272
|
};
|
|
2168
2273
|
const tabLabel = (label, active) => active ? accent(bold(`[${label}]`)) : muted(label);
|
|
@@ -2173,8 +2278,9 @@ async function runSettingsEditor(ctx) {
|
|
|
2173
2278
|
} else if (tab === "help") {
|
|
2174
2279
|
const shortcutWidth = Math.max(visibleWidth("Shortcut"), ...helpRows.map(([shortcut]) => visibleWidth(shortcut)));
|
|
2175
2280
|
const descriptionWidth = Math.max(1, innerW - shortcutWidth - 4);
|
|
2176
|
-
content.push(dim(` ${pad("Shortcut", shortcutWidth)} Description`), ...helpRows.map(([shortcut, description]) => ` ${accent(pad(shortcut, shortcutWidth))} ${text(
|
|
2281
|
+
content.push(dim(` ${pad("Shortcut", shortcutWidth)} Description`), ...helpRows.map(([shortcut, description]) => ` ${accent(pad(shortcut, shortcutWidth))} ${text(truncateToWidth(description, descriptionWidth))}`));
|
|
2177
2282
|
} else {
|
|
2283
|
+
const labelWidth = Math.max(...specs.map(({ key }) => visibleWidth(settingLabel(key))));
|
|
2178
2284
|
for (const [index, entry] of specs.entries()) {
|
|
2179
2285
|
const selected = index === selectedSetting;
|
|
2180
2286
|
const inherited = projectSettings[entry.key] === undefined;
|
|
@@ -2182,10 +2288,8 @@ async function runSettingsEditor(ctx) {
|
|
|
2182
2288
|
const valueText = `[ ${formatSettingDisplayValue(entry.key, value)} ]`;
|
|
2183
2289
|
const renderedValue = inherited ? dim(valueText) : selected ? accent(valueText) : text(valueText);
|
|
2184
2290
|
const cursor = selected ? accent("›") : " ";
|
|
2185
|
-
content.push(`${cursor} ${
|
|
2291
|
+
content.push(`${cursor} ${text(pad(settingLabel(entry.key), labelWidth))} ${renderedValue}`);
|
|
2186
2292
|
}
|
|
2187
|
-
const effective = resolveGoosedumpSettings(location, projectTrusted);
|
|
2188
|
-
content.push("", dim(`Effective: turns=${formatCompactSetting(effective.turnsBeforeCompact)} · summary=${effectiveSummaryMaxTokens(location, projectTrusted)} tokens · memory=${effective.enableMemory ? "on" : "off"}`));
|
|
2189
2293
|
}
|
|
2190
2294
|
return [
|
|
2191
2295
|
overlayHeader(colors, width, "Goosedump Settings"),
|
|
@@ -2314,7 +2418,7 @@ async function runSearchPanel(ctx, query, executablePath) {
|
|
|
2314
2418
|
}
|
|
2315
2419
|
return {
|
|
2316
2420
|
render(width) {
|
|
2317
|
-
const header = overlayHeader(colors, width, `Goosedump search: ${
|
|
2421
|
+
const header = overlayHeader(colors, width, `Goosedump search: ${truncateToWidth(query, 44)}`);
|
|
2318
2422
|
if (error) {
|
|
2319
2423
|
return [header, ...overlayError(colors, width, error), overlayFooter(colors, width)];
|
|
2320
2424
|
}
|
|
@@ -2672,50 +2776,35 @@ function createGoosedumpIntegration(options = {}) {
|
|
|
2672
2776
|
const shouldRegisterTool = options.registerTool ?? true;
|
|
2673
2777
|
const shouldOverrideDefaultCompaction = options.overrideDefaultCompaction ?? true;
|
|
2674
2778
|
const nativeExecutablePath = options.nativeExecutablePath ?? resolveNativeGoosedumpBinary();
|
|
2675
|
-
const
|
|
2676
|
-
let rememberRunning = null;
|
|
2779
|
+
const inference = new InferenceCoordinator;
|
|
2677
2780
|
function enqueueRemember(target, ctx, reportError) {
|
|
2678
2781
|
if (!isMemoryEnabled(ctx.cwd, projectSettingsTrusted(ctx)))
|
|
2679
|
-
return;
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2684
|
-
|
|
2685
|
-
|
|
2686
|
-
|
|
2687
|
-
function ensureRememberWorker() {
|
|
2688
|
-
if (rememberRunning)
|
|
2689
|
-
return;
|
|
2690
|
-
rememberRunning = (async () => {
|
|
2691
|
-
while (rememberQueue.length > 0) {
|
|
2692
|
-
const job = rememberQueue.shift();
|
|
2693
|
-
if (!job)
|
|
2694
|
-
continue;
|
|
2695
|
-
showMemoryActivity(job.ctx);
|
|
2696
|
-
try {
|
|
2697
|
-
await goosedumpMemoryRemember(job.target, undefined, nativeExecutablePath);
|
|
2698
|
-
} catch (err) {
|
|
2699
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2700
|
-
job.reportError(message);
|
|
2701
|
-
} finally {
|
|
2702
|
-
hideMemoryActivity(job.ctx);
|
|
2703
|
-
}
|
|
2782
|
+
return false;
|
|
2783
|
+
const leaseOwner = randomUUID2();
|
|
2784
|
+
const running = inference.tryRunNonCompact(async (signal) => {
|
|
2785
|
+
showMemoryActivity(ctx);
|
|
2786
|
+
try {
|
|
2787
|
+
await goosedumpMemoryRemember(target, signal, nativeExecutablePath, leaseOwner);
|
|
2788
|
+
} finally {
|
|
2789
|
+
hideMemoryActivity(ctx);
|
|
2704
2790
|
}
|
|
2705
|
-
})().finally(() => {
|
|
2706
|
-
rememberRunning = null;
|
|
2707
|
-
if (rememberQueue.length > 0)
|
|
2708
|
-
ensureRememberWorker();
|
|
2709
2791
|
});
|
|
2792
|
+
if (!running)
|
|
2793
|
+
return false;
|
|
2794
|
+
running.catch((err) => {
|
|
2795
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2796
|
+
reportError(message);
|
|
2797
|
+
});
|
|
2798
|
+
return true;
|
|
2710
2799
|
}
|
|
2711
2800
|
async function flushRememberQueue() {
|
|
2712
|
-
|
|
2713
|
-
await rememberRunning;
|
|
2801
|
+
await inference.waitForIdle();
|
|
2714
2802
|
}
|
|
2715
2803
|
const compaction = new CompactionRuntime({
|
|
2716
2804
|
overrideDefaultCompaction: shouldOverrideDefaultCompaction,
|
|
2717
2805
|
nativeExecutablePath,
|
|
2718
|
-
enqueueRemember
|
|
2806
|
+
enqueueRemember,
|
|
2807
|
+
runCompactInference: (operation) => inference.runCompact(operation)
|
|
2719
2808
|
});
|
|
2720
2809
|
function register(pi) {
|
|
2721
2810
|
compaction.setActivePi(pi);
|
|
@@ -2723,6 +2812,7 @@ function createGoosedumpIntegration(options = {}) {
|
|
|
2723
2812
|
registerGooseTools(pi, {
|
|
2724
2813
|
nativeExecutablePath,
|
|
2725
2814
|
enqueueRemember,
|
|
2815
|
+
runNonCompactInference: (operation, signal) => inference.tryRunNonCompact(operation, signal),
|
|
2726
2816
|
requestCompaction: (ctx, keep) => compaction.requestCompaction(ctx, keep)
|
|
2727
2817
|
});
|
|
2728
2818
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goosedump",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.57",
|
|
4
4
|
"description": "Pi extension for goosedump session search, durable memory, session management, and compaction",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"compaction",
|
|
@@ -37,24 +37,32 @@
|
|
|
37
37
|
"ci:test": "npm test"
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@
|
|
41
|
-
"@jarkkojs/goosedump": "^0.12.55",
|
|
42
|
-
"@sinclair/typebox": "^0.34.49"
|
|
40
|
+
"@jarkkojs/goosedump": "^0.12.57"
|
|
43
41
|
},
|
|
44
42
|
"devDependencies": {
|
|
45
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
43
|
+
"@earendil-works/pi-coding-agent": "^0.84.2",
|
|
44
|
+
"@earendil-works/pi-tui": "^0.84.2",
|
|
46
45
|
"@types/node": "^26.1.2",
|
|
47
46
|
"oxfmt": "^0.60.0",
|
|
48
47
|
"oxlint": "^1.68.0",
|
|
48
|
+
"typebox": "1.3.7",
|
|
49
49
|
"typescript": "^7.0.2",
|
|
50
50
|
"vitest": "^4.1.10"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
|
-
"@earendil-works/pi-coding-agent": "^0.
|
|
53
|
+
"@earendil-works/pi-coding-agent": "^0.84.2",
|
|
54
|
+
"@earendil-works/pi-tui": "^0.84.2",
|
|
55
|
+
"typebox": "^1.3.7"
|
|
54
56
|
},
|
|
55
57
|
"peerDependenciesMeta": {
|
|
56
58
|
"@earendil-works/pi-coding-agent": {
|
|
57
59
|
"optional": true
|
|
60
|
+
},
|
|
61
|
+
"@earendil-works/pi-tui": {
|
|
62
|
+
"optional": true
|
|
63
|
+
},
|
|
64
|
+
"typebox": {
|
|
65
|
+
"optional": true
|
|
58
66
|
}
|
|
59
67
|
},
|
|
60
68
|
"pi": {
|