pi-goosedump 0.5.0 → 0.5.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/index.ts +249 -359
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -37,9 +37,8 @@ interface GoosedumpMessage {
|
|
|
37
37
|
score?: number;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
interface
|
|
40
|
+
interface GoosedumpSearchResult {
|
|
41
41
|
messages: GoosedumpMessage[];
|
|
42
|
-
totalCount: number;
|
|
43
42
|
page: number;
|
|
44
43
|
totalPages: number;
|
|
45
44
|
}
|
|
@@ -87,10 +86,6 @@ interface SearchJson {
|
|
|
87
86
|
hits: HitJson[];
|
|
88
87
|
}
|
|
89
88
|
|
|
90
|
-
interface GrepJson {
|
|
91
|
-
hits: HitJson[];
|
|
92
|
-
}
|
|
93
|
-
|
|
94
89
|
interface CompactJson {
|
|
95
90
|
goals: string[];
|
|
96
91
|
files: { modified: string[]; created: string[]; read: string[] };
|
|
@@ -233,6 +228,14 @@ function formatMessagesFull(messages: GoosedumpMessage[]): string {
|
|
|
233
228
|
return messages.map((m) => `[${m.id}] ${m.role}:\n${m.content}`).join('\n\n---\n\n');
|
|
234
229
|
}
|
|
235
230
|
|
|
231
|
+
function textResult(text: string): AgentToolResult<void> {
|
|
232
|
+
return { content: [{ type: 'text', text }], details: undefined };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function missingContextId(action: string): AgentToolResult<void> {
|
|
236
|
+
return textResult(`contextId is required for ${action} when no current session is available`);
|
|
237
|
+
}
|
|
238
|
+
|
|
236
239
|
function runGoosedump(args: string[]): string {
|
|
237
240
|
return execFileSync(process.execPath, [binaryPath(), ...args], {
|
|
238
241
|
encoding: 'utf-8',
|
|
@@ -247,59 +250,31 @@ function goosedumpList(): GoosedumpListing[] {
|
|
|
247
250
|
}));
|
|
248
251
|
}
|
|
249
252
|
|
|
250
|
-
function
|
|
253
|
+
function goosedumpSearch(
|
|
251
254
|
contextId: string,
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
ids?: string;
|
|
256
|
-
rank?: boolean;
|
|
257
|
-
page?: number;
|
|
258
|
-
} = {},
|
|
259
|
-
): GoosedumpContextResult {
|
|
255
|
+
query: string,
|
|
256
|
+
options: { scope?: string; page?: number } = {},
|
|
257
|
+
): GoosedumpSearchResult {
|
|
260
258
|
const scope = options.scope ?? 'lineage';
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
const target = `pi:${contextId}`;
|
|
264
|
-
const args = hasQuery
|
|
265
|
-
? isSearch
|
|
266
|
-
? ['search', target, options.grep ?? '']
|
|
267
|
-
: ['grep', target, options.grep ?? '']
|
|
268
|
-
: ['show', target];
|
|
269
|
-
|
|
270
|
-
if (options.ids) {
|
|
271
|
-
args.push('--ids', options.ids);
|
|
272
|
-
}
|
|
273
|
-
if (isSearch && options.page) {
|
|
274
|
-
args.push('--page', String(options.page));
|
|
275
|
-
}
|
|
259
|
+
const args = ['search', `pi:${contextId}`, query];
|
|
260
|
+
if (options.page) args.push('--page', String(options.page));
|
|
276
261
|
args.push('--scope', scope);
|
|
277
262
|
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
const json = JSON.parse(output) as SearchJson | GrepJson;
|
|
282
|
-
const messages = (json.hits ?? []).map(hitMessage);
|
|
283
|
-
const page = isSearch ? ((json as SearchJson).page ?? options.page ?? 1) : 1;
|
|
284
|
-
const totalPages = isSearch ? ((json as SearchJson).totalPages ?? 1) : 1;
|
|
285
|
-
return { messages, totalCount: messages.length, page, totalPages };
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
const json = JSON.parse(output) as ShowJson;
|
|
289
|
-
const messages = (json.messages ?? []).map(showMessage);
|
|
290
|
-
return { messages, totalCount: messages.length, page: options.page ?? 1, totalPages: 1 };
|
|
263
|
+
const json = JSON.parse(runGoosedump(args)) as SearchJson;
|
|
264
|
+
const messages = (json.hits ?? []).map(hitMessage);
|
|
265
|
+
return { messages, page: json.page ?? options.page ?? 1, totalPages: json.totalPages ?? 1 };
|
|
291
266
|
}
|
|
292
267
|
|
|
293
|
-
function
|
|
268
|
+
function goosedumpShow(
|
|
294
269
|
contextId: string,
|
|
295
|
-
|
|
296
|
-
scope: string = 'lineage',
|
|
270
|
+
options: { scope?: string; ids?: string[] } = {},
|
|
297
271
|
): GoosedumpMessage[] {
|
|
298
|
-
|
|
272
|
+
const scope = options.scope ?? 'lineage';
|
|
273
|
+
const args = ['show', `pi:${contextId}`];
|
|
274
|
+
if (options.ids && options.ids.length > 0) args.push('--ids', options.ids.join(','));
|
|
275
|
+
args.push('--scope', scope);
|
|
299
276
|
|
|
300
|
-
const json = JSON.parse(
|
|
301
|
-
runGoosedump(['show', `pi:${contextId}`, '--ids', entryIds.join(','), '--scope', scope]),
|
|
302
|
-
) as ShowJson;
|
|
277
|
+
const json = JSON.parse(runGoosedump(args)) as ShowJson;
|
|
303
278
|
return (json.messages ?? []).map(showMessage);
|
|
304
279
|
}
|
|
305
280
|
|
|
@@ -592,14 +567,204 @@ function buildGoosedumpCompaction(
|
|
|
592
567
|
};
|
|
593
568
|
}
|
|
594
569
|
|
|
570
|
+
async function runSessionBrowser(
|
|
571
|
+
ctx: ExtensionContext,
|
|
572
|
+
listings: GoosedumpListing[],
|
|
573
|
+
): Promise<string | null | undefined> {
|
|
574
|
+
let resumePath: string | null | undefined;
|
|
575
|
+
|
|
576
|
+
await ctx.ui.custom<void>(
|
|
577
|
+
(tui, theme, _kb, done) => {
|
|
578
|
+
let selectedIndex = 0;
|
|
579
|
+
let mode: 'list' | 'compact' = 'list';
|
|
580
|
+
let compactLines: string[] = [];
|
|
581
|
+
let compactScroll = 0;
|
|
582
|
+
let compactTitle = '';
|
|
583
|
+
|
|
584
|
+
const COMPACT_VIEWPORT = 20;
|
|
585
|
+
|
|
586
|
+
return {
|
|
587
|
+
render(width: number): string[] {
|
|
588
|
+
const innerW = width - 4;
|
|
589
|
+
const border = theme.fg('border', '│');
|
|
590
|
+
const borderFg = (s: string) => theme.fg('border', s);
|
|
591
|
+
const dim = (s: string) => theme.fg('dim', s);
|
|
592
|
+
const accent = (s: string) => theme.fg('accent', s);
|
|
593
|
+
const text = (s: string) => theme.fg('text', s);
|
|
594
|
+
const muted = (s: string) => theme.fg('muted', s);
|
|
595
|
+
|
|
596
|
+
const lines: string[] = [];
|
|
597
|
+
|
|
598
|
+
if (mode === 'compact') {
|
|
599
|
+
const title = accent(` Compact: ${truncateToWidth(compactTitle, 40)} `);
|
|
600
|
+
const topFill = borderFg('─'.repeat(Math.max(0, width - 4 - visibleWidth(title))));
|
|
601
|
+
lines.push(`${borderFg('╭─')}${title}${topFill}${borderFg('─╮')}`);
|
|
602
|
+
|
|
603
|
+
const total = compactLines.length;
|
|
604
|
+
const maxScroll = Math.max(0, total - COMPACT_VIEWPORT);
|
|
605
|
+
if (compactScroll > maxScroll) compactScroll = maxScroll;
|
|
606
|
+
const window = compactLines.slice(compactScroll, compactScroll + COMPACT_VIEWPORT);
|
|
607
|
+
for (const row of window) {
|
|
608
|
+
lines.push(makeRow(text(truncateToWidth(row, innerW)), innerW, border));
|
|
609
|
+
}
|
|
610
|
+
for (let i = window.length; i < COMPACT_VIEWPORT; i++) {
|
|
611
|
+
lines.push(makeRow('', innerW, border));
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const position =
|
|
615
|
+
total > COMPACT_VIEWPORT
|
|
616
|
+
? `${compactScroll + 1}-${Math.min(compactScroll + COMPACT_VIEWPORT, total)} of ${total}`
|
|
617
|
+
: `${total} line${total === 1 ? '' : 's'}`;
|
|
618
|
+
lines.push(makeRow('', innerW, border));
|
|
619
|
+
lines.push(makeRow(dim(`↑↓ scroll esc back (${position})`), innerW, border));
|
|
620
|
+
lines.push(`${borderFg('╰')}${borderFg('─'.repeat(width - 2))}${borderFg('╯')}`);
|
|
621
|
+
return lines;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
// Top border
|
|
625
|
+
const title = accent(' Goosedump Sessions ');
|
|
626
|
+
const topFill = borderFg('─'.repeat(Math.max(0, width - 4 - visibleWidth(title))));
|
|
627
|
+
lines.push(`${borderFg('╭─')}${title}${topFill}${borderFg('─╮')}`);
|
|
628
|
+
|
|
629
|
+
// Session count
|
|
630
|
+
const countInfo = `${listings.length} session${listings.length === 1 ? '' : 's'}`;
|
|
631
|
+
lines.push(makeRow(`${muted(countInfo)}`, innerW, border));
|
|
632
|
+
|
|
633
|
+
lines.push(makeRow('', innerW, border));
|
|
634
|
+
|
|
635
|
+
// Session list
|
|
636
|
+
const maxItems = Math.min(listings.length, 20);
|
|
637
|
+
for (let i = 0; i < maxItems; i++) {
|
|
638
|
+
const listing = listings[i];
|
|
639
|
+
const isSelected = i === selectedIndex;
|
|
640
|
+
const cursor = isSelected ? accent('▶') : ' ';
|
|
641
|
+
const idText = isSelected ? accent(listing.id) : text(listing.id);
|
|
642
|
+
const line = ` ${cursor} ${idText}`;
|
|
643
|
+
lines.push(makeRow(truncateToWidth(line, innerW), innerW, border));
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
if (listings.length > 20) {
|
|
647
|
+
lines.push(makeRow(dim(` ... and ${listings.length - 20} more`), innerW, border));
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// Footer
|
|
651
|
+
lines.push(makeRow('', innerW, border));
|
|
652
|
+
lines.push(
|
|
653
|
+
makeRow(
|
|
654
|
+
dim('↑↓ navigate enter compact shift+enter resume esc close'),
|
|
655
|
+
innerW,
|
|
656
|
+
border,
|
|
657
|
+
),
|
|
658
|
+
);
|
|
659
|
+
|
|
660
|
+
// Bottom border
|
|
661
|
+
lines.push(`${borderFg('╰')}${borderFg('─'.repeat(width - 2))}${borderFg('╯')}`);
|
|
662
|
+
|
|
663
|
+
return lines;
|
|
664
|
+
},
|
|
665
|
+
|
|
666
|
+
handleInput(data: string): void {
|
|
667
|
+
if (mode === 'compact') {
|
|
668
|
+
if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl('c'))) {
|
|
669
|
+
mode = 'list';
|
|
670
|
+
tui.requestRender();
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
if (matchesKey(data, Key.up)) {
|
|
674
|
+
compactScroll = Math.max(0, compactScroll - 1);
|
|
675
|
+
tui.requestRender();
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
if (matchesKey(data, Key.down)) {
|
|
679
|
+
compactScroll += 1;
|
|
680
|
+
tui.requestRender();
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
if (matchesKey(data, Key.pageUp)) {
|
|
684
|
+
compactScroll = Math.max(0, compactScroll - COMPACT_VIEWPORT);
|
|
685
|
+
tui.requestRender();
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
if (matchesKey(data, Key.pageDown)) {
|
|
689
|
+
compactScroll += COMPACT_VIEWPORT;
|
|
690
|
+
tui.requestRender();
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
return;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl('c'))) {
|
|
697
|
+
done(undefined);
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
if (matchesKey(data, Key.up)) {
|
|
702
|
+
selectedIndex = Math.max(0, selectedIndex - 1);
|
|
703
|
+
tui.requestRender();
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
if (matchesKey(data, Key.down)) {
|
|
708
|
+
selectedIndex = Math.min(listings.length - 1, selectedIndex + 1);
|
|
709
|
+
tui.requestRender();
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
if (matchesKey(data, Key.shift(Key.enter))) {
|
|
714
|
+
resumePath = listings[selectedIndex]?.path;
|
|
715
|
+
done(undefined);
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
if (matchesKey(data, Key.enter) || matchesKey(data, Key.return)) {
|
|
720
|
+
const listing = listings[selectedIndex];
|
|
721
|
+
if (!listing) return;
|
|
722
|
+
compactTitle = listing.id;
|
|
723
|
+
compactScroll = 0;
|
|
724
|
+
try {
|
|
725
|
+
const summary = goosedumpCompact(listing.id, { scope: 'lineage' }).trim();
|
|
726
|
+
compactLines = summary
|
|
727
|
+
? summary.split('\n')
|
|
728
|
+
: ['No compact summary available for this session.'];
|
|
729
|
+
} catch (err) {
|
|
730
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
731
|
+
compactLines = [`Failed to compact session: ${msg}`];
|
|
732
|
+
}
|
|
733
|
+
mode = 'compact';
|
|
734
|
+
tui.requestRender();
|
|
735
|
+
return;
|
|
736
|
+
}
|
|
737
|
+
},
|
|
738
|
+
|
|
739
|
+
invalidate(): void {},
|
|
740
|
+
};
|
|
741
|
+
|
|
742
|
+
function makeRow(content: string, innerW: number, border: string): string {
|
|
743
|
+
const line = truncateToWidth(content, innerW);
|
|
744
|
+
const pad = Math.max(0, innerW - visibleWidth(line));
|
|
745
|
+
return `${border} ${line}${' '.repeat(pad)} ${border}`;
|
|
746
|
+
}
|
|
747
|
+
},
|
|
748
|
+
{
|
|
749
|
+
overlay: true,
|
|
750
|
+
overlayOptions: {
|
|
751
|
+
anchor: 'center',
|
|
752
|
+
width: 72,
|
|
753
|
+
margin: 2,
|
|
754
|
+
},
|
|
755
|
+
},
|
|
756
|
+
);
|
|
757
|
+
|
|
758
|
+
return resumePath;
|
|
759
|
+
}
|
|
760
|
+
|
|
595
761
|
export function createGoosedumpIntegration(
|
|
596
762
|
options: GoosedumpIntegrationOptions = {},
|
|
597
763
|
): GoosedumpIntegration {
|
|
598
764
|
const shouldRegisterTool = options.registerTool ?? true;
|
|
599
765
|
const shouldOverrideDefaultCompaction = options.overrideDefaultCompaction ?? true;
|
|
600
766
|
|
|
601
|
-
let
|
|
602
|
-
let goosedumpEnabled = false;
|
|
767
|
+
let goosedumpAvailable = false;
|
|
603
768
|
|
|
604
769
|
function checkGoosedump(ctx?: ExtensionContext): boolean {
|
|
605
770
|
const version = goosedumpVersion();
|
|
@@ -690,16 +855,10 @@ export function createGoosedumpIntegration(
|
|
|
690
855
|
),
|
|
691
856
|
}),
|
|
692
857
|
async execute(_id, params, _signal, _onUpdate, ctx): Promise<AgentToolResult<void>> {
|
|
693
|
-
if (!
|
|
694
|
-
return
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
type: 'text',
|
|
698
|
-
text: 'goosedump is not available. Install with: npm install @jarkkojs/goosedump',
|
|
699
|
-
},
|
|
700
|
-
],
|
|
701
|
-
details: undefined,
|
|
702
|
-
};
|
|
858
|
+
if (!goosedumpAvailable) {
|
|
859
|
+
return textResult(
|
|
860
|
+
'goosedump is not available. Install with: npm install @jarkkojs/goosedump',
|
|
861
|
+
);
|
|
703
862
|
}
|
|
704
863
|
|
|
705
864
|
try {
|
|
@@ -707,10 +866,7 @@ export function createGoosedumpIntegration(
|
|
|
707
866
|
case 'list': {
|
|
708
867
|
const listings = goosedumpList();
|
|
709
868
|
if (listings.length === 0) {
|
|
710
|
-
return
|
|
711
|
-
content: [{ type: 'text', text: 'No sessions found.' }],
|
|
712
|
-
details: undefined,
|
|
713
|
-
};
|
|
869
|
+
return textResult('No sessions found.');
|
|
714
870
|
}
|
|
715
871
|
|
|
716
872
|
const lines = ['Available sessions:', ''];
|
|
@@ -718,45 +874,22 @@ export function createGoosedumpIntegration(
|
|
|
718
874
|
lines.push(` ${listing.id}`);
|
|
719
875
|
}
|
|
720
876
|
|
|
721
|
-
return
|
|
722
|
-
content: [{ type: 'text', text: lines.join('\n') }],
|
|
723
|
-
details: undefined,
|
|
724
|
-
};
|
|
877
|
+
return textResult(lines.join('\n'));
|
|
725
878
|
}
|
|
726
879
|
|
|
727
880
|
case 'search': {
|
|
728
881
|
const contextId = params.contextId ?? currentSessionContextId(ctx);
|
|
729
|
-
if (!contextId)
|
|
730
|
-
return {
|
|
731
|
-
content: [
|
|
732
|
-
{
|
|
733
|
-
type: 'text',
|
|
734
|
-
text: 'contextId is required for search when no current session is available',
|
|
735
|
-
},
|
|
736
|
-
],
|
|
737
|
-
details: undefined,
|
|
738
|
-
};
|
|
739
|
-
}
|
|
882
|
+
if (!contextId) return missingContextId('search');
|
|
740
883
|
|
|
741
|
-
if (!params.query)
|
|
742
|
-
return {
|
|
743
|
-
content: [{ type: 'text', text: 'query is required for search' }],
|
|
744
|
-
details: undefined,
|
|
745
|
-
};
|
|
746
|
-
}
|
|
884
|
+
if (!params.query) return textResult('query is required for search');
|
|
747
885
|
|
|
748
|
-
const result =
|
|
886
|
+
const result = goosedumpSearch(contextId, params.query, {
|
|
749
887
|
scope: params.scope ?? 'lineage',
|
|
750
|
-
grep: params.query,
|
|
751
|
-
rank: true,
|
|
752
888
|
page: params.page ?? 1,
|
|
753
889
|
});
|
|
754
890
|
|
|
755
891
|
if (result.messages.length === 0) {
|
|
756
|
-
return {
|
|
757
|
-
content: [{ type: 'text', text: `No results for "${params.query}"` }],
|
|
758
|
-
details: undefined,
|
|
759
|
-
};
|
|
892
|
+
return textResult(`No results for "${params.query}"`);
|
|
760
893
|
}
|
|
761
894
|
|
|
762
895
|
const useCompact = params.compact ?? true;
|
|
@@ -765,97 +898,48 @@ export function createGoosedumpIntegration(
|
|
|
765
898
|
: formatMessagesFull(result.messages);
|
|
766
899
|
|
|
767
900
|
const header = `Results for "${params.query}" (page ${result.page} of ${result.totalPages}):\n\n`;
|
|
768
|
-
return
|
|
769
|
-
content: [{ type: 'text', text: header + text }],
|
|
770
|
-
details: undefined,
|
|
771
|
-
};
|
|
901
|
+
return textResult(header + text);
|
|
772
902
|
}
|
|
773
903
|
|
|
774
904
|
case 'expand': {
|
|
775
905
|
const contextId = params.contextId ?? currentSessionContextId(ctx);
|
|
776
|
-
if (!contextId)
|
|
777
|
-
return {
|
|
778
|
-
content: [
|
|
779
|
-
{
|
|
780
|
-
type: 'text',
|
|
781
|
-
text: 'contextId is required for expand when no current session is available',
|
|
782
|
-
},
|
|
783
|
-
],
|
|
784
|
-
details: undefined,
|
|
785
|
-
};
|
|
786
|
-
}
|
|
906
|
+
if (!contextId) return missingContextId('expand');
|
|
787
907
|
|
|
788
908
|
if (!params.ids || params.ids.length === 0) {
|
|
789
|
-
return
|
|
790
|
-
content: [
|
|
791
|
-
{ type: 'text', text: 'ids is required for expand (array of entry IDs)' },
|
|
792
|
-
],
|
|
793
|
-
details: undefined,
|
|
794
|
-
};
|
|
909
|
+
return textResult('ids is required for expand (array of entry IDs)');
|
|
795
910
|
}
|
|
796
911
|
|
|
797
|
-
const messages =
|
|
798
|
-
|
|
799
|
-
params.ids,
|
|
800
|
-
|
|
801
|
-
);
|
|
912
|
+
const messages = goosedumpShow(contextId, {
|
|
913
|
+
scope: params.scope ?? 'lineage',
|
|
914
|
+
ids: params.ids,
|
|
915
|
+
});
|
|
802
916
|
|
|
803
917
|
if (messages.length === 0) {
|
|
804
|
-
return
|
|
805
|
-
content: [{ type: 'text', text: 'No entries found for the given IDs.' }],
|
|
806
|
-
details: undefined,
|
|
807
|
-
};
|
|
918
|
+
return textResult('No entries found for the given IDs.');
|
|
808
919
|
}
|
|
809
920
|
|
|
810
|
-
return
|
|
811
|
-
content: [{ type: 'text', text: formatMessagesFull(messages) }],
|
|
812
|
-
details: undefined,
|
|
813
|
-
};
|
|
921
|
+
return textResult(formatMessagesFull(messages));
|
|
814
922
|
}
|
|
815
923
|
|
|
816
924
|
case 'view': {
|
|
817
925
|
const contextId = params.contextId ?? currentSessionContextId(ctx);
|
|
818
|
-
if (!contextId)
|
|
819
|
-
return {
|
|
820
|
-
content: [
|
|
821
|
-
{
|
|
822
|
-
type: 'text',
|
|
823
|
-
text: 'contextId is required for view when no current session is available',
|
|
824
|
-
},
|
|
825
|
-
],
|
|
826
|
-
details: undefined,
|
|
827
|
-
};
|
|
828
|
-
}
|
|
926
|
+
if (!contextId) return missingContextId('view');
|
|
829
927
|
|
|
830
|
-
const
|
|
831
|
-
scope: params.scope ?? 'lineage',
|
|
832
|
-
});
|
|
928
|
+
const messages = goosedumpShow(contextId, { scope: params.scope ?? 'lineage' });
|
|
833
929
|
|
|
834
|
-
if (
|
|
835
|
-
return {
|
|
836
|
-
content: [{ type: 'text', text: `Session "${contextId}" has no messages.` }],
|
|
837
|
-
details: undefined,
|
|
838
|
-
};
|
|
930
|
+
if (messages.length === 0) {
|
|
931
|
+
return textResult(`Session "${contextId}" has no messages.`);
|
|
839
932
|
}
|
|
840
933
|
|
|
841
|
-
return
|
|
842
|
-
content: [{ type: 'text', text: formatMessagesFull(result.messages) }],
|
|
843
|
-
details: undefined,
|
|
844
|
-
};
|
|
934
|
+
return textResult(formatMessagesFull(messages));
|
|
845
935
|
}
|
|
846
936
|
|
|
847
937
|
default:
|
|
848
|
-
return {
|
|
849
|
-
content: [{ type: 'text', text: `Unknown action: ${params.action}` }],
|
|
850
|
-
details: undefined,
|
|
851
|
-
};
|
|
938
|
+
return textResult(`Unknown action: ${params.action}`);
|
|
852
939
|
}
|
|
853
940
|
} catch (err) {
|
|
854
941
|
const message = err instanceof Error ? err.message : String(err);
|
|
855
|
-
return {
|
|
856
|
-
content: [{ type: 'text', text: `goosedump error: ${message}` }],
|
|
857
|
-
details: undefined,
|
|
858
|
-
};
|
|
942
|
+
return textResult(`goosedump error: ${message}`);
|
|
859
943
|
}
|
|
860
944
|
},
|
|
861
945
|
}),
|
|
@@ -863,10 +947,9 @@ export function createGoosedumpIntegration(
|
|
|
863
947
|
}
|
|
864
948
|
|
|
865
949
|
pi.on('session_start', async (_event, ctx) => {
|
|
866
|
-
|
|
867
|
-
goosedumpEnabled = goosedumpReady;
|
|
950
|
+
goosedumpAvailable = checkGoosedump(ctx);
|
|
868
951
|
|
|
869
|
-
if (
|
|
952
|
+
if (goosedumpAvailable) {
|
|
870
953
|
const theme = ctx.ui.theme;
|
|
871
954
|
const dot = theme.fg('success', '●');
|
|
872
955
|
const label = theme.fg('text', 'Goosedump');
|
|
@@ -875,7 +958,7 @@ export function createGoosedumpIntegration(
|
|
|
875
958
|
});
|
|
876
959
|
|
|
877
960
|
pi.on('session_before_compact', async (event, ctx) => {
|
|
878
|
-
if (!shouldOverrideDefaultCompaction || !
|
|
961
|
+
if (!shouldOverrideDefaultCompaction || !goosedumpAvailable) return;
|
|
879
962
|
if (event.customInstructions?.trim()) return;
|
|
880
963
|
if (event.signal.aborted) return;
|
|
881
964
|
|
|
@@ -905,7 +988,7 @@ export function createGoosedumpIntegration(
|
|
|
905
988
|
});
|
|
906
989
|
|
|
907
990
|
pi.on('session_compact', async (event, ctx) => {
|
|
908
|
-
if (!
|
|
991
|
+
if (!goosedumpAvailable || !event.fromExtension) return;
|
|
909
992
|
|
|
910
993
|
const compactionEntry = event.compactionEntry;
|
|
911
994
|
if (!compactionEntry) return;
|
|
@@ -929,7 +1012,7 @@ export function createGoosedumpIntegration(
|
|
|
929
1012
|
maybePi.registerCommand?.('goosedump', {
|
|
930
1013
|
description: 'Browse coding agent session history',
|
|
931
1014
|
handler: async (_args, ctx) => {
|
|
932
|
-
if (!
|
|
1015
|
+
if (!goosedumpAvailable) {
|
|
933
1016
|
ctx.ui.notify(
|
|
934
1017
|
'goosedump is not available. Install with: npm install @jarkkojs/goosedump',
|
|
935
1018
|
'error',
|
|
@@ -945,200 +1028,7 @@ export function createGoosedumpIntegration(
|
|
|
945
1028
|
return;
|
|
946
1029
|
}
|
|
947
1030
|
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
await ctx.ui.custom<void>(
|
|
951
|
-
(tui, theme, _kb, done) => {
|
|
952
|
-
let selectedIndex = 0;
|
|
953
|
-
let mode: 'list' | 'compact' = 'list';
|
|
954
|
-
let compactLines: string[] = [];
|
|
955
|
-
let compactScroll = 0;
|
|
956
|
-
let compactTitle = '';
|
|
957
|
-
|
|
958
|
-
const COMPACT_VIEWPORT = 20;
|
|
959
|
-
|
|
960
|
-
return {
|
|
961
|
-
render(width: number): string[] {
|
|
962
|
-
const innerW = width - 4;
|
|
963
|
-
const border = theme.fg('border', '│');
|
|
964
|
-
const borderFg = (s: string) => theme.fg('border', s);
|
|
965
|
-
const dim = (s: string) => theme.fg('dim', s);
|
|
966
|
-
const accent = (s: string) => theme.fg('accent', s);
|
|
967
|
-
const text = (s: string) => theme.fg('text', s);
|
|
968
|
-
const muted = (s: string) => theme.fg('muted', s);
|
|
969
|
-
|
|
970
|
-
const lines: string[] = [];
|
|
971
|
-
|
|
972
|
-
if (mode === 'compact') {
|
|
973
|
-
const title = accent(` Compact: ${truncateToWidth(compactTitle, 40)} `);
|
|
974
|
-
const topFill = borderFg(
|
|
975
|
-
'─'.repeat(Math.max(0, width - 4 - visibleWidth(title))),
|
|
976
|
-
);
|
|
977
|
-
lines.push(`${borderFg('╭─')}${title}${topFill}${borderFg('─╮')}`);
|
|
978
|
-
|
|
979
|
-
const total = compactLines.length;
|
|
980
|
-
const maxScroll = Math.max(0, total - COMPACT_VIEWPORT);
|
|
981
|
-
if (compactScroll > maxScroll) compactScroll = maxScroll;
|
|
982
|
-
const window = compactLines.slice(
|
|
983
|
-
compactScroll,
|
|
984
|
-
compactScroll + COMPACT_VIEWPORT,
|
|
985
|
-
);
|
|
986
|
-
for (const row of window) {
|
|
987
|
-
lines.push(makeRow(text(truncateToWidth(row, innerW)), innerW, border));
|
|
988
|
-
}
|
|
989
|
-
for (let i = window.length; i < COMPACT_VIEWPORT; i++) {
|
|
990
|
-
lines.push(makeRow('', innerW, border));
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
const position =
|
|
994
|
-
total > COMPACT_VIEWPORT
|
|
995
|
-
? `${compactScroll + 1}-${Math.min(compactScroll + COMPACT_VIEWPORT, total)} of ${total}`
|
|
996
|
-
: `${total} line${total === 1 ? '' : 's'}`;
|
|
997
|
-
lines.push(makeRow('', innerW, border));
|
|
998
|
-
lines.push(makeRow(dim(`↑↓ scroll esc back (${position})`), innerW, border));
|
|
999
|
-
lines.push(
|
|
1000
|
-
`${borderFg('╰')}${borderFg('─'.repeat(width - 2))}${borderFg('╯')}`,
|
|
1001
|
-
);
|
|
1002
|
-
return lines;
|
|
1003
|
-
}
|
|
1004
|
-
|
|
1005
|
-
// Top border
|
|
1006
|
-
const title = accent(' Goosedump Sessions ');
|
|
1007
|
-
const topFill = borderFg(
|
|
1008
|
-
'─'.repeat(Math.max(0, width - 4 - visibleWidth(title))),
|
|
1009
|
-
);
|
|
1010
|
-
lines.push(`${borderFg('╭─')}${title}${topFill}${borderFg('─╮')}`);
|
|
1011
|
-
|
|
1012
|
-
// Session count
|
|
1013
|
-
const countInfo = `${listings.length} session${listings.length === 1 ? '' : 's'}`;
|
|
1014
|
-
lines.push(makeRow(`${muted(countInfo)}`, innerW, border));
|
|
1015
|
-
|
|
1016
|
-
lines.push(makeRow('', innerW, border));
|
|
1017
|
-
|
|
1018
|
-
// Session list
|
|
1019
|
-
const maxItems = Math.min(listings.length, 20);
|
|
1020
|
-
for (let i = 0; i < maxItems; i++) {
|
|
1021
|
-
const listing = listings[i];
|
|
1022
|
-
const isSelected = i === selectedIndex;
|
|
1023
|
-
const cursor = isSelected ? accent('▶') : ' ';
|
|
1024
|
-
const idText = isSelected ? accent(listing.id) : text(listing.id);
|
|
1025
|
-
const line = ` ${cursor} ${idText}`;
|
|
1026
|
-
lines.push(makeRow(truncateToWidth(line, innerW), innerW, border));
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
if (listings.length > 20) {
|
|
1030
|
-
lines.push(
|
|
1031
|
-
makeRow(dim(` ... and ${listings.length - 20} more`), innerW, border),
|
|
1032
|
-
);
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
// Footer
|
|
1036
|
-
lines.push(makeRow('', innerW, border));
|
|
1037
|
-
lines.push(
|
|
1038
|
-
makeRow(
|
|
1039
|
-
dim('↑↓ navigate enter compact shift+enter resume esc close'),
|
|
1040
|
-
innerW,
|
|
1041
|
-
border,
|
|
1042
|
-
),
|
|
1043
|
-
);
|
|
1044
|
-
|
|
1045
|
-
// Bottom border
|
|
1046
|
-
lines.push(`${borderFg('╰')}${borderFg('─'.repeat(width - 2))}${borderFg('╯')}`);
|
|
1047
|
-
|
|
1048
|
-
return lines;
|
|
1049
|
-
},
|
|
1050
|
-
|
|
1051
|
-
handleInput(data: string): void {
|
|
1052
|
-
if (mode === 'compact') {
|
|
1053
|
-
if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl('c'))) {
|
|
1054
|
-
mode = 'list';
|
|
1055
|
-
tui.requestRender();
|
|
1056
|
-
return;
|
|
1057
|
-
}
|
|
1058
|
-
if (matchesKey(data, Key.up)) {
|
|
1059
|
-
compactScroll = Math.max(0, compactScroll - 1);
|
|
1060
|
-
tui.requestRender();
|
|
1061
|
-
return;
|
|
1062
|
-
}
|
|
1063
|
-
if (matchesKey(data, Key.down)) {
|
|
1064
|
-
compactScroll += 1;
|
|
1065
|
-
tui.requestRender();
|
|
1066
|
-
return;
|
|
1067
|
-
}
|
|
1068
|
-
if (matchesKey(data, Key.pageUp)) {
|
|
1069
|
-
compactScroll = Math.max(0, compactScroll - COMPACT_VIEWPORT);
|
|
1070
|
-
tui.requestRender();
|
|
1071
|
-
return;
|
|
1072
|
-
}
|
|
1073
|
-
if (matchesKey(data, Key.pageDown)) {
|
|
1074
|
-
compactScroll += COMPACT_VIEWPORT;
|
|
1075
|
-
tui.requestRender();
|
|
1076
|
-
return;
|
|
1077
|
-
}
|
|
1078
|
-
return;
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl('c'))) {
|
|
1082
|
-
done(undefined);
|
|
1083
|
-
return;
|
|
1084
|
-
}
|
|
1085
|
-
|
|
1086
|
-
if (matchesKey(data, Key.up)) {
|
|
1087
|
-
selectedIndex = Math.max(0, selectedIndex - 1);
|
|
1088
|
-
tui.requestRender();
|
|
1089
|
-
return;
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
if (matchesKey(data, Key.down)) {
|
|
1093
|
-
selectedIndex = Math.min(listings.length - 1, selectedIndex + 1);
|
|
1094
|
-
tui.requestRender();
|
|
1095
|
-
return;
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
if (matchesKey(data, Key.shift(Key.enter))) {
|
|
1099
|
-
resumePath = listings[selectedIndex]?.path;
|
|
1100
|
-
done(undefined);
|
|
1101
|
-
return;
|
|
1102
|
-
}
|
|
1103
|
-
|
|
1104
|
-
if (matchesKey(data, Key.enter) || matchesKey(data, Key.return)) {
|
|
1105
|
-
const listing = listings[selectedIndex];
|
|
1106
|
-
if (!listing) return;
|
|
1107
|
-
compactTitle = listing.id;
|
|
1108
|
-
compactScroll = 0;
|
|
1109
|
-
try {
|
|
1110
|
-
const summary = goosedumpCompact(listing.id, { scope: 'lineage' }).trim();
|
|
1111
|
-
compactLines = summary
|
|
1112
|
-
? summary.split('\n')
|
|
1113
|
-
: ['No compact summary available for this session.'];
|
|
1114
|
-
} catch (err) {
|
|
1115
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
1116
|
-
compactLines = [`Failed to compact session: ${msg}`];
|
|
1117
|
-
}
|
|
1118
|
-
mode = 'compact';
|
|
1119
|
-
tui.requestRender();
|
|
1120
|
-
return;
|
|
1121
|
-
}
|
|
1122
|
-
},
|
|
1123
|
-
|
|
1124
|
-
invalidate(): void {},
|
|
1125
|
-
};
|
|
1126
|
-
|
|
1127
|
-
function makeRow(content: string, innerW: number, border: string): string {
|
|
1128
|
-
const line = truncateToWidth(content, innerW);
|
|
1129
|
-
const pad = Math.max(0, innerW - visibleWidth(line));
|
|
1130
|
-
return `${border} ${line}${' '.repeat(pad)} ${border}`;
|
|
1131
|
-
}
|
|
1132
|
-
},
|
|
1133
|
-
{
|
|
1134
|
-
overlay: true,
|
|
1135
|
-
overlayOptions: {
|
|
1136
|
-
anchor: 'center',
|
|
1137
|
-
width: 72,
|
|
1138
|
-
margin: 2,
|
|
1139
|
-
},
|
|
1140
|
-
},
|
|
1141
|
-
);
|
|
1031
|
+
const resumePath = await runSessionBrowser(ctx, listings);
|
|
1142
1032
|
|
|
1143
1033
|
if (resumePath) {
|
|
1144
1034
|
await ctx.switchSession(resumePath);
|