pi-goosedump 0.1.4 → 0.2.2
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 +14 -7
- package/index.ts +238 -187
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -20,19 +20,19 @@ Once installed, pi-goosedump registers a tool and a slash command:
|
|
|
20
20
|
|
|
21
21
|
The agent can browse session history with:
|
|
22
22
|
|
|
23
|
-
| Action | Description
|
|
24
|
-
| -------- |
|
|
25
|
-
| `list` | List all available sessions
|
|
26
|
-
| `search` |
|
|
27
|
-
| `expand` | Show full content for specific entry IDs
|
|
28
|
-
| `view` | View the full session transcript
|
|
23
|
+
| Action | Description |
|
|
24
|
+
| -------- | ---------------------------------------- |
|
|
25
|
+
| `list` | List all available sessions |
|
|
26
|
+
| `search` | Rank messages by query relevance |
|
|
27
|
+
| `expand` | Show full content for specific entry IDs |
|
|
28
|
+
| `view` | View the full session transcript |
|
|
29
29
|
|
|
30
30
|
Examples:
|
|
31
31
|
|
|
32
32
|
```
|
|
33
33
|
goosedump({ action: "list" })
|
|
34
34
|
goosedump({ action: "search", contextId: "abc123", query: "bug fix" })
|
|
35
|
-
goosedump({ action: "expand", contextId: "abc123", ids: [
|
|
35
|
+
goosedump({ action: "expand", contextId: "abc123", ids: ["entry-a", "entry-b"] })
|
|
36
36
|
goosedump({ action: "view", contextId: "abc123" })
|
|
37
37
|
```
|
|
38
38
|
|
|
@@ -40,6 +40,13 @@ goosedump({ action: "view", contextId: "abc123" })
|
|
|
40
40
|
|
|
41
41
|
Opens an interactive session browser.
|
|
42
42
|
|
|
43
|
+
### Compaction
|
|
44
|
+
|
|
45
|
+
pi-goosedump hooks Pi's `/compact` and auto-compaction flow. It uses
|
|
46
|
+
`goosedump compact pi <session>` with Pi's compaction range (`--from`,
|
|
47
|
+
`--until`, and `--scope`) so the generated summary matches the entries Pi is
|
|
48
|
+
about to replace.
|
|
49
|
+
|
|
43
50
|
## License
|
|
44
51
|
|
|
45
52
|
`pi-goosedump` is licensed under `MIT`. See [LICENSE](LICENSE) for more
|
package/index.ts
CHANGED
|
@@ -3,21 +3,27 @@
|
|
|
3
3
|
|
|
4
4
|
import type {
|
|
5
5
|
AgentToolResult,
|
|
6
|
+
CompactionEntry,
|
|
7
|
+
CompactionResult,
|
|
6
8
|
ExtensionAPI,
|
|
7
9
|
ExtensionContext,
|
|
10
|
+
SessionEntry,
|
|
8
11
|
} from '@earendil-works/pi-coding-agent';
|
|
9
12
|
|
|
10
13
|
import { defineTool } from '@earendil-works/pi-coding-agent';
|
|
11
14
|
|
|
12
15
|
import { execFileSync } from 'node:child_process';
|
|
13
|
-
import { existsSync } from 'node:fs';
|
|
16
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
17
|
+
import { createRequire } from 'node:module';
|
|
14
18
|
import { homedir } from 'node:os';
|
|
15
|
-
import { join } from 'node:path';
|
|
19
|
+
import { basename, join } from 'node:path';
|
|
16
20
|
|
|
17
21
|
import { Key, matchesKey, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';
|
|
18
22
|
import { Type } from '@sinclair/typebox';
|
|
19
23
|
|
|
20
|
-
const
|
|
24
|
+
const require = createRequire(import.meta.url);
|
|
25
|
+
|
|
26
|
+
const GOOSEDUMP_VERSION = [0, 2, 2] as const;
|
|
21
27
|
|
|
22
28
|
interface GoosedumpListing {
|
|
23
29
|
id: string;
|
|
@@ -160,10 +166,26 @@ function formatMessagesFull(messages: GoosedumpMessage[]): string {
|
|
|
160
166
|
return messages.map((m) => `[${m.id}] ${m.role}:\n${m.content}`).join('\n\n---\n\n');
|
|
161
167
|
}
|
|
162
168
|
|
|
163
|
-
function
|
|
164
|
-
|
|
169
|
+
function runGoosedump(args: string[]): string {
|
|
170
|
+
return execFileSync(process.execPath, [binaryPath(), ...args], {
|
|
165
171
|
encoding: 'utf-8',
|
|
166
172
|
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function parsePageInfo(output: string, fallbackPage: number): { page: number; totalPages: number } {
|
|
176
|
+
const match = output.match(/\(page\s+(\d+)\s+of\s+(\d+)\):/i);
|
|
177
|
+
if (!match) {
|
|
178
|
+
return { page: fallbackPage, totalPages: 1 };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
page: Number(match[1]),
|
|
183
|
+
totalPages: Number(match[2]),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function goosedumpList(): GoosedumpListing[] {
|
|
188
|
+
const output = runGoosedump(['list', 'pi']);
|
|
167
189
|
return parseListings(output);
|
|
168
190
|
}
|
|
169
191
|
|
|
@@ -178,37 +200,34 @@ function goosedumpContext(
|
|
|
178
200
|
page?: number;
|
|
179
201
|
} = {},
|
|
180
202
|
): GoosedumpContextResult {
|
|
181
|
-
const
|
|
203
|
+
const scope = options.scope ?? 'lineage';
|
|
204
|
+
const hasQuery = typeof options.grep === 'string';
|
|
205
|
+
const args = hasQuery
|
|
206
|
+
? options.rank === false
|
|
207
|
+
? ['grep', 'pi', contextId, options.grep ?? '']
|
|
208
|
+
: ['search', 'pi', contextId, options.grep ?? '']
|
|
209
|
+
: ['show', 'pi', contextId];
|
|
182
210
|
|
|
183
|
-
if (options.scope && options.scope !== 'lineage') {
|
|
184
|
-
args.push('--scope', options.scope);
|
|
185
|
-
}
|
|
186
|
-
if (options.grep) {
|
|
187
|
-
args.push('--grep', options.grep);
|
|
188
|
-
}
|
|
189
211
|
if (options.compact) {
|
|
190
|
-
args.push('--
|
|
212
|
+
args.push('--clip');
|
|
191
213
|
}
|
|
192
214
|
if (options.ids) {
|
|
193
215
|
args.push('--ids', options.ids);
|
|
194
216
|
}
|
|
195
|
-
if (options.rank && options.
|
|
196
|
-
args.push('--
|
|
197
|
-
if (options.page && options.page > 1) {
|
|
198
|
-
args.push('--page', String(options.page));
|
|
199
|
-
}
|
|
217
|
+
if (hasQuery && options.rank !== false && options.page) {
|
|
218
|
+
args.push('--page', String(options.page));
|
|
200
219
|
}
|
|
220
|
+
args.push('--scope', scope);
|
|
201
221
|
|
|
202
|
-
const output =
|
|
203
|
-
encoding: 'utf-8',
|
|
204
|
-
});
|
|
205
|
-
|
|
222
|
+
const output = runGoosedump(args);
|
|
206
223
|
const messages = parseMessages(output);
|
|
224
|
+
const pageInfo = parsePageInfo(output, options.page ?? 1);
|
|
225
|
+
|
|
207
226
|
return {
|
|
208
227
|
messages,
|
|
209
228
|
totalCount: messages.length,
|
|
210
|
-
page:
|
|
211
|
-
totalPages:
|
|
229
|
+
page: pageInfo.page,
|
|
230
|
+
totalPages: pageInfo.totalPages,
|
|
212
231
|
};
|
|
213
232
|
}
|
|
214
233
|
|
|
@@ -217,17 +236,40 @@ function goosedumpExpand(
|
|
|
217
236
|
entryIds: string[],
|
|
218
237
|
scope: string = 'lineage',
|
|
219
238
|
): GoosedumpMessage[] {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
239
|
+
if (entryIds.length === 0) return [];
|
|
240
|
+
|
|
241
|
+
const output = runGoosedump([
|
|
242
|
+
'show',
|
|
243
|
+
'pi',
|
|
244
|
+
contextId,
|
|
245
|
+
'--ids',
|
|
246
|
+
entryIds.join(','),
|
|
247
|
+
'--scope',
|
|
248
|
+
scope,
|
|
249
|
+
]);
|
|
250
|
+
return parseMessages(output);
|
|
251
|
+
}
|
|
225
252
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
253
|
+
function goosedumpCompact(
|
|
254
|
+
contextId: string,
|
|
255
|
+
options: {
|
|
256
|
+
scope?: string;
|
|
257
|
+
from?: string;
|
|
258
|
+
until?: string;
|
|
259
|
+
ids?: string[];
|
|
260
|
+
},
|
|
261
|
+
): string {
|
|
262
|
+
const scope = options.scope ?? 'lineage';
|
|
263
|
+
const args = ['compact', 'pi', contextId, '--scope', scope];
|
|
264
|
+
|
|
265
|
+
if (options.ids && options.ids.length > 0) {
|
|
266
|
+
args.push('--ids', options.ids.join(','));
|
|
267
|
+
} else {
|
|
268
|
+
if (options.from) args.push('--from', options.from);
|
|
269
|
+
if (options.until) args.push('--until', options.until);
|
|
270
|
+
}
|
|
229
271
|
|
|
230
|
-
return
|
|
272
|
+
return runGoosedump(args).trim();
|
|
231
273
|
}
|
|
232
274
|
|
|
233
275
|
export interface GoosedumpIntegrationOptions {
|
|
@@ -235,18 +277,23 @@ export interface GoosedumpIntegrationOptions {
|
|
|
235
277
|
overrideDefaultCompaction?: boolean;
|
|
236
278
|
}
|
|
237
279
|
|
|
238
|
-
interface
|
|
239
|
-
|
|
240
|
-
|
|
280
|
+
interface GoosedumpCompactRange {
|
|
281
|
+
scope: string;
|
|
282
|
+
from?: string;
|
|
283
|
+
until: string;
|
|
284
|
+
ids?: string[];
|
|
241
285
|
}
|
|
242
286
|
|
|
243
|
-
interface
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
287
|
+
interface GoosedumpCompactionDetails {
|
|
288
|
+
source: 'goosedump';
|
|
289
|
+
goosedumpVersion: string | null;
|
|
290
|
+
contextId: string;
|
|
291
|
+
scope: string;
|
|
292
|
+
from?: string;
|
|
293
|
+
until?: string;
|
|
294
|
+
ids?: string[];
|
|
295
|
+
previousSummaryIncluded: boolean;
|
|
296
|
+
fileOps: unknown;
|
|
250
297
|
}
|
|
251
298
|
|
|
252
299
|
export interface GoosedumpIntegration {
|
|
@@ -257,143 +304,138 @@ export default function (pi: ExtensionAPI) {
|
|
|
257
304
|
createGoosedumpIntegration().register(pi);
|
|
258
305
|
}
|
|
259
306
|
|
|
260
|
-
function
|
|
261
|
-
if (
|
|
262
|
-
if (Array.isArray(m.content)) {
|
|
263
|
-
return m.content
|
|
264
|
-
.filter((c) => c.type === 'text' && typeof c.text === 'string')
|
|
265
|
-
.map((c) => c.text!)
|
|
266
|
-
.join(' ');
|
|
267
|
-
}
|
|
268
|
-
return '';
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
function extractContextFromMessages(messages: MessageLike[]): ExtractedContext {
|
|
272
|
-
const goalPatterns = [
|
|
273
|
-
/(?:goal|objective|task|aim|purpose)[\s:]*([^.\n]+)/gi,
|
|
274
|
-
/(?:working on|implementing|building|creating|fixing|adding)\s+([^.\n]+)/gi,
|
|
275
|
-
/#\s*(?:Goal|Task|Objective)[\s:]*([^\n]+)/gi,
|
|
276
|
-
];
|
|
277
|
-
|
|
278
|
-
const referencePatterns = [
|
|
279
|
-
/(?:https?:\/\/[^\s]+)/gi,
|
|
280
|
-
/(?:[\w./-]+\/[@\w.-]+(?:#\S+)?)/g,
|
|
281
|
-
/(?:`[^`]+\.[a-z]{1,6}`)/g,
|
|
282
|
-
/(?:v?\d+\.\d+(?:\.\d+)?(?:-[\w.]+)?)/g,
|
|
283
|
-
/(?:#[1-9]\d*)/g,
|
|
284
|
-
];
|
|
285
|
-
|
|
286
|
-
const decisionPatterns = [
|
|
287
|
-
/(?:decided|decision|chosen?|opted|settled|agreed)\s+(?:to\s+)?([^.\n]+)/gi,
|
|
288
|
-
/(?:will|should|must|going to)\s+(?:use|switch|change|keep|stay|go with)\s+([^.\n]+)/gi,
|
|
289
|
-
/(?:let's|lets|we'll)\s+([^.\n]+)/gi,
|
|
290
|
-
];
|
|
291
|
-
|
|
292
|
-
const constraintPatterns = [
|
|
293
|
-
/(?:cannot|can't|must not|should not|don't|do not|never)\s+([^.\n]+)/gi,
|
|
294
|
-
/(?:constraint|restriction|limitation|requirement)[\s:]*([^.\n]+)/gi,
|
|
295
|
-
/(?:need to|have to|required to)\s+(?:keep|maintain|preserve|ensure|avoid)\s+([^.\n]+)/gi,
|
|
296
|
-
];
|
|
297
|
-
|
|
298
|
-
const statusPatterns = [
|
|
299
|
-
/(?:status|state|progress)[\s:]*([^.\n]+)/gi,
|
|
300
|
-
/(?:done|completed|finished|resolved|fixed)\s+([^.\n]+)/gi,
|
|
301
|
-
/(?:todo|pending|remaining|left|in progress|wip)\s*:?\s*([^.\n]+)/gi,
|
|
302
|
-
];
|
|
303
|
-
|
|
304
|
-
const allText = messages.map((m) => getMessageText(m)).join('\n');
|
|
305
|
-
|
|
306
|
-
const recentText = messages
|
|
307
|
-
.slice(-30)
|
|
308
|
-
.map((m) => getMessageText(m))
|
|
309
|
-
.join('\n');
|
|
310
|
-
|
|
311
|
-
const extractMatches = (text: string, patterns: RegExp[]): Set<string> => {
|
|
312
|
-
const matches = new Set<string>();
|
|
313
|
-
for (const pattern of patterns) {
|
|
314
|
-
pattern.lastIndex = 0;
|
|
315
|
-
let match: RegExpExecArray | null;
|
|
316
|
-
while ((match = pattern.exec(text)) !== null) {
|
|
317
|
-
const extracted = (match[1] ?? '').trim().slice(0, 200);
|
|
318
|
-
if (extracted.length > 3) matches.add(extracted);
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
return matches;
|
|
322
|
-
};
|
|
307
|
+
function sessionFileContextId(sessionFile: string | null | undefined): string | null {
|
|
308
|
+
if (!sessionFile) return null;
|
|
323
309
|
|
|
324
|
-
const
|
|
325
|
-
const
|
|
310
|
+
const name = basename(sessionFile);
|
|
311
|
+
const nameMatch = name.match(
|
|
312
|
+
/(?:^|_)([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.jsonl$/i,
|
|
313
|
+
);
|
|
314
|
+
if (nameMatch) return nameMatch[1];
|
|
326
315
|
|
|
327
|
-
|
|
328
|
-
.
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
const lines = content.split('\n').filter((l: string) => l.trim().length > 10);
|
|
336
|
-
return lines.length > 0 ? lines[0].trim().slice(0, 120) : null;
|
|
337
|
-
})
|
|
338
|
-
.filter((l: string | null): l is string => l !== null)
|
|
339
|
-
.slice(0, 8);
|
|
316
|
+
try {
|
|
317
|
+
const firstLine = readFileSync(sessionFile, 'utf-8').split(/\r?\n/, 1)[0];
|
|
318
|
+
const header = JSON.parse(firstLine) as { id?: unknown };
|
|
319
|
+
return typeof header.id === 'string' && header.id.length > 0 ? header.id : null;
|
|
320
|
+
} catch {
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
340
324
|
|
|
341
|
-
|
|
342
|
-
|
|
325
|
+
function currentSessionContextId(ctx: ExtensionContext): string | null {
|
|
326
|
+
return sessionFileContextId(ctx.sessionManager.getSessionFile());
|
|
327
|
+
}
|
|
343
328
|
|
|
344
|
-
|
|
345
|
-
|
|
329
|
+
function isCompactionEntry(entry: SessionEntry): entry is CompactionEntry {
|
|
330
|
+
return entry.type === 'compaction';
|
|
331
|
+
}
|
|
346
332
|
|
|
347
|
-
|
|
348
|
-
|
|
333
|
+
function latestCompactionBefore(
|
|
334
|
+
branchEntries: SessionEntry[],
|
|
335
|
+
untilIndex: number,
|
|
336
|
+
): CompactionEntry | undefined {
|
|
337
|
+
for (let i = untilIndex - 1; i >= 0; i -= 1) {
|
|
338
|
+
const entry = branchEntries[i];
|
|
339
|
+
if (isCompactionEntry(entry)) return entry;
|
|
340
|
+
}
|
|
341
|
+
return undefined;
|
|
342
|
+
}
|
|
349
343
|
|
|
350
|
-
|
|
351
|
-
|
|
344
|
+
function compactionStartId(
|
|
345
|
+
branchEntries: SessionEntry[],
|
|
346
|
+
previousCompaction: CompactionEntry,
|
|
347
|
+
untilIndex: number,
|
|
348
|
+
): string | undefined {
|
|
349
|
+
const keptIndex = branchEntries.findIndex(
|
|
350
|
+
(entry) => entry.id === previousCompaction.firstKeptEntryId,
|
|
351
|
+
);
|
|
352
|
+
if (keptIndex >= 0 && keptIndex < untilIndex) return previousCompaction.firstKeptEntryId;
|
|
353
|
+
|
|
354
|
+
const compactionIndex = branchEntries.findIndex((entry) => entry.id === previousCompaction.id);
|
|
355
|
+
if (compactionIndex >= 0 && compactionIndex + 1 < untilIndex) {
|
|
356
|
+
return branchEntries[compactionIndex + 1].id;
|
|
357
|
+
}
|
|
352
358
|
|
|
353
|
-
return
|
|
354
|
-
goal,
|
|
355
|
-
outstanding:
|
|
356
|
-
outstandingItems.length > 0 ? outstandingItems : ['No outstanding items identified'],
|
|
357
|
-
references,
|
|
358
|
-
decisions,
|
|
359
|
-
constraints: constraints.length > 0 ? constraints : ['No specific constraints identified'],
|
|
360
|
-
status,
|
|
361
|
-
};
|
|
359
|
+
return undefined;
|
|
362
360
|
}
|
|
363
361
|
|
|
364
|
-
function
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
362
|
+
function buildCompactRange(
|
|
363
|
+
branchEntries: SessionEntry[],
|
|
364
|
+
firstKeptEntryId: string,
|
|
365
|
+
): GoosedumpCompactRange | undefined {
|
|
366
|
+
const untilIndex = branchEntries.findIndex((entry) => entry.id === firstKeptEntryId);
|
|
367
|
+
if (untilIndex < 0) return undefined;
|
|
368
|
+
|
|
369
|
+
const previousCompaction = latestCompactionBefore(branchEntries, untilIndex);
|
|
370
|
+
const from = previousCompaction
|
|
371
|
+
? compactionStartId(branchEntries, previousCompaction, untilIndex)
|
|
372
|
+
: undefined;
|
|
373
|
+
|
|
374
|
+
return from && from !== firstKeptEntryId
|
|
375
|
+
? { scope: 'lineage', from, until: firstKeptEntryId }
|
|
376
|
+
: { scope: 'lineage', until: firstKeptEntryId };
|
|
377
|
+
}
|
|
368
378
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
379
|
+
function mergeWithPreviousSummary(
|
|
380
|
+
summary: string,
|
|
381
|
+
previousSummary: string | undefined,
|
|
382
|
+
): { summary: string; previousSummaryIncluded: boolean } {
|
|
383
|
+
const nextSummary = summary.trim();
|
|
384
|
+
const priorSummary = previousSummary?.trim();
|
|
372
385
|
|
|
373
|
-
if (
|
|
374
|
-
|
|
386
|
+
if (!priorSummary) {
|
|
387
|
+
return { summary: nextSummary, previousSummaryIncluded: false };
|
|
375
388
|
}
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
sections.push(`## Key Decisions\n${ctx.decisions.map((d) => `- ${d}`).join('\n')}`);
|
|
389
|
+
if (!nextSummary) {
|
|
390
|
+
return { summary: priorSummary, previousSummaryIncluded: true };
|
|
379
391
|
}
|
|
380
392
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
393
|
+
return {
|
|
394
|
+
summary: `${priorSummary}\n\n## Recent Compaction Update\n${nextSummary}`,
|
|
395
|
+
previousSummaryIncluded: true,
|
|
396
|
+
};
|
|
397
|
+
}
|
|
384
398
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
399
|
+
function buildGoosedumpCompaction(
|
|
400
|
+
contextId: string,
|
|
401
|
+
preparation: {
|
|
402
|
+
firstKeptEntryId: string;
|
|
403
|
+
tokensBefore: number;
|
|
404
|
+
previousSummary?: string;
|
|
405
|
+
fileOps: unknown;
|
|
406
|
+
},
|
|
407
|
+
branchEntries: SessionEntry[],
|
|
408
|
+
): CompactionResult<GoosedumpCompactionDetails> | undefined {
|
|
409
|
+
const range = buildCompactRange(branchEntries, preparation.firstKeptEntryId);
|
|
410
|
+
if (!range) return undefined;
|
|
411
|
+
|
|
412
|
+
const summary = goosedumpCompact(contextId, range);
|
|
413
|
+
const merged = mergeWithPreviousSummary(summary, preparation.previousSummary);
|
|
414
|
+
if (!merged.summary) return undefined;
|
|
388
415
|
|
|
389
|
-
return
|
|
416
|
+
return {
|
|
417
|
+
summary: merged.summary,
|
|
418
|
+
firstKeptEntryId: preparation.firstKeptEntryId,
|
|
419
|
+
tokensBefore: preparation.tokensBefore,
|
|
420
|
+
details: {
|
|
421
|
+
source: 'goosedump',
|
|
422
|
+
goosedumpVersion: goosedumpVersion(),
|
|
423
|
+
contextId,
|
|
424
|
+
scope: range.scope,
|
|
425
|
+
from: range.from,
|
|
426
|
+
until: range.until,
|
|
427
|
+
ids: range.ids,
|
|
428
|
+
previousSummaryIncluded: merged.previousSummaryIncluded,
|
|
429
|
+
fileOps: preparation.fileOps,
|
|
430
|
+
},
|
|
431
|
+
};
|
|
390
432
|
}
|
|
391
433
|
|
|
392
434
|
export function createGoosedumpIntegration(
|
|
393
435
|
options: GoosedumpIntegrationOptions = {},
|
|
394
436
|
): GoosedumpIntegration {
|
|
395
437
|
const shouldRegisterTool = options.registerTool ?? true;
|
|
396
|
-
const
|
|
438
|
+
const shouldOverrideDefaultCompaction = options.overrideDefaultCompaction ?? true;
|
|
397
439
|
|
|
398
440
|
let goosedumpReady = false;
|
|
399
441
|
let goosedumpEnabled = false;
|
|
@@ -430,12 +472,13 @@ export function createGoosedumpIntegration(
|
|
|
430
472
|
name: 'goosedump',
|
|
431
473
|
label: 'goosedump',
|
|
432
474
|
description:
|
|
433
|
-
'Browse coding agent session history. List all sessions, or view/search within a session. Supports
|
|
434
|
-
promptSnippet:
|
|
475
|
+
'Browse coding agent session history. List all sessions, or view/search within a session. Supports ranked search, compact overview, and result expansion. Default scope is active lineage.',
|
|
476
|
+
promptSnippet:
|
|
477
|
+
'goosedump({ action: "search", contextId, query, scope?, page? }) - search session history',
|
|
435
478
|
promptGuidelines: [
|
|
436
479
|
'When researching past conversation history, use goosedump to find relevant context.',
|
|
437
|
-
'Start with compact search (compact: true) for quick overview, then expand interesting entries.',
|
|
438
|
-
'Default scope is "lineage" (current branch); use scope: "all" to
|
|
480
|
+
'Start with compact ranked search (compact: true) for quick overview, then expand interesting entries.',
|
|
481
|
+
'Default scope is "lineage" (current branch); use scope: "all" to include all entries in the session.',
|
|
439
482
|
],
|
|
440
483
|
parameters: Type.Object({
|
|
441
484
|
action: Type.Union(
|
|
@@ -456,12 +499,12 @@ export function createGoosedumpIntegration(
|
|
|
456
499
|
}),
|
|
457
500
|
),
|
|
458
501
|
query: Type.Optional(
|
|
459
|
-
Type.String({ description: '
|
|
502
|
+
Type.String({ description: 'Search query to rank messages by relevance' }),
|
|
460
503
|
),
|
|
461
504
|
scope: Type.Optional(
|
|
462
505
|
Type.Union([Type.Literal('lineage'), Type.Literal('all')], {
|
|
463
506
|
default: 'lineage',
|
|
464
|
-
description: 'Scope: lineage (current branch) or all
|
|
507
|
+
description: 'Scope: lineage (current branch) or all entries in the session',
|
|
465
508
|
}),
|
|
466
509
|
),
|
|
467
510
|
compact: Type.Optional(
|
|
@@ -548,7 +591,8 @@ export function createGoosedumpIntegration(
|
|
|
548
591
|
};
|
|
549
592
|
}
|
|
550
593
|
|
|
551
|
-
const
|
|
594
|
+
const useCompact = params.compact ?? true;
|
|
595
|
+
const text = useCompact
|
|
552
596
|
? formatMessagesCompact(result.messages)
|
|
553
597
|
: formatMessagesFull(result.messages);
|
|
554
598
|
|
|
@@ -653,34 +697,41 @@ export function createGoosedumpIntegration(
|
|
|
653
697
|
}
|
|
654
698
|
});
|
|
655
699
|
|
|
656
|
-
pi.on('session_before_compact', async (event,
|
|
657
|
-
if (!goosedumpEnabled || !goosedumpReady) return;
|
|
700
|
+
pi.on('session_before_compact', async (event, ctx) => {
|
|
701
|
+
if (!shouldOverrideDefaultCompaction || !goosedumpEnabled || !goosedumpReady) return;
|
|
702
|
+
if (event.customInstructions?.trim()) return;
|
|
703
|
+
if (event.signal.aborted) return;
|
|
658
704
|
|
|
659
|
-
const { messagesToSummarize,
|
|
705
|
+
const { messagesToSummarize, turnPrefixMessages } = event.preparation;
|
|
706
|
+
if (messagesToSummarize.length === 0 && turnPrefixMessages.length === 0) return;
|
|
660
707
|
|
|
661
|
-
|
|
708
|
+
const contextId = currentSessionContextId(ctx);
|
|
709
|
+
if (!contextId) return;
|
|
662
710
|
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
711
|
+
try {
|
|
712
|
+
const compaction = buildGoosedumpCompaction(
|
|
713
|
+
contextId,
|
|
714
|
+
event.preparation,
|
|
715
|
+
event.branchEntries,
|
|
716
|
+
);
|
|
717
|
+
return compaction ? { compaction } : undefined;
|
|
718
|
+
} catch (err) {
|
|
719
|
+
if (ctx.hasUI) {
|
|
720
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
721
|
+
ctx.ui.notify(
|
|
722
|
+
`goosedump compact failed; falling back to Pi compaction: ${message}`,
|
|
723
|
+
'error',
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
return undefined;
|
|
727
|
+
}
|
|
677
728
|
});
|
|
678
729
|
|
|
679
|
-
pi.on('session_compact', async (event,
|
|
680
|
-
if (!goosedumpEnabled || !goosedumpReady) return;
|
|
730
|
+
pi.on('session_compact', async (event, ctx) => {
|
|
731
|
+
if (!goosedumpEnabled || !goosedumpReady || !event.fromExtension) return;
|
|
681
732
|
|
|
682
733
|
const compactionEntry = event.compactionEntry;
|
|
683
|
-
if (!compactionEntry
|
|
734
|
+
if (!compactionEntry) return;
|
|
684
735
|
|
|
685
736
|
const tokensBefore = compactionEntry.tokensBefore;
|
|
686
737
|
const summaryLen = compactionEntry.summary.length;
|
|
@@ -688,7 +739,7 @@ export function createGoosedumpIntegration(
|
|
|
688
739
|
const tokensSaved =
|
|
689
740
|
tokensBefore > 0 ? Math.round((1 - tokenEstimate / tokensBefore) * 100) : 0;
|
|
690
741
|
|
|
691
|
-
|
|
742
|
+
ctx.ui.notify(
|
|
692
743
|
`Goosedump compacted session: ~${tokensSaved}% token reduction (${tokensBefore} → ~${tokenEstimate} tokens)`,
|
|
693
744
|
'info',
|
|
694
745
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-goosedump",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.2",
|
|
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.2.2",
|
|
33
33
|
"@sinclair/typebox": "^0.34.49"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|