pi-goosedump 0.10.7 → 0.10.9

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.
Files changed (3) hide show
  1. package/README.md +2 -2
  2. package/index.ts +62 -13
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -22,8 +22,8 @@ report that they are unavailable.
22
22
  - **`goose_get`** expands entry IDs returned by `goose_search` or `goose_grep`.
23
23
  - **`goose_grep`** filters session messages with a glob pattern.
24
24
  - **`goose_recall`** searches memory harvested by earlier compactions. It
25
- searches the current working directory by default and returns full entries
26
- with provenance.
25
+ searches the current working directory by default and returns remembered
26
+ excerpts with provenance within a 4,096-byte/token-safe output budget.
27
27
  - **`goose_compact`** starts asynchronous compaction of the current Pi session.
28
28
  Set `keep` to preserve the last N user turns verbatim.
29
29
 
package/index.ts CHANGED
@@ -78,6 +78,13 @@ interface GoosedumpMemoryHit {
78
78
  source_path: string;
79
79
  }
80
80
 
81
+ const RECALL_TOKEN_BUDGET = 4096;
82
+ // Every non-empty token occupies at least one UTF-8 byte, so this byte cap is
83
+ // also a conservative hard cap on the number of tokens returned to Pi.
84
+ const RECALL_MAX_BYTES = RECALL_TOKEN_BUDGET;
85
+ const MEMORY_HIT_SEPARATOR = '\n\n---\n\n';
86
+ const MEMORY_TEXT_TRUNCATED = '\n[Entry text truncated]';
87
+
81
88
  // A `content` part of a pi-native message line.
82
89
  interface PiPartJson {
83
90
  type: string;
@@ -395,13 +402,44 @@ function parseMemoryHits(output: string): GoosedumpMemoryHit[] {
395
402
  );
396
403
  }
397
404
 
398
- function formatMemoryHits(hits: GoosedumpMemoryHit[]): string {
399
- return hits
400
- .map(
401
- (hit) =>
402
- `[${hit.hash}] ${hit.kind} (${hit.provider}:${hit.context_id}/${hit.entry_id})\nWorking directory: ${hit.path}\nSource: ${hit.source_path}\n${hit.text}`,
403
- )
404
- .join('\n\n---\n\n');
405
+ function formatMemoryHit(hit: GoosedumpMemoryHit, maxBytes: number): string {
406
+ const provenance =
407
+ `[${hit.hash}] ${hit.kind} (${hit.provider}:${hit.context_id}/${hit.entry_id})\n` +
408
+ `Working directory: ${hit.path}\nSource: ${hit.source_path}\n`;
409
+ const full = `${provenance}${hit.text}`;
410
+ if (Buffer.byteLength(full) <= maxBytes) return full;
411
+
412
+ const provenanceBytes = Buffer.byteLength(provenance);
413
+ const markerBytes = Buffer.byteLength(MEMORY_TEXT_TRUNCATED);
414
+ if (provenanceBytes + markerBytes >= maxBytes) return utf8Prefix(provenance, maxBytes);
415
+
416
+ const textBytes = maxBytes - provenanceBytes - markerBytes;
417
+ return `${provenance}${utf8Prefix(hit.text, textBytes)}${MEMORY_TEXT_TRUNCATED}`;
418
+ }
419
+
420
+ function formatMemoryHits(hits: GoosedumpMemoryHit[], maxBytes = RECALL_MAX_BYTES): string {
421
+ const formatted: string[] = [];
422
+ let remainingBytes = maxBytes;
423
+ const separatorBytes = Buffer.byteLength(MEMORY_HIT_SEPARATOR);
424
+
425
+ for (let index = 0; index < hits.length; index += 1) {
426
+ if (index > 0) {
427
+ if (remainingBytes < separatorBytes) break;
428
+ formatted.push(MEMORY_HIT_SEPARATOR);
429
+ remainingBytes -= separatorBytes;
430
+ }
431
+
432
+ const remainingHits = hits.length - index;
433
+ const futureSeparators = separatorBytes * (remainingHits - 1);
434
+ const hitBytes = Math.floor((remainingBytes - futureSeparators) / remainingHits);
435
+ if (hitBytes <= 0) break;
436
+
437
+ const hit = formatMemoryHit(hits[index], hitBytes);
438
+ formatted.push(hit);
439
+ remainingBytes -= Buffer.byteLength(hit);
440
+ }
441
+
442
+ return formatted.join('');
405
443
  }
406
444
 
407
445
  const ANSI_SGR = new RegExp(String.fromCharCode(27) + '\\[[0-9;]*m', 'g');
@@ -461,10 +499,14 @@ function runGoosedump(args: string[]): string {
461
499
  // Non-blocking variant for the compact path: goosedump compact runs an ML
462
500
  // model and can take many seconds, so execFileSync would freeze Pi's event
463
501
  // loop (and its compaction spinner). execFile keeps the loop free to render.
464
- function runGoosedumpAsync(args: string[], signal?: AbortSignal): Promise<GoosedumpCommandOutput> {
502
+ function runGoosedumpAsync(
503
+ args: string[],
504
+ signal?: AbortSignal,
505
+ executablePath = resolveNativeGoosedumpBinary(),
506
+ ): Promise<GoosedumpCommandOutput> {
465
507
  return new Promise((resolve, reject) => {
466
508
  execFile(
467
- resolveNativeGoosedumpBinary(),
509
+ executablePath,
468
510
  args,
469
511
  {
470
512
  encoding: 'utf-8',
@@ -542,11 +584,12 @@ async function goosedumpRecall(
542
584
  query: string,
543
585
  options: { provider?: string; path?: string; limit?: number } = {},
544
586
  signal?: AbortSignal,
587
+ executablePath?: string,
545
588
  ): Promise<{ hits: GoosedumpMemoryHit[]; warning: string }> {
546
589
  const args = ['recall', query, '--limit', String(options.limit ?? 5)];
547
590
  if (options.provider) args.push('--provider', options.provider);
548
591
  if (options.path) args.push('--path', options.path);
549
- const output = await runGoosedumpAsync(args, signal);
592
+ const output = await runGoosedumpAsync(args, signal, executablePath);
550
593
  return { hits: parseMemoryHits(output.stdout), warning: output.stderr.trim() };
551
594
  }
552
595
 
@@ -604,13 +647,14 @@ async function goosedumpCompact(
604
647
  target: string,
605
648
  options: { scope?: string; from?: string; until: string },
606
649
  signal?: AbortSignal,
650
+ executablePath?: string,
607
651
  ): Promise<{ summary: string; warning: string }> {
608
652
  const scope = options.scope ?? 'lineage';
609
653
  const args = ['compact', target, '--scope', scope];
610
654
  if (options.from) args.push('--from', options.from);
611
655
  args.push('--until', options.until);
612
656
 
613
- const output = await runGoosedumpAsync(args, signal);
657
+ const output = await runGoosedumpAsync(args, signal, executablePath);
614
658
  for (const line of output.stdout.split('\n')) {
615
659
  if (!line) continue;
616
660
  try {
@@ -628,6 +672,7 @@ async function goosedumpCompact(
628
672
  export interface GoosedumpIntegrationOptions {
629
673
  registerTool?: boolean;
630
674
  overrideDefaultCompaction?: boolean;
675
+ nativeExecutablePath?: string;
631
676
  }
632
677
 
633
678
  interface GoosedumpCompactRange {
@@ -930,6 +975,7 @@ async function buildGoosedumpCompaction(
930
975
  reserveTokens: number,
931
976
  keep?: number,
932
977
  signal?: AbortSignal,
978
+ executablePath?: string,
933
979
  ): Promise<
934
980
  CompactionResult<GoosedumpCompactionDetails> | { cancel: true; reason: string } | undefined
935
981
  > {
@@ -947,7 +993,7 @@ async function buildGoosedumpCompaction(
947
993
  const range = buildCompactRange(branchEntries, firstKeptEntryId);
948
994
  if (!range) return undefined;
949
995
 
950
- const generated = await goosedumpCompact(target, range, signal);
996
+ const generated = await goosedumpCompact(target, range, signal, executablePath);
951
997
  if (!generated.summary) return undefined;
952
998
  const summary = mergeCompactionSummary(
953
999
  preparation.previousSummary,
@@ -1426,6 +1472,7 @@ export function createGoosedumpIntegration(
1426
1472
  ): GoosedumpIntegration {
1427
1473
  const shouldRegisterTool = options.registerTool ?? true;
1428
1474
  const shouldOverrideDefaultCompaction = options.overrideDefaultCompaction ?? true;
1475
+ const nativeExecutablePath = options.nativeExecutablePath;
1429
1476
 
1430
1477
  let goosedumpAvailable = false;
1431
1478
  let compactSettings = DEFAULT_GOOSEDUMP_SETTINGS;
@@ -1510,7 +1557,7 @@ export function createGoosedumpIntegration(
1510
1557
  name: 'goose_recall',
1511
1558
  label: 'goose_recall',
1512
1559
  description:
1513
- 'Recall durable context harvested from earlier compactions. Searches the current project by default and returns full remembered entries with provenance.',
1560
+ 'Recall durable context harvested from earlier compactions. Searches the current project by default and returns bounded remembered excerpts with provenance.',
1514
1561
  promptSnippet: 'Recall durable context from earlier sessions',
1515
1562
  promptGuidelines: [
1516
1563
  'Use goose_recall for decisions, evidence, and context that may have disappeared from the current session.',
@@ -1553,6 +1600,7 @@ export function createGoosedumpIntegration(
1553
1600
  limit: params.limit,
1554
1601
  },
1555
1602
  signal,
1603
+ nativeExecutablePath,
1556
1604
  );
1557
1605
  if (warning && ctx.hasUI) ctx.ui.notify(warning, 'warning');
1558
1606
  if (hits.length === 0) return textResult(`No durable memory for "${params.query}"`);
@@ -1842,6 +1890,7 @@ export function createGoosedumpIntegration(
1842
1890
  piReserveTokens(ctx.cwd),
1843
1891
  keep,
1844
1892
  event.signal,
1893
+ nativeExecutablePath,
1845
1894
  );
1846
1895
  if (compaction && 'cancel' in compaction) {
1847
1896
  if (ctx.hasUI) ctx.ui.notify(compaction.reason, 'info');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goosedump",
3
- "version": "0.10.7",
3
+ "version": "0.10.9",
4
4
  "description": "Pi extension for goosedump-backed session-history search, entry expansion, and compaction",
5
5
  "keywords": [
6
6
  "compaction",
@@ -33,7 +33,7 @@
33
33
  },
34
34
  "dependencies": {
35
35
  "@earendil-works/pi-tui": "^0.78.0",
36
- "@jarkkojs/goosedump": "^0.10.7",
36
+ "@jarkkojs/goosedump": "^0.10.9",
37
37
  "@sinclair/typebox": "^0.34.49"
38
38
  },
39
39
  "devDependencies": {