pi-goosedump 0.12.55 → 0.12.56
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 +164 -78
- 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>
|
|
@@ -529,8 +532,11 @@ async function goosedumpRecall(query, options = {}, signal, executablePath) {
|
|
|
529
532
|
const output = await runGoosedumpAsync(args, signal, executablePath);
|
|
530
533
|
return { hits: parseMemoryHits(output.stdout), warning: output.stderr.trim() };
|
|
531
534
|
}
|
|
532
|
-
async function goosedumpMemoryRemember(target, signal, executablePath) {
|
|
533
|
-
const
|
|
535
|
+
async function goosedumpMemoryRemember(target, signal, executablePath, leaseOwner) {
|
|
536
|
+
const args = ["memory", "learn", target, "--json"];
|
|
537
|
+
if (leaseOwner)
|
|
538
|
+
args.push("--lease-owner", leaseOwner);
|
|
539
|
+
const output = await runGoosedumpAsync(args, signal, executablePath);
|
|
534
540
|
return output.stdout.trim();
|
|
535
541
|
}
|
|
536
542
|
async function goosedumpMemoryForget(target, signal, executablePath) {
|
|
@@ -620,7 +626,7 @@ import { findCutPoint } from "@earendil-works/pi-coding-agent";
|
|
|
620
626
|
|
|
621
627
|
// src/settings.ts
|
|
622
628
|
import { DEFAULT_COMPACTION_SETTINGS } from "@earendil-works/pi-coding-agent";
|
|
623
|
-
import { Value as Value3 } from "
|
|
629
|
+
import { Value as Value3 } from "typebox/value";
|
|
624
630
|
import { randomUUID } from "node:crypto";
|
|
625
631
|
import {
|
|
626
632
|
existsSync as existsSync2,
|
|
@@ -633,9 +639,9 @@ import {
|
|
|
633
639
|
import { homedir } from "node:os";
|
|
634
640
|
import { dirname as dirname2, join as join2 } from "node:path";
|
|
635
641
|
function settingSpecFromSchema(schema) {
|
|
636
|
-
|
|
642
|
+
const { type, minimum, maximum } = schema;
|
|
643
|
+
if (type === "boolean")
|
|
637
644
|
return { kind: "boolean" };
|
|
638
|
-
const { minimum, maximum } = schema;
|
|
639
645
|
if (typeof minimum !== "number" || typeof maximum !== "number") {
|
|
640
646
|
throw new Error("goosedump setting schema is missing integer bounds");
|
|
641
647
|
}
|
|
@@ -996,6 +1002,7 @@ class CompactionRuntime {
|
|
|
996
1002
|
overrideDefaultCompaction;
|
|
997
1003
|
nativeExecutablePath;
|
|
998
1004
|
enqueueRemember;
|
|
1005
|
+
runCompactInference;
|
|
999
1006
|
compactSettings = DEFAULT_GOOSEDUMP_SETTINGS;
|
|
1000
1007
|
piAutoCompactionEnabled = DEFAULT_COMPACTION_SETTINGS2.enabled;
|
|
1001
1008
|
piCompactionReserveTokens = DEFAULT_COMPACTION_SETTINGS2.reserveTokens;
|
|
@@ -1014,6 +1021,7 @@ class CompactionRuntime {
|
|
|
1014
1021
|
this.overrideDefaultCompaction = deps.overrideDefaultCompaction;
|
|
1015
1022
|
this.nativeExecutablePath = deps.nativeExecutablePath;
|
|
1016
1023
|
this.enqueueRemember = deps.enqueueRemember;
|
|
1024
|
+
this.runCompactInference = deps.runCompactInference;
|
|
1017
1025
|
}
|
|
1018
1026
|
setActivePi(pi) {
|
|
1019
1027
|
this.activePi = pi;
|
|
@@ -1224,7 +1232,7 @@ class CompactionRuntime {
|
|
|
1224
1232
|
});
|
|
1225
1233
|
}
|
|
1226
1234
|
}
|
|
1227
|
-
const compaction = await buildGoosedumpCompaction(target, event.preparation, event.branchEntries, effectiveSummaryMaxTokens(ctx.cwd, projectSettingsTrusted(ctx)), keep, signal, this.nativeExecutablePath);
|
|
1235
|
+
const compaction = await this.runCompactInference(() => buildGoosedumpCompaction(target, event.preparation, event.branchEntries, effectiveSummaryMaxTokens(ctx.cwd, projectSettingsTrusted(ctx)), keep, signal, this.nativeExecutablePath));
|
|
1228
1236
|
if (!this.runningOperationIsCurrent(operation, generation, sessionFile)) {
|
|
1229
1237
|
return { cancel: true };
|
|
1230
1238
|
}
|
|
@@ -1461,36 +1469,124 @@ class CompactionRuntime {
|
|
|
1461
1469
|
}
|
|
1462
1470
|
}
|
|
1463
1471
|
|
|
1472
|
+
// src/inference-coordinator.ts
|
|
1473
|
+
class InferenceBusyError extends Error {
|
|
1474
|
+
constructor(message = "another inference task is already active") {
|
|
1475
|
+
super(message);
|
|
1476
|
+
this.name = "InferenceBusyError";
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
class InferenceCoordinator {
|
|
1481
|
+
activeAttempt = null;
|
|
1482
|
+
compactActive = false;
|
|
1483
|
+
idleWaiters = [];
|
|
1484
|
+
tryRunNonCompact(operation, externalSignal) {
|
|
1485
|
+
if (this.compactActive || this.activeAttempt)
|
|
1486
|
+
return null;
|
|
1487
|
+
let resolvePromise = () => {};
|
|
1488
|
+
let rejectPromise = () => {};
|
|
1489
|
+
const promise = new Promise((resolve, reject) => {
|
|
1490
|
+
resolvePromise = resolve;
|
|
1491
|
+
rejectPromise = reject;
|
|
1492
|
+
});
|
|
1493
|
+
const job = {
|
|
1494
|
+
operation,
|
|
1495
|
+
externalSignal,
|
|
1496
|
+
settled: false,
|
|
1497
|
+
resolve: (value) => {
|
|
1498
|
+
if (job.settled)
|
|
1499
|
+
return;
|
|
1500
|
+
job.settled = true;
|
|
1501
|
+
resolvePromise(value);
|
|
1502
|
+
},
|
|
1503
|
+
reject: (error) => {
|
|
1504
|
+
if (job.settled)
|
|
1505
|
+
return;
|
|
1506
|
+
job.settled = true;
|
|
1507
|
+
rejectPromise(error);
|
|
1508
|
+
}
|
|
1509
|
+
};
|
|
1510
|
+
this.startNonCompact(job);
|
|
1511
|
+
return promise;
|
|
1512
|
+
}
|
|
1513
|
+
async runCompact(operation) {
|
|
1514
|
+
if (this.compactActive)
|
|
1515
|
+
throw new InferenceBusyError("compaction inference is already active");
|
|
1516
|
+
this.compactActive = true;
|
|
1517
|
+
const interrupted = this.activeAttempt;
|
|
1518
|
+
if (interrupted) {
|
|
1519
|
+
interrupted.preempted = true;
|
|
1520
|
+
interrupted.controller.abort(new Error("inference paused for compaction"));
|
|
1521
|
+
await interrupted.completion;
|
|
1522
|
+
}
|
|
1523
|
+
try {
|
|
1524
|
+
return await operation();
|
|
1525
|
+
} finally {
|
|
1526
|
+
this.compactActive = false;
|
|
1527
|
+
if (interrupted && !interrupted.job.settled) {
|
|
1528
|
+
this.startNonCompact(interrupted.job);
|
|
1529
|
+
} else {
|
|
1530
|
+
this.resolveIdleWaiters();
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
async waitForIdle() {
|
|
1535
|
+
while (this.compactActive || this.activeAttempt) {
|
|
1536
|
+
await new Promise((resolve) => this.idleWaiters.push(resolve));
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
startNonCompact(job) {
|
|
1540
|
+
const externalSignal = job.externalSignal;
|
|
1541
|
+
if (externalSignal?.aborted) {
|
|
1542
|
+
job.reject(externalSignal.reason ?? new Error("inference cancelled"));
|
|
1543
|
+
this.resolveIdleWaiters();
|
|
1544
|
+
return;
|
|
1545
|
+
}
|
|
1546
|
+
const controller = new AbortController;
|
|
1547
|
+
const forwardExternalAbort = () => controller.abort(externalSignal?.reason);
|
|
1548
|
+
externalSignal?.addEventListener("abort", forwardExternalAbort, { once: true });
|
|
1549
|
+
const attempt = {
|
|
1550
|
+
job,
|
|
1551
|
+
controller,
|
|
1552
|
+
preempted: false,
|
|
1553
|
+
completion: Promise.resolve()
|
|
1554
|
+
};
|
|
1555
|
+
this.activeAttempt = attempt;
|
|
1556
|
+
attempt.completion = Promise.resolve().then(() => job.operation(controller.signal)).then((value) => {
|
|
1557
|
+
job.resolve(value);
|
|
1558
|
+
}, (error) => {
|
|
1559
|
+
if (!attempt.preempted)
|
|
1560
|
+
job.reject(error);
|
|
1561
|
+
}).finally(() => {
|
|
1562
|
+
externalSignal?.removeEventListener("abort", forwardExternalAbort);
|
|
1563
|
+
if (this.activeAttempt === attempt)
|
|
1564
|
+
this.activeAttempt = null;
|
|
1565
|
+
this.resolveIdleWaiters();
|
|
1566
|
+
});
|
|
1567
|
+
}
|
|
1568
|
+
resolveIdleWaiters() {
|
|
1569
|
+
if (this.compactActive || this.activeAttempt)
|
|
1570
|
+
return;
|
|
1571
|
+
const waiters = this.idleWaiters;
|
|
1572
|
+
this.idleWaiters = [];
|
|
1573
|
+
for (const resolve of waiters)
|
|
1574
|
+
resolve();
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1464
1578
|
// src/memory-activity.ts
|
|
1465
|
-
|
|
1466
|
-
var MEMORY_ACTIVITY_WIDGET_KEY = "goosedump-memory-activity";
|
|
1467
|
-
var ACTIVITY_FRAMES = ["▰▱▱▱", "▱▰▱▱", "▱▱▰▱", "▱▱▱▰", "▱▱▰▱", "▱▰▱▱"];
|
|
1579
|
+
var MEMORY_ACTIVITY_STATUS_KEY = "goosedump-memory-activity";
|
|
1468
1580
|
function showMemoryActivity(ctx) {
|
|
1469
1581
|
if (!ctx.hasUI)
|
|
1470
1582
|
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
|
-
});
|
|
1583
|
+
const dot = ctx.ui.theme.fg("accent", "●");
|
|
1584
|
+
const label = ctx.ui.theme.fg("muted", "remembering");
|
|
1585
|
+
ctx.ui.setStatus(MEMORY_ACTIVITY_STATUS_KEY, `${dot} ${label}`);
|
|
1490
1586
|
}
|
|
1491
1587
|
function hideMemoryActivity(ctx) {
|
|
1492
1588
|
if (ctx.hasUI)
|
|
1493
|
-
ctx.ui.
|
|
1589
|
+
ctx.ui.setStatus(MEMORY_ACTIVITY_STATUS_KEY, undefined);
|
|
1494
1590
|
}
|
|
1495
1591
|
|
|
1496
1592
|
// src/policy.ts
|
|
@@ -1650,7 +1746,7 @@ import {
|
|
|
1650
1746
|
defineTool,
|
|
1651
1747
|
truncateHead
|
|
1652
1748
|
} from "@earendil-works/pi-coding-agent";
|
|
1653
|
-
import { Type as Type3 } from "
|
|
1749
|
+
import { Type as Type3 } from "typebox";
|
|
1654
1750
|
function memoryDisabledResult() {
|
|
1655
1751
|
return textResult(MEMORY_DISABLED_MESSAGE);
|
|
1656
1752
|
}
|
|
@@ -1739,7 +1835,7 @@ function defineGooseTool(name, parameters, handler) {
|
|
|
1739
1835
|
});
|
|
1740
1836
|
}
|
|
1741
1837
|
function registerGooseTools(pi, deps) {
|
|
1742
|
-
const { nativeExecutablePath, enqueueRemember, requestCompaction } = deps;
|
|
1838
|
+
const { nativeExecutablePath, enqueueRemember, requestCompaction, runNonCompactInference } = deps;
|
|
1743
1839
|
pi.registerTool(defineGooseTool("goose_remember", Type3.Object({ ...historyTargetParams() }), async (params, _signal, ctx) => {
|
|
1744
1840
|
const disabled = requireMemoryEnabled(ctx);
|
|
1745
1841
|
if (disabled)
|
|
@@ -1747,12 +1843,12 @@ function registerGooseTools(pi, deps) {
|
|
|
1747
1843
|
const resolved = resolveHistoryTarget(ctx, "goose_remember", params);
|
|
1748
1844
|
if (!resolved.ok)
|
|
1749
1845
|
return resolved.result;
|
|
1750
|
-
enqueueRemember(resolved.target, ctx, (message) => {
|
|
1846
|
+
const accepted = enqueueRemember(resolved.target, ctx, (message) => {
|
|
1751
1847
|
if (ctx.hasUI) {
|
|
1752
1848
|
ctx.ui.notify(`goosedump memory learning failed: ${message}`, "warning");
|
|
1753
1849
|
}
|
|
1754
1850
|
});
|
|
1755
|
-
return textResult(`Memory learning
|
|
1851
|
+
return textResult(accepted ? `Memory learning started for ${resolved.target}.` : `Memory learning dropped for ${resolved.target}: another inference task is active.`);
|
|
1756
1852
|
}));
|
|
1757
1853
|
pi.registerTool(defineGooseTool("goose_recall", Type3.Object({
|
|
1758
1854
|
query: Type3.String({ description: PARAM_DESCRIPTIONS.memoryQuery }),
|
|
@@ -1775,12 +1871,16 @@ function registerGooseTools(pi, deps) {
|
|
|
1775
1871
|
const disabled = requireMemoryEnabled(ctx);
|
|
1776
1872
|
if (disabled)
|
|
1777
1873
|
return disabled;
|
|
1778
|
-
const
|
|
1874
|
+
const inference = runNonCompactInference((inferenceSignal) => goosedumpRecall(params.query, {
|
|
1779
1875
|
path: params.allProjects ? undefined : ctx.cwd,
|
|
1780
1876
|
type: params.type,
|
|
1781
1877
|
limit: params.limit,
|
|
1782
1878
|
history: params.history
|
|
1783
|
-
},
|
|
1879
|
+
}, inferenceSignal, nativeExecutablePath), signal);
|
|
1880
|
+
if (!inference) {
|
|
1881
|
+
return textResult("Memory recall dropped: another inference task is active.");
|
|
1882
|
+
}
|
|
1883
|
+
const { hits, warning } = await inference;
|
|
1784
1884
|
if (warning && ctx.hasUI)
|
|
1785
1885
|
ctx.ui.notify(warning, "warning");
|
|
1786
1886
|
if (hits.length === 0)
|
|
@@ -1927,7 +2027,7 @@ import {
|
|
|
1927
2027
|
Key,
|
|
1928
2028
|
decodeKittyPrintable,
|
|
1929
2029
|
matchesKey,
|
|
1930
|
-
truncateToWidth
|
|
2030
|
+
truncateToWidth,
|
|
1931
2031
|
visibleWidth,
|
|
1932
2032
|
wrapTextWithAnsi
|
|
1933
2033
|
} from "@earendil-works/pi-tui";
|
|
@@ -2010,7 +2110,7 @@ class NumberInputSubmenu {
|
|
|
2010
2110
|
}
|
|
2011
2111
|
}
|
|
2012
2112
|
function makeRow(content, innerW, border) {
|
|
2013
|
-
const line =
|
|
2113
|
+
const line = truncateToWidth(content, innerW);
|
|
2014
2114
|
const pad = Math.max(0, innerW - visibleWidth(line));
|
|
2015
2115
|
return `${border} ${line}${" ".repeat(pad)} ${border}`;
|
|
2016
2116
|
}
|
|
@@ -2162,7 +2262,7 @@ async function runSettingsEditor(ctx) {
|
|
|
2162
2262
|
const { accent, bold, dim, muted, text } = colors;
|
|
2163
2263
|
const innerW = overlayInnerWidth(width);
|
|
2164
2264
|
const pad = (value, cellWidth) => {
|
|
2165
|
-
const clipped =
|
|
2265
|
+
const clipped = truncateToWidth(value, cellWidth);
|
|
2166
2266
|
return `${clipped}${" ".repeat(Math.max(0, cellWidth - visibleWidth(clipped)))}`;
|
|
2167
2267
|
};
|
|
2168
2268
|
const tabLabel = (label, active) => active ? accent(bold(`[${label}]`)) : muted(label);
|
|
@@ -2173,7 +2273,7 @@ async function runSettingsEditor(ctx) {
|
|
|
2173
2273
|
} else if (tab === "help") {
|
|
2174
2274
|
const shortcutWidth = Math.max(visibleWidth("Shortcut"), ...helpRows.map(([shortcut]) => visibleWidth(shortcut)));
|
|
2175
2275
|
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(
|
|
2276
|
+
content.push(dim(` ${pad("Shortcut", shortcutWidth)} Description`), ...helpRows.map(([shortcut, description]) => ` ${accent(pad(shortcut, shortcutWidth))} ${text(truncateToWidth(description, descriptionWidth))}`));
|
|
2177
2277
|
} else {
|
|
2178
2278
|
for (const [index, entry] of specs.entries()) {
|
|
2179
2279
|
const selected = index === selectedSetting;
|
|
@@ -2314,7 +2414,7 @@ async function runSearchPanel(ctx, query, executablePath) {
|
|
|
2314
2414
|
}
|
|
2315
2415
|
return {
|
|
2316
2416
|
render(width) {
|
|
2317
|
-
const header = overlayHeader(colors, width, `Goosedump search: ${
|
|
2417
|
+
const header = overlayHeader(colors, width, `Goosedump search: ${truncateToWidth(query, 44)}`);
|
|
2318
2418
|
if (error) {
|
|
2319
2419
|
return [header, ...overlayError(colors, width, error), overlayFooter(colors, width)];
|
|
2320
2420
|
}
|
|
@@ -2672,50 +2772,35 @@ function createGoosedumpIntegration(options = {}) {
|
|
|
2672
2772
|
const shouldRegisterTool = options.registerTool ?? true;
|
|
2673
2773
|
const shouldOverrideDefaultCompaction = options.overrideDefaultCompaction ?? true;
|
|
2674
2774
|
const nativeExecutablePath = options.nativeExecutablePath ?? resolveNativeGoosedumpBinary();
|
|
2675
|
-
const
|
|
2676
|
-
let rememberRunning = null;
|
|
2775
|
+
const inference = new InferenceCoordinator;
|
|
2677
2776
|
function enqueueRemember(target, ctx, reportError) {
|
|
2678
2777
|
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
|
-
}
|
|
2778
|
+
return false;
|
|
2779
|
+
const leaseOwner = randomUUID2();
|
|
2780
|
+
const running = inference.tryRunNonCompact(async (signal) => {
|
|
2781
|
+
showMemoryActivity(ctx);
|
|
2782
|
+
try {
|
|
2783
|
+
await goosedumpMemoryRemember(target, signal, nativeExecutablePath, leaseOwner);
|
|
2784
|
+
} finally {
|
|
2785
|
+
hideMemoryActivity(ctx);
|
|
2704
2786
|
}
|
|
2705
|
-
})().finally(() => {
|
|
2706
|
-
rememberRunning = null;
|
|
2707
|
-
if (rememberQueue.length > 0)
|
|
2708
|
-
ensureRememberWorker();
|
|
2709
2787
|
});
|
|
2788
|
+
if (!running)
|
|
2789
|
+
return false;
|
|
2790
|
+
running.catch((err) => {
|
|
2791
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
2792
|
+
reportError(message);
|
|
2793
|
+
});
|
|
2794
|
+
return true;
|
|
2710
2795
|
}
|
|
2711
2796
|
async function flushRememberQueue() {
|
|
2712
|
-
|
|
2713
|
-
await rememberRunning;
|
|
2797
|
+
await inference.waitForIdle();
|
|
2714
2798
|
}
|
|
2715
2799
|
const compaction = new CompactionRuntime({
|
|
2716
2800
|
overrideDefaultCompaction: shouldOverrideDefaultCompaction,
|
|
2717
2801
|
nativeExecutablePath,
|
|
2718
|
-
enqueueRemember
|
|
2802
|
+
enqueueRemember,
|
|
2803
|
+
runCompactInference: (operation) => inference.runCompact(operation)
|
|
2719
2804
|
});
|
|
2720
2805
|
function register(pi) {
|
|
2721
2806
|
compaction.setActivePi(pi);
|
|
@@ -2723,6 +2808,7 @@ function createGoosedumpIntegration(options = {}) {
|
|
|
2723
2808
|
registerGooseTools(pi, {
|
|
2724
2809
|
nativeExecutablePath,
|
|
2725
2810
|
enqueueRemember,
|
|
2811
|
+
runNonCompactInference: (operation, signal) => inference.tryRunNonCompact(operation, signal),
|
|
2726
2812
|
requestCompaction: (ctx, keep) => compaction.requestCompaction(ctx, keep)
|
|
2727
2813
|
});
|
|
2728
2814
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goosedump",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.56",
|
|
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.56"
|
|
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": {
|