pi-goosedump 0.7.3 → 0.8.1
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 +16 -1
- package/index.ts +435 -214
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# pi-goosedump
|
|
2
2
|
|
|
3
|
+
<img alt="pi-goosedump browser dialog" src="screenshot.png">
|
|
4
|
+
|
|
3
5
|
Coding agent context data browser plugin for [pi](https://pi.dev/) using
|
|
4
6
|
[`goosedump`](https://github.com/jarkkojs/goosedump).
|
|
5
7
|
|
|
@@ -73,7 +75,9 @@ goosedump({ action: "grep", pattern: "*rand*", outputFormat: "claude" })
|
|
|
73
75
|
|
|
74
76
|
### Command: `/goosedump`
|
|
75
77
|
|
|
76
|
-
Opens an interactive
|
|
78
|
+
Opens an interactive dashboard. The sessions tab lists Pi sessions from the
|
|
79
|
+
current working directory only. The settings tab edits the global and project
|
|
80
|
+
`goosedump` settings sections.
|
|
77
81
|
|
|
78
82
|
### Compaction
|
|
79
83
|
|
|
@@ -82,6 +86,17 @@ current session to its goosedump id and runs `goosedump compact <id>` with Pi's
|
|
|
82
86
|
compaction range (`--from`, `--until`, and `--scope`) so the generated summary
|
|
83
87
|
matches the entries Pi is about to replace.
|
|
84
88
|
|
|
89
|
+
An optional goosedump counter can also trigger compaction. `0` disables the counter;
|
|
90
|
+
when it fires, it resets to its configured value.
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"goosedump": {
|
|
95
|
+
"turnsBeforeCompact": 0
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
85
100
|
## License
|
|
86
101
|
|
|
87
102
|
`pi-goosedump` is licensed under `MIT`. See [LICENSE](LICENSE) for more
|
package/index.ts
CHANGED
|
@@ -10,16 +10,22 @@ import type {
|
|
|
10
10
|
SessionEntry,
|
|
11
11
|
} from '@earendil-works/pi-coding-agent';
|
|
12
12
|
|
|
13
|
-
import { defineTool } from '@earendil-works/pi-coding-agent';
|
|
13
|
+
import { defineTool, getSettingsListTheme } from '@earendil-works/pi-coding-agent';
|
|
14
14
|
|
|
15
15
|
import { execFileSync } from 'node:child_process';
|
|
16
|
-
import { existsSync } from 'node:fs';
|
|
16
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
17
17
|
import { createRequire } from 'node:module';
|
|
18
18
|
import { homedir } from 'node:os';
|
|
19
|
-
import { basename, extname, isAbsolute, join, relative } from 'node:path';
|
|
19
|
+
import { basename, dirname, extname, isAbsolute, join, relative } from 'node:path';
|
|
20
20
|
|
|
21
21
|
import {
|
|
22
|
+
type Component,
|
|
23
|
+
Input,
|
|
22
24
|
Key,
|
|
25
|
+
SettingsList,
|
|
26
|
+
type SettingItem,
|
|
27
|
+
decodeKittyPrintable,
|
|
28
|
+
getKeybindings,
|
|
23
29
|
matchesKey,
|
|
24
30
|
truncateToWidth,
|
|
25
31
|
visibleWidth,
|
|
@@ -29,25 +35,24 @@ import { Type } from '@sinclair/typebox';
|
|
|
29
35
|
|
|
30
36
|
const require = createRequire(import.meta.url);
|
|
31
37
|
|
|
32
|
-
const GOOSEDUMP_VERSION = [0,
|
|
38
|
+
const GOOSEDUMP_VERSION = [0, 8, 2] as const;
|
|
33
39
|
|
|
34
|
-
// One element of goosedump 0.
|
|
40
|
+
// One element of goosedump 0.8's `list` JSON output.
|
|
35
41
|
interface ListEntryJson {
|
|
36
42
|
id: string;
|
|
37
43
|
parent: string | null;
|
|
38
44
|
provider: string;
|
|
39
|
-
native_id: string;
|
|
40
45
|
cwd: string;
|
|
41
46
|
count: number;
|
|
42
47
|
label: string | null;
|
|
43
48
|
from: string | null;
|
|
44
49
|
until: string | null;
|
|
50
|
+
native_id?: string;
|
|
45
51
|
}
|
|
46
52
|
|
|
47
53
|
interface GoosedumpListing {
|
|
48
54
|
id: string;
|
|
49
55
|
provider: string;
|
|
50
|
-
nativeId: string;
|
|
51
56
|
cwd: string;
|
|
52
57
|
parent: string | null;
|
|
53
58
|
label: string | null;
|
|
@@ -66,24 +71,26 @@ interface GoosedumpSearchResult {
|
|
|
66
71
|
page: number;
|
|
67
72
|
totalPages: number;
|
|
68
73
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
name: string;
|
|
72
|
-
arguments: unknown;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
interface ShowMessageJson {
|
|
76
|
-
entryId: string;
|
|
77
|
-
kind: 'text' | 'assistant' | 'toolResult' | 'bash';
|
|
78
|
-
role?: string;
|
|
74
|
+
interface ShowPartJson {
|
|
75
|
+
type: 'text' | 'reasoning' | 'toolCall' | 'toolResult' | 'image' | 'bash' | 'passthrough';
|
|
79
76
|
text?: string;
|
|
80
|
-
|
|
81
|
-
|
|
77
|
+
name?: string;
|
|
78
|
+
arguments?: unknown;
|
|
82
79
|
toolName?: string;
|
|
83
80
|
content?: string;
|
|
84
81
|
isError?: boolean;
|
|
82
|
+
mimeType?: string;
|
|
83
|
+
ocrText?: string;
|
|
85
84
|
command?: string;
|
|
86
85
|
output?: string;
|
|
86
|
+
kind?: string;
|
|
87
|
+
raw?: unknown;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
interface ShowMessageJson {
|
|
91
|
+
entryId: string;
|
|
92
|
+
role: string;
|
|
93
|
+
parts: ShowPartJson[];
|
|
87
94
|
}
|
|
88
95
|
|
|
89
96
|
interface ShowJson {
|
|
@@ -115,6 +122,87 @@ interface CompactJson {
|
|
|
115
122
|
brief: string;
|
|
116
123
|
}
|
|
117
124
|
|
|
125
|
+
interface GoosedumpSettings {
|
|
126
|
+
turnsBeforeCompact: number;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
type SettingsScope = 'global' | 'project';
|
|
130
|
+
type GoosedumpSettingKey = keyof GoosedumpSettings;
|
|
131
|
+
|
|
132
|
+
const DEFAULT_GOOSEDUMP_SETTINGS: GoosedumpSettings = {
|
|
133
|
+
turnsBeforeCompact: 0,
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
function piAgentDir(): string {
|
|
137
|
+
return process.env.PI_CODING_AGENT_DIR ?? join(homedir(), '.pi', 'agent');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function globalSettingsPath(): string {
|
|
141
|
+
return join(piAgentDir(), 'settings.json');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function projectSettingsPath(cwd: string): string {
|
|
145
|
+
return join(cwd, '.pi', 'settings.json');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
149
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function readJsonObject(path: string): Record<string, unknown> {
|
|
153
|
+
if (!existsSync(path)) return {};
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
const parsed = JSON.parse(readFileSync(path, 'utf8')) as unknown;
|
|
157
|
+
return isRecord(parsed) ? parsed : {};
|
|
158
|
+
} catch {
|
|
159
|
+
return {};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function readGoosedumpSettingsFrom(path: string): Partial<GoosedumpSettings> {
|
|
164
|
+
const raw = readJsonObject(path).goosedump;
|
|
165
|
+
if (!isRecord(raw)) return {};
|
|
166
|
+
|
|
167
|
+
const settings: Partial<GoosedumpSettings> = {};
|
|
168
|
+
const value = raw.turnsBeforeCompact;
|
|
169
|
+
if (typeof value === 'number' && Number.isSafeInteger(value) && value >= 0) {
|
|
170
|
+
settings.turnsBeforeCompact = value;
|
|
171
|
+
}
|
|
172
|
+
return settings;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function resolveGoosedumpSettings(cwd: string): GoosedumpSettings {
|
|
176
|
+
return {
|
|
177
|
+
...DEFAULT_GOOSEDUMP_SETTINGS,
|
|
178
|
+
...readGoosedumpSettingsFrom(globalSettingsPath()),
|
|
179
|
+
...readGoosedumpSettingsFrom(projectSettingsPath(cwd)),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function readScopedGoosedumpSettings(cwd: string, scope: SettingsScope): GoosedumpSettings {
|
|
184
|
+
const path = scope === 'global' ? globalSettingsPath() : projectSettingsPath(cwd);
|
|
185
|
+
return { ...DEFAULT_GOOSEDUMP_SETTINGS, ...readGoosedumpSettingsFrom(path) };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function writeScopedGoosedumpSetting(
|
|
189
|
+
cwd: string,
|
|
190
|
+
scope: SettingsScope,
|
|
191
|
+
key: GoosedumpSettingKey,
|
|
192
|
+
value: number,
|
|
193
|
+
): void {
|
|
194
|
+
const path = scope === 'global' ? globalSettingsPath() : projectSettingsPath(cwd);
|
|
195
|
+
const root = readJsonObject(path);
|
|
196
|
+
const goosedump = isRecord(root.goosedump) ? root.goosedump : {};
|
|
197
|
+
root.goosedump = { ...goosedump, [key]: value };
|
|
198
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
199
|
+
writeFileSync(path, `${JSON.stringify(root, null, 2)}\n`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function formatCompactSetting(value: number): string {
|
|
203
|
+
return value === 0 ? 'disabled' : String(value);
|
|
204
|
+
}
|
|
205
|
+
|
|
118
206
|
function resolveGoosedumpBinary(): string {
|
|
119
207
|
try {
|
|
120
208
|
return require.resolve('@jarkkojs/goosedump/bin/goosedump.js');
|
|
@@ -181,51 +269,43 @@ function summarizeToolArgs(args: unknown): string {
|
|
|
181
269
|
return clip(JSON.stringify(args), 160);
|
|
182
270
|
}
|
|
183
271
|
|
|
184
|
-
function
|
|
185
|
-
switch (
|
|
186
|
-
case '
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
return '
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
if (m.thinking && m.thinking.length > 0) parts.push(m.thinking.join('\n'));
|
|
202
|
-
if (m.text) parts.push(m.text);
|
|
203
|
-
if (m.toolCalls && m.toolCalls.length > 0) {
|
|
204
|
-
parts.push(
|
|
205
|
-
m.toolCalls
|
|
206
|
-
.map((tc) => {
|
|
207
|
-
const summary = summarizeToolArgs(tc.arguments);
|
|
208
|
-
return summary ? `${tc.name}(${summary})` : tc.name;
|
|
209
|
-
})
|
|
210
|
-
.join('\n'),
|
|
211
|
-
);
|
|
212
|
-
}
|
|
213
|
-
return parts.join('\n');
|
|
272
|
+
function showPartContent(part: ShowPartJson): string {
|
|
273
|
+
switch (part.type) {
|
|
274
|
+
case 'text':
|
|
275
|
+
case 'reasoning':
|
|
276
|
+
return part.text ?? '';
|
|
277
|
+
case 'toolCall': {
|
|
278
|
+
const summary = summarizeToolArgs(part.arguments);
|
|
279
|
+
return summary ? `${part.name ?? 'tool'}(${summary})` : (part.name ?? 'tool');
|
|
280
|
+
}
|
|
281
|
+
case 'toolResult': {
|
|
282
|
+
const name = part.toolName ?? 'tool';
|
|
283
|
+
const header = `${name}${part.isError ? ' ⚠' : ''}`;
|
|
284
|
+
return part.content ? `${header}\n${part.content}` : header;
|
|
285
|
+
}
|
|
286
|
+
case 'image': {
|
|
287
|
+
const header = `[image ${part.mimeType ?? 'unknown'}]`;
|
|
288
|
+
return part.ocrText ? `${header}\n${part.ocrText}` : header;
|
|
214
289
|
}
|
|
215
|
-
case 'toolResult':
|
|
216
|
-
return m.content ?? '';
|
|
217
290
|
case 'bash': {
|
|
218
|
-
const cmd =
|
|
219
|
-
if (cmd &&
|
|
220
|
-
return cmd || (
|
|
291
|
+
const cmd = part.command ? `$ ${part.command}` : '';
|
|
292
|
+
if (cmd && part.output) return `${cmd}\n${part.output}`;
|
|
293
|
+
return cmd || (part.output ?? '');
|
|
294
|
+
}
|
|
295
|
+
case 'passthrough': {
|
|
296
|
+
const kind = part.kind ? `passthrough:${part.kind}` : 'passthrough';
|
|
297
|
+
const raw = part.raw === undefined ? '' : JSON.stringify(part.raw);
|
|
298
|
+
return raw ? `[${kind}] ${raw}` : `[${kind}]`;
|
|
221
299
|
}
|
|
222
|
-
default:
|
|
223
|
-
return m.text ?? '';
|
|
224
300
|
}
|
|
225
301
|
}
|
|
226
302
|
|
|
303
|
+
function showMessageContent(m: ShowMessageJson): string {
|
|
304
|
+
return m.parts.map(showPartContent).filter(Boolean).join('\n');
|
|
305
|
+
}
|
|
306
|
+
|
|
227
307
|
function showMessage(m: ShowMessageJson): GoosedumpMessage {
|
|
228
|
-
return { id: m.entryId, role:
|
|
308
|
+
return { id: m.entryId, role: m.role, content: showMessageContent(m) };
|
|
229
309
|
}
|
|
230
310
|
|
|
231
311
|
function hitMessage(h: HitJson): GoosedumpMessage {
|
|
@@ -269,16 +349,18 @@ function goosedumpList(provider = 'pi'): GoosedumpListing[] {
|
|
|
269
349
|
const entries = JSON.parse(runGoosedump(['list'])) as ListEntryJson[];
|
|
270
350
|
return entries
|
|
271
351
|
.filter((entry) => entry.provider === provider)
|
|
272
|
-
.map((entry) =>
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
352
|
+
.map((entry) => {
|
|
353
|
+
const nativeId = entry.native_id ?? entry.id;
|
|
354
|
+
return {
|
|
355
|
+
id: entry.id,
|
|
356
|
+
provider: entry.provider,
|
|
357
|
+
cwd: entry.cwd,
|
|
358
|
+
parent: entry.parent,
|
|
359
|
+
label: entry.label ?? null,
|
|
360
|
+
// Only pi sessions map back to a local file we can resume.
|
|
361
|
+
path: provider === 'pi' ? piSessionFilePath(nativeId) : null,
|
|
362
|
+
};
|
|
363
|
+
});
|
|
282
364
|
}
|
|
283
365
|
|
|
284
366
|
function piSessionsDir(): string {
|
|
@@ -475,7 +557,7 @@ function currentSessionContextId(ctx: ExtensionContext): string | null {
|
|
|
475
557
|
const sessionFile = ctx.sessionManager.getSessionFile();
|
|
476
558
|
if (!sessionFile) return null;
|
|
477
559
|
const nativeId = sessionFileNativeId(sessionFile);
|
|
478
|
-
const listing = goosedumpList('pi').find((entry) => entry.
|
|
560
|
+
const listing = goosedumpList('pi').find((entry) => entry.id === nativeId);
|
|
479
561
|
return listing ? listing.id : null;
|
|
480
562
|
}
|
|
481
563
|
|
|
@@ -663,67 +745,119 @@ function buildGoosedumpCompaction(
|
|
|
663
745
|
};
|
|
664
746
|
}
|
|
665
747
|
|
|
748
|
+
function isControlChar(char: string): boolean {
|
|
749
|
+
const code = char.charCodeAt(0);
|
|
750
|
+
return code < 32 || code === 0x7f || (code >= 0x80 && code <= 0x9f);
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
class NumberInputSubmenu implements Component {
|
|
754
|
+
private readonly input = new Input();
|
|
755
|
+
private readonly hint: (text: string) => string;
|
|
756
|
+
private readonly onSubmitValue: (value: string) => void;
|
|
757
|
+
private readonly onCancelEdit: () => void;
|
|
758
|
+
private readonly title: string;
|
|
759
|
+
|
|
760
|
+
constructor(
|
|
761
|
+
title: string,
|
|
762
|
+
initialValue: string,
|
|
763
|
+
onSubmit: (value: string) => void,
|
|
764
|
+
onCancel: () => void,
|
|
765
|
+
) {
|
|
766
|
+
this.title = title;
|
|
767
|
+
this.onSubmitValue = onSubmit;
|
|
768
|
+
this.onCancelEdit = onCancel;
|
|
769
|
+
this.input.setValue(initialValue);
|
|
770
|
+
this.hint = getSettingsListTheme().hint;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
handleInput(data: string): void {
|
|
774
|
+
const kb = getKeybindings();
|
|
775
|
+
if (kb.matches(data, 'tui.select.confirm') || data === '\n') {
|
|
776
|
+
const digits = this.input.getValue().replace(/\D/g, '');
|
|
777
|
+
const parsed = Number(digits);
|
|
778
|
+
this.onSubmitValue(digits !== '' && Number.isSafeInteger(parsed) ? String(parsed) : '0');
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
if (kb.matches(data, 'tui.select.cancel')) {
|
|
782
|
+
this.onCancelEdit();
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
const kittyChar = decodeKittyPrintable(data);
|
|
786
|
+
const printable = kittyChar ?? (data.length === 1 && !isControlChar(data) ? data : undefined);
|
|
787
|
+
if (printable !== undefined && !/^[0-9]$/.test(printable)) return;
|
|
788
|
+
this.input.handleInput(data);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
invalidate(): void {
|
|
792
|
+
this.input.invalidate();
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
render(width: number): string[] {
|
|
796
|
+
return [
|
|
797
|
+
this.hint(` ${this.title}`),
|
|
798
|
+
'',
|
|
799
|
+
...this.input.render(width),
|
|
800
|
+
'',
|
|
801
|
+
this.hint(' enter submit · esc cancel · digits only'),
|
|
802
|
+
];
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
|
|
666
806
|
async function runSessionBrowser(
|
|
667
807
|
ctx: ExtensionContext,
|
|
668
808
|
listings: GoosedumpListing[],
|
|
669
809
|
): Promise<string | null | undefined> {
|
|
670
810
|
let resumePath: string | null | undefined;
|
|
671
811
|
|
|
672
|
-
const OVERLAY_WIDTH =
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
if (l.parent !== null) {
|
|
680
|
-
const siblings = childrenMap.get(l.parent) ?? [];
|
|
681
|
-
siblings.push(l);
|
|
682
|
-
childrenMap.set(l.parent, siblings);
|
|
683
|
-
}
|
|
812
|
+
const OVERLAY_WIDTH = 84;
|
|
813
|
+
const LIST_VIEWPORT = 18;
|
|
814
|
+
const COMPACT_VIEWPORT = 18;
|
|
815
|
+
const location = ctx.cwd;
|
|
816
|
+
|
|
817
|
+
function locationListings(): GoosedumpListing[] {
|
|
818
|
+
return listings.filter((listing) => listing.cwd === location);
|
|
684
819
|
}
|
|
685
820
|
|
|
686
|
-
function
|
|
687
|
-
|
|
688
|
-
roots = listings.filter((l) => l.parent === null || !listingIds.has(l.parent));
|
|
689
|
-
childrenMap = new Map<string, GoosedumpListing[]>();
|
|
690
|
-
for (const l of listings) {
|
|
691
|
-
if (l.parent !== null) {
|
|
692
|
-
const siblings = childrenMap.get(l.parent) ?? [];
|
|
693
|
-
siblings.push(l);
|
|
694
|
-
childrenMap.set(l.parent, siblings);
|
|
695
|
-
}
|
|
696
|
-
}
|
|
821
|
+
function refreshListings(): void {
|
|
822
|
+
listings.splice(0, listings.length, ...goosedumpList());
|
|
697
823
|
}
|
|
698
824
|
|
|
699
|
-
function
|
|
700
|
-
if (
|
|
825
|
+
function shortPath(path: string, maxLen: number): string {
|
|
826
|
+
if (path.length <= maxLen) return path;
|
|
701
827
|
const head = Math.floor(maxLen / 2) + 2;
|
|
702
828
|
const tail = maxLen - head - 3;
|
|
703
|
-
if (tail < 4) return
|
|
704
|
-
return
|
|
829
|
+
if (tail < 4) return `${path.slice(0, maxLen - 3)}...`;
|
|
830
|
+
return `${path.slice(0, head)}...${path.slice(path.length - tail)}`;
|
|
705
831
|
}
|
|
706
832
|
|
|
707
|
-
function
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
|
|
833
|
+
function settingLabel(scope: SettingsScope, key: GoosedumpSettingKey): string {
|
|
834
|
+
const scopeLabel = scope === 'global' ? 'Global' : 'Project';
|
|
835
|
+
return `${scopeLabel} ${key}`;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
function buildSettingItems(): SettingItem[] {
|
|
839
|
+
const global = readScopedGoosedumpSettings(location, 'global');
|
|
840
|
+
const project = readScopedGoosedumpSettings(location, 'project');
|
|
841
|
+
const settingsByScope = { global, project } satisfies Record<SettingsScope, GoosedumpSettings>;
|
|
842
|
+
|
|
843
|
+
return (['global', 'project'] as const).map((scope) => ({
|
|
844
|
+
id: `${scope}:turnsBeforeCompact`,
|
|
845
|
+
label: settingLabel(scope, 'turnsBeforeCompact'),
|
|
846
|
+
description: 'LLM turns before goosedump starts compaction; 0 disables this trigger.',
|
|
847
|
+
currentValue: String(settingsByScope[scope].turnsBeforeCompact),
|
|
848
|
+
submenu: (currentValue, done) =>
|
|
849
|
+
new NumberInputSubmenu(
|
|
850
|
+
settingLabel(scope, 'turnsBeforeCompact'),
|
|
851
|
+
currentValue,
|
|
852
|
+
(value) => done(value),
|
|
853
|
+
() => done(),
|
|
854
|
+
),
|
|
855
|
+
}));
|
|
721
856
|
}
|
|
722
857
|
|
|
723
858
|
await ctx.ui.custom<void>(
|
|
724
859
|
(tui, theme, _kb, done) => {
|
|
725
|
-
let
|
|
726
|
-
let displayList = flattenTree(roots, expandedNodes, 0);
|
|
860
|
+
let tab: 'sessions' | 'settings' = 'sessions';
|
|
727
861
|
let selectedIndex = 0;
|
|
728
862
|
let listScroll = 0;
|
|
729
863
|
let mode: 'list' | 'compact' = 'list';
|
|
@@ -731,26 +865,44 @@ async function runSessionBrowser(
|
|
|
731
865
|
let compactScroll = 0;
|
|
732
866
|
let compactTitle = '';
|
|
733
867
|
let pendingDeletionId: string | null = null;
|
|
868
|
+
let settingsList: SettingsList;
|
|
734
869
|
|
|
735
|
-
const COMPACT_VIEWPORT = 20;
|
|
736
|
-
const LIST_VIEWPORT = 20;
|
|
737
870
|
const wrapWidth = OVERLAY_WIDTH - 4;
|
|
738
871
|
|
|
739
|
-
function
|
|
740
|
-
|
|
872
|
+
function resetSelection(): void {
|
|
873
|
+
const count = locationListings().length;
|
|
874
|
+
selectedIndex = Math.min(selectedIndex, Math.max(0, count - 1));
|
|
875
|
+
listScroll = Math.min(listScroll, Math.max(0, count - LIST_VIEWPORT));
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
function rebuildSettingsList(): void {
|
|
879
|
+
settingsList = new SettingsList(
|
|
880
|
+
buildSettingItems(),
|
|
881
|
+
10,
|
|
882
|
+
getSettingsListTheme(),
|
|
883
|
+
(id, newValue) => {
|
|
884
|
+
const [scope, key] = id.split(':') as [SettingsScope, GoosedumpSettingKey];
|
|
885
|
+
const parsed = Number(newValue);
|
|
886
|
+
if (!Number.isSafeInteger(parsed) || parsed < 0) return;
|
|
887
|
+
writeScopedGoosedumpSetting(location, scope, key, parsed);
|
|
888
|
+
settingsList.updateValue(id, newValue);
|
|
889
|
+
ctx.ui.notify(`${settingLabel(scope, key)} = ${formatCompactSetting(parsed)}`, 'info');
|
|
890
|
+
tui.requestRender();
|
|
891
|
+
},
|
|
892
|
+
() => {
|
|
893
|
+
done(undefined);
|
|
894
|
+
},
|
|
895
|
+
);
|
|
741
896
|
}
|
|
742
897
|
|
|
898
|
+
rebuildSettingsList();
|
|
899
|
+
|
|
743
900
|
function executeDeletion(): void {
|
|
744
901
|
try {
|
|
745
902
|
const id = pendingDeletionId!;
|
|
746
903
|
goosedumpDelete(id);
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
rebuildTree();
|
|
750
|
-
rebuildDisplayList();
|
|
751
|
-
if (selectedIndex >= displayList.length) {
|
|
752
|
-
selectedIndex = Math.max(0, displayList.length - 1);
|
|
753
|
-
}
|
|
904
|
+
refreshListings();
|
|
905
|
+
resetSelection();
|
|
754
906
|
pendingDeletionId = null;
|
|
755
907
|
} catch (err) {
|
|
756
908
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -760,6 +912,52 @@ async function runSessionBrowser(
|
|
|
760
912
|
tui.requestRender();
|
|
761
913
|
}
|
|
762
914
|
|
|
915
|
+
function makeRow(content: string, innerW: number, border: string): string {
|
|
916
|
+
const line = truncateToWidth(content, innerW);
|
|
917
|
+
const pad = Math.max(0, innerW - visibleWidth(line));
|
|
918
|
+
return `${border} ${line}${' '.repeat(pad)} ${border}`;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
function renderTabs(innerW: number, border: string): string {
|
|
922
|
+
const sessions = tab === 'sessions' ? theme.fg('accent', '[Sessions]') : '[Sessions]';
|
|
923
|
+
const settings = tab === 'settings' ? theme.fg('accent', '[Settings]') : '[Settings]';
|
|
924
|
+
return makeRow(`${sessions} ${settings}`, innerW, border);
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
function renderSettings(width: number, innerW: number, border: string): string[] {
|
|
928
|
+
const borderFg = (s: string) => theme.fg('border', s);
|
|
929
|
+
const accent = (s: string) => theme.fg('accent', s);
|
|
930
|
+
const dim = (s: string) => theme.fg('dim', s);
|
|
931
|
+
const lines: string[] = [];
|
|
932
|
+
const title = accent(' Goosedump Dashboard ');
|
|
933
|
+
const topFill = borderFg('─'.repeat(Math.max(0, width - 4 - visibleWidth(title))));
|
|
934
|
+
lines.push(`${borderFg('╭─')}${title}${topFill}${borderFg('─╮')}`);
|
|
935
|
+
lines.push(renderTabs(innerW, border));
|
|
936
|
+
lines.push(
|
|
937
|
+
makeRow(
|
|
938
|
+
dim('Settings are written to ~/.pi/agent/settings.json and .pi/settings.json.'),
|
|
939
|
+
innerW,
|
|
940
|
+
border,
|
|
941
|
+
),
|
|
942
|
+
);
|
|
943
|
+
lines.push(makeRow('', innerW, border));
|
|
944
|
+
for (const line of settingsList.render(innerW)) lines.push(makeRow(line, innerW, border));
|
|
945
|
+
lines.push(makeRow('', innerW, border));
|
|
946
|
+
const effective = resolveGoosedumpSettings(location);
|
|
947
|
+
lines.push(
|
|
948
|
+
makeRow(
|
|
949
|
+
dim(`Effective: turns=${formatCompactSetting(effective.turnsBeforeCompact)}`),
|
|
950
|
+
innerW,
|
|
951
|
+
border,
|
|
952
|
+
),
|
|
953
|
+
);
|
|
954
|
+
lines.push(
|
|
955
|
+
makeRow(dim('tab switch ↑↓ select enter/space edit esc close'), innerW, border),
|
|
956
|
+
);
|
|
957
|
+
lines.push(`${borderFg('╰')}${borderFg('─'.repeat(width - 2))}${borderFg('╯')}`);
|
|
958
|
+
return lines;
|
|
959
|
+
}
|
|
960
|
+
|
|
763
961
|
return {
|
|
764
962
|
render(width: number): string[] {
|
|
765
963
|
const innerW = width - 4;
|
|
@@ -770,21 +968,17 @@ async function runSessionBrowser(
|
|
|
770
968
|
const text = (s: string) => theme.fg('text', s);
|
|
771
969
|
const muted = (s: string) => theme.fg('muted', s);
|
|
772
970
|
|
|
773
|
-
const lines: string[] = [];
|
|
774
|
-
|
|
775
971
|
if (mode === 'compact') {
|
|
776
|
-
const
|
|
972
|
+
const lines: string[] = [];
|
|
973
|
+
const title = accent(` Compact: ${truncateToWidth(compactTitle, 48)} `);
|
|
777
974
|
const topFill = borderFg('─'.repeat(Math.max(0, width - 4 - visibleWidth(title))));
|
|
778
975
|
lines.push(`${borderFg('╭─')}${title}${topFill}${borderFg('─╮')}`);
|
|
779
976
|
|
|
780
977
|
const total = compactLines.length;
|
|
781
978
|
const window = compactLines.slice(compactScroll, compactScroll + COMPACT_VIEWPORT);
|
|
782
|
-
for (const row of window)
|
|
783
|
-
|
|
784
|
-
}
|
|
785
|
-
for (let i = window.length; i < COMPACT_VIEWPORT; i++) {
|
|
979
|
+
for (const row of window) lines.push(makeRow(text(row), innerW, border));
|
|
980
|
+
for (let i = window.length; i < COMPACT_VIEWPORT; i++)
|
|
786
981
|
lines.push(makeRow('', innerW, border));
|
|
787
|
-
}
|
|
788
982
|
|
|
789
983
|
const position =
|
|
790
984
|
total > COMPACT_VIEWPORT
|
|
@@ -796,85 +990,77 @@ async function runSessionBrowser(
|
|
|
796
990
|
return lines;
|
|
797
991
|
}
|
|
798
992
|
|
|
799
|
-
|
|
800
|
-
|
|
993
|
+
if (tab === 'settings') return renderSettings(width, innerW, border);
|
|
994
|
+
|
|
995
|
+
const currentListings = locationListings();
|
|
996
|
+
const lines: string[] = [];
|
|
997
|
+
const title = accent(' Goosedump Dashboard ');
|
|
801
998
|
const topFill = borderFg('─'.repeat(Math.max(0, width - 4 - visibleWidth(title))));
|
|
802
999
|
lines.push(`${borderFg('╭─')}${title}${topFill}${borderFg('─╮')}`);
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
1000
|
+
lines.push(renderTabs(innerW, border));
|
|
1001
|
+
lines.push(
|
|
1002
|
+
makeRow(muted(`Location: ${shortPath(location, innerW - 10)}`), innerW, border),
|
|
1003
|
+
);
|
|
1004
|
+
lines.push(
|
|
1005
|
+
makeRow(
|
|
1006
|
+
muted(
|
|
1007
|
+
`${currentListings.length} session${currentListings.length === 1 ? '' : 's'} here`,
|
|
1008
|
+
),
|
|
1009
|
+
innerW,
|
|
1010
|
+
border,
|
|
1011
|
+
),
|
|
1012
|
+
);
|
|
808
1013
|
lines.push(makeRow('', innerW, border));
|
|
809
1014
|
|
|
810
|
-
|
|
811
|
-
const total = displayList.length;
|
|
812
|
-
const window = displayList.slice(listScroll, listScroll + LIST_VIEWPORT);
|
|
1015
|
+
const window = currentListings.slice(listScroll, listScroll + LIST_VIEWPORT);
|
|
813
1016
|
for (let i = 0; i < window.length; i++) {
|
|
814
1017
|
const absolute = listScroll + i;
|
|
815
|
-
const
|
|
1018
|
+
const listing = window[i];
|
|
816
1019
|
const isSelected = absolute === selectedIndex;
|
|
817
|
-
const deletePending =
|
|
818
|
-
|
|
819
|
-
const cursor = deletePending ? dim('␡ ') : isSelected ? accent('▶') : ' ';
|
|
820
|
-
const indent = ' '.repeat(entry.depth * 2);
|
|
821
|
-
const toggle = entry.hasChildren
|
|
822
|
-
? expandedNodes.has(entry.listing.id)
|
|
823
|
-
? muted('−')
|
|
824
|
-
: muted('+')
|
|
825
|
-
: ' ';
|
|
1020
|
+
const deletePending = pendingDeletionId !== null && listing.id === pendingDeletionId;
|
|
1021
|
+
const cursor = deletePending ? dim('␡') : isSelected ? accent('▶') : ' ';
|
|
826
1022
|
const idText = deletePending
|
|
827
|
-
? dim(
|
|
828
|
-
: isSelected
|
|
829
|
-
? accent(entry.listing.id)
|
|
830
|
-
: text(entry.listing.id);
|
|
831
|
-
|
|
832
|
-
// Prefix: " ▶" (3) or "␡ " (3) + indent + toggle + " " + id (8) + " "
|
|
833
|
-
const prefixLen = 3 + entry.depth * 2 + 1 + 1 + 8 + 1;
|
|
834
|
-
const cwdMax = Math.max(8, innerW - prefixLen);
|
|
835
|
-
const cwdTruncated = truncateCwd(entry.listing.cwd, cwdMax);
|
|
836
|
-
const cwdText = deletePending
|
|
837
|
-
? muted(cwdTruncated)
|
|
1023
|
+
? dim(listing.id)
|
|
838
1024
|
: isSelected
|
|
839
|
-
? accent(
|
|
840
|
-
:
|
|
841
|
-
|
|
842
|
-
const
|
|
843
|
-
|
|
1025
|
+
? accent(listing.id)
|
|
1026
|
+
: text(listing.id);
|
|
1027
|
+
const label = listing.label ? ` ${listing.label}` : '';
|
|
1028
|
+
const count = muted(
|
|
1029
|
+
` (${listing.provider}, ${listing.path ? 'resumable' : 'no file'})`,
|
|
1030
|
+
);
|
|
1031
|
+
lines.push(makeRow(` ${cursor} ${idText}${muted(label)}${count}`, innerW, border));
|
|
844
1032
|
}
|
|
845
1033
|
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
1034
|
+
for (let i = window.length; i < LIST_VIEWPORT; i++)
|
|
1035
|
+
lines.push(makeRow('', innerW, border));
|
|
1036
|
+
|
|
1037
|
+
if (currentListings.length > LIST_VIEWPORT) {
|
|
1038
|
+
const position = `${listScroll + 1}-${Math.min(listScroll + LIST_VIEWPORT, currentListings.length)} of ${currentListings.length}`;
|
|
1039
|
+
lines.push(makeRow(dim(`(${position})`), innerW, border));
|
|
849
1040
|
}
|
|
850
1041
|
|
|
851
|
-
// Deletion confirmation
|
|
852
1042
|
if (pendingDeletionId) {
|
|
853
1043
|
lines.push(makeRow('', innerW, border));
|
|
854
1044
|
lines.push(
|
|
855
1045
|
makeRow(
|
|
856
|
-
dim(`
|
|
1046
|
+
dim(`Delete session ${pendingDeletionId}? enter=confirm esc=cancel`),
|
|
857
1047
|
innerW,
|
|
858
1048
|
border,
|
|
859
1049
|
),
|
|
860
1050
|
);
|
|
861
1051
|
}
|
|
862
1052
|
|
|
863
|
-
// Footer
|
|
864
1053
|
lines.push(makeRow('', innerW, border));
|
|
865
1054
|
lines.push(
|
|
866
1055
|
makeRow(
|
|
867
1056
|
dim(
|
|
868
|
-
'↑↓ navigate
|
|
1057
|
+
'tab settings ↑↓ navigate enter compact shift+enter resume ctrl+d delete esc close',
|
|
869
1058
|
),
|
|
870
1059
|
innerW,
|
|
871
1060
|
border,
|
|
872
1061
|
),
|
|
873
1062
|
);
|
|
874
|
-
|
|
875
|
-
// Bottom border
|
|
876
1063
|
lines.push(`${borderFg('╰')}${borderFg('─'.repeat(width - 2))}${borderFg('╯')}`);
|
|
877
|
-
|
|
878
1064
|
return lines;
|
|
879
1065
|
},
|
|
880
1066
|
|
|
@@ -909,11 +1095,25 @@ async function runSessionBrowser(
|
|
|
909
1095
|
return;
|
|
910
1096
|
}
|
|
911
1097
|
|
|
1098
|
+
if (matchesKey(data, Key.tab) || matchesKey(data, Key.shift(Key.tab))) {
|
|
1099
|
+
tab = tab === 'sessions' ? 'settings' : 'sessions';
|
|
1100
|
+
if (tab === 'settings') rebuildSettingsList();
|
|
1101
|
+
tui.requestRender();
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
if (tab === 'settings') {
|
|
1106
|
+
settingsList.handleInput(data);
|
|
1107
|
+
tui.requestRender();
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
|
|
912
1111
|
if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl('c'))) {
|
|
913
1112
|
done(undefined);
|
|
914
1113
|
return;
|
|
915
1114
|
}
|
|
916
1115
|
|
|
1116
|
+
const currentListings = locationListings();
|
|
917
1117
|
if (matchesKey(data, Key.up)) {
|
|
918
1118
|
selectedIndex = Math.max(0, selectedIndex - 1);
|
|
919
1119
|
if (selectedIndex < listScroll) listScroll = selectedIndex;
|
|
@@ -922,7 +1122,7 @@ async function runSessionBrowser(
|
|
|
922
1122
|
}
|
|
923
1123
|
|
|
924
1124
|
if (matchesKey(data, Key.down)) {
|
|
925
|
-
selectedIndex = Math.min(
|
|
1125
|
+
selectedIndex = Math.min(Math.max(0, currentListings.length - 1), selectedIndex + 1);
|
|
926
1126
|
if (selectedIndex >= listScroll + LIST_VIEWPORT) {
|
|
927
1127
|
listScroll = selectedIndex - LIST_VIEWPORT + 1;
|
|
928
1128
|
}
|
|
@@ -930,7 +1130,6 @@ async function runSessionBrowser(
|
|
|
930
1130
|
return;
|
|
931
1131
|
}
|
|
932
1132
|
|
|
933
|
-
// Deletion confirmation: enter confirms, esc cancels
|
|
934
1133
|
if (pendingDeletionId) {
|
|
935
1134
|
if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl('c'))) {
|
|
936
1135
|
pendingDeletionId = null;
|
|
@@ -944,47 +1143,21 @@ async function runSessionBrowser(
|
|
|
944
1143
|
return;
|
|
945
1144
|
}
|
|
946
1145
|
|
|
947
|
-
// Ctrl+D marks session for deletion
|
|
948
1146
|
if (matchesKey(data, Key.ctrl('d'))) {
|
|
949
|
-
const listing =
|
|
1147
|
+
const listing = currentListings[selectedIndex];
|
|
950
1148
|
if (listing) pendingDeletionId = listing.id;
|
|
951
1149
|
tui.requestRender();
|
|
952
1150
|
return;
|
|
953
1151
|
}
|
|
954
1152
|
|
|
955
|
-
// Expand/collapse: space, +, or - key
|
|
956
|
-
const entry = displayList[selectedIndex];
|
|
957
|
-
if (entry && entry.hasChildren && (data === ' ' || data === '+' || data === '-')) {
|
|
958
|
-
const id = entry.listing.id;
|
|
959
|
-
if (data === '+') {
|
|
960
|
-
expandedNodes.add(id);
|
|
961
|
-
} else if (data === '-') {
|
|
962
|
-
expandedNodes.delete(id);
|
|
963
|
-
} else {
|
|
964
|
-
// space = toggle
|
|
965
|
-
if (expandedNodes.has(id)) {
|
|
966
|
-
expandedNodes.delete(id);
|
|
967
|
-
} else {
|
|
968
|
-
expandedNodes.add(id);
|
|
969
|
-
}
|
|
970
|
-
}
|
|
971
|
-
rebuildDisplayList();
|
|
972
|
-
// Keep selection on the same entry
|
|
973
|
-
const newIndex = displayList.findIndex((d) => d.listing.id === id);
|
|
974
|
-
if (newIndex >= 0) selectedIndex = newIndex;
|
|
975
|
-
if (selectedIndex < listScroll) listScroll = selectedIndex;
|
|
976
|
-
tui.requestRender();
|
|
977
|
-
return;
|
|
978
|
-
}
|
|
979
|
-
|
|
980
1153
|
if (matchesKey(data, Key.shift(Key.enter))) {
|
|
981
|
-
resumePath =
|
|
1154
|
+
resumePath = currentListings[selectedIndex]?.path;
|
|
982
1155
|
done(undefined);
|
|
983
1156
|
return;
|
|
984
1157
|
}
|
|
985
1158
|
|
|
986
1159
|
if (matchesKey(data, Key.enter) || matchesKey(data, Key.return)) {
|
|
987
|
-
const listing =
|
|
1160
|
+
const listing = currentListings[selectedIndex];
|
|
988
1161
|
if (!listing) return;
|
|
989
1162
|
compactTitle = listing.id;
|
|
990
1163
|
compactScroll = 0;
|
|
@@ -999,18 +1172,11 @@ async function runSessionBrowser(
|
|
|
999
1172
|
}
|
|
1000
1173
|
mode = 'compact';
|
|
1001
1174
|
tui.requestRender();
|
|
1002
|
-
return;
|
|
1003
1175
|
}
|
|
1004
1176
|
},
|
|
1005
1177
|
|
|
1006
1178
|
invalidate(): void {},
|
|
1007
1179
|
};
|
|
1008
|
-
|
|
1009
|
-
function makeRow(content: string, innerW: number, border: string): string {
|
|
1010
|
-
const line = truncateToWidth(content, innerW);
|
|
1011
|
-
const pad = Math.max(0, innerW - visibleWidth(line));
|
|
1012
|
-
return `${border} ${line}${' '.repeat(pad)} ${border}`;
|
|
1013
|
-
}
|
|
1014
1180
|
},
|
|
1015
1181
|
{
|
|
1016
1182
|
overlay: true,
|
|
@@ -1032,6 +1198,56 @@ export function createGoosedumpIntegration(
|
|
|
1032
1198
|
const shouldOverrideDefaultCompaction = options.overrideDefaultCompaction ?? true;
|
|
1033
1199
|
|
|
1034
1200
|
let goosedumpAvailable = false;
|
|
1201
|
+
let compactSettings = DEFAULT_GOOSEDUMP_SETTINGS;
|
|
1202
|
+
let settingsSignature = '';
|
|
1203
|
+
let turnsRemaining = 0;
|
|
1204
|
+
let counterTriggered = false;
|
|
1205
|
+
let compactionQueued = false;
|
|
1206
|
+
|
|
1207
|
+
function compactSettingsSignature(settings: GoosedumpSettings): string {
|
|
1208
|
+
return String(settings.turnsBeforeCompact);
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
function resetCompactCounters(cwd: string): void {
|
|
1212
|
+
compactSettings = resolveGoosedumpSettings(cwd);
|
|
1213
|
+
settingsSignature = compactSettingsSignature(compactSettings);
|
|
1214
|
+
turnsRemaining = compactSettings.turnsBeforeCompact;
|
|
1215
|
+
counterTriggered = false;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
function reloadCompactSettings(cwd: string): void {
|
|
1219
|
+
const next = resolveGoosedumpSettings(cwd);
|
|
1220
|
+
const nextSignature = compactSettingsSignature(next);
|
|
1221
|
+
if (nextSignature !== settingsSignature) resetCompactCounters(cwd);
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
function noteCompletedTurn(ctx: ExtensionContext): void {
|
|
1225
|
+
if (!goosedumpAvailable || !shouldOverrideDefaultCompaction) return;
|
|
1226
|
+
reloadCompactSettings(ctx.cwd);
|
|
1227
|
+
if (compactSettings.turnsBeforeCompact > 0) {
|
|
1228
|
+
turnsRemaining -= 1;
|
|
1229
|
+
if (turnsRemaining <= 0) counterTriggered = true;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
if (!counterTriggered || compactionQueued) return;
|
|
1233
|
+
// ctx.compact() uses manual compaction semantics and aborts the current run.
|
|
1234
|
+
// Wait until no queued work remains before triggering plugin-driven compaction.
|
|
1235
|
+
if (ctx.hasPendingMessages()) return;
|
|
1236
|
+
|
|
1237
|
+
resetCompactCounters(ctx.cwd);
|
|
1238
|
+
compactionQueued = true;
|
|
1239
|
+
ctx.compact({
|
|
1240
|
+
onComplete: () => {
|
|
1241
|
+
compactionQueued = false;
|
|
1242
|
+
resetCompactCounters(ctx.cwd);
|
|
1243
|
+
},
|
|
1244
|
+
onError: (error) => {
|
|
1245
|
+
compactionQueued = false;
|
|
1246
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1247
|
+
if (ctx.hasUI) ctx.ui.notify(`goosedump compact failed: ${message}`, 'error');
|
|
1248
|
+
},
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1035
1251
|
|
|
1036
1252
|
function checkGoosedump(ctx?: ExtensionContext): boolean {
|
|
1037
1253
|
const version = goosedumpVersion();
|
|
@@ -1323,6 +1539,7 @@ export function createGoosedumpIntegration(
|
|
|
1323
1539
|
|
|
1324
1540
|
pi.on('session_start', async (_event, ctx) => {
|
|
1325
1541
|
goosedumpAvailable = checkGoosedump(ctx);
|
|
1542
|
+
resetCompactCounters(ctx.cwd);
|
|
1326
1543
|
|
|
1327
1544
|
if (goosedumpAvailable) {
|
|
1328
1545
|
const theme = ctx.ui.theme;
|
|
@@ -1332,6 +1549,10 @@ export function createGoosedumpIntegration(
|
|
|
1332
1549
|
}
|
|
1333
1550
|
});
|
|
1334
1551
|
|
|
1552
|
+
pi.on('turn_end', async (_event, ctx) => {
|
|
1553
|
+
noteCompletedTurn(ctx);
|
|
1554
|
+
});
|
|
1555
|
+
|
|
1335
1556
|
pi.on('session_before_compact', async (event, ctx) => {
|
|
1336
1557
|
if (!shouldOverrideDefaultCompaction || !goosedumpAvailable) return;
|
|
1337
1558
|
if (event.customInstructions?.trim()) return;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goosedump",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Coding agent context data browser plugin for pi",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"goosedump",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
31
|
"@earendil-works/pi-tui": "^0.78.0",
|
|
32
|
-
"@jarkkojs/goosedump": "^0.
|
|
32
|
+
"@jarkkojs/goosedump": "^0.8.2",
|
|
33
33
|
"@sinclair/typebox": "^0.34.49"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|