pi-goosedump 0.12.18 → 0.12.20

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 +13 -11
  2. package/dist/index.ts +61 -69
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -93,17 +93,19 @@ Optional settings live under `goosedump` in:
93
93
  ```
94
94
 
95
95
  `turnsBeforeCompact` is an integer from 0 to 255. `0` disables scheduled
96
- compaction. Each fully settled agent run counts as one turn; intermediate model
97
- responses that call tools do not. The countdown is restored when a session
98
- resumes and appears in the footer with a status dot (green when >25% turns
99
- remain, yellow when ≤25%, red when ≤12.5%) followed by a hexadecimal countdown.
100
- `● GD:0A` means ten turns remain, `● GD:00` is ready to compact, and `● GD:--`
101
- is disabled. The footer adds `queued` while compaction is waiting for the active
102
- agent run to settle and `compacting` while the summary is being built. Pi remains
103
- the sole scheduler for its normal context threshold. Whichever limit is reached
104
- first compacts once, and every successful compaction resets the countdown. A
105
- failed or cancelled compaction leaves the counter armed at zero and is not
106
- retried in the same settlement.
96
+ compaction. Each completed model response counts as one turn, including responses
97
+ that call tools. The countdown is restored when a session resumes and appears in
98
+ the footer with a status dot (green when >25% turns remain, yellow when ≤25%, red
99
+ when ≤12.5%) followed by a hexadecimal countdown. `● GD:0A` means ten turns
100
+ remain, `● GD:00` is ready to compact, and `● GD:--` is disabled. The footer
101
+ adds `queued` while compaction is waiting to start and `compacting` while the
102
+ summary is being built. When the counter hits zero mid tool-loop, compaction
103
+ starts immediately after the current tool batch is persisted and the task is
104
+ resumed automatically. When the counter hits zero on a terminal turn, compaction
105
+ starts after the agent has settled. Pi remains the sole scheduler for its normal
106
+ context threshold. Whichever limit is reached first compacts once, and every
107
+ successful compaction resets the countdown. A failed or cancelled compaction
108
+ leaves the counter armed at zero and retries on the next tool turn or settlement.
107
109
 
108
110
  `summaryMaxTokens` is an integer from 512 to 65536 (default 4096). The effective
109
111
  summary budget is the lower of this value and 80% of Pi's `compaction.reserveTokens`,
package/dist/index.ts CHANGED
@@ -590,26 +590,11 @@ function currentSessionTarget(ctx) {
590
590
  function isCompactionEntry(entry) {
591
591
  return entry.type === "compaction";
592
592
  }
593
- var COMPLETED_AGENT_RUN_ENTRY = "goosedump-completed-agent-run";
594
593
  function isMessageEntry(entry) {
595
594
  return entry.type === "message";
596
595
  }
597
- function isTerminalAssistant(message) {
598
- if (message.role !== "assistant")
599
- return false;
600
- if (message.stopReason !== "stop" && message.stopReason !== "length")
601
- return false;
602
- return !(Array.isArray(message.content) && message.content.some((part) => isRecord(part) && part.type === "toolCall"));
603
- }
604
- function completedAgentRunData(entry) {
605
- if (entry.type !== "custom" || entry.customType !== COMPLETED_AGENT_RUN_ENTRY)
606
- return;
607
- if (!isRecord(entry.data) || entry.data.version !== 1)
608
- return;
609
- const startedAfterEntryId = entry.data.startedAfterEntryId;
610
- if (startedAfterEntryId !== null && typeof startedAfterEntryId !== "string")
611
- return;
612
- return { version: 1, startedAfterEntryId };
596
+ function isCompletedAssistant(message) {
597
+ return message.role === "assistant" && message.stopReason !== "error" && message.stopReason !== "aborted";
613
598
  }
614
599
  var SUMMARIZABLE_ENTRY_TYPES = new Set(["message", "custom_message", "branch_summary"]);
615
600
  function piKeepRecentTokens(cwd, includeProject) {
@@ -681,7 +666,7 @@ function activeHistoryStartIndex(branchEntries) {
681
666
  const compaction = latestCompaction(branchEntries);
682
667
  return compaction ? compactionBoundaryStartIndex(branchEntries, compaction) : 0;
683
668
  }
684
- function completedRunsSinceCompaction(branchEntries) {
669
+ function completedTurnsSinceCompaction(branchEntries) {
685
670
  let start = 0;
686
671
  for (let i = branchEntries.length - 1;i >= 0; i -= 1) {
687
672
  if (isCompactionEntry(branchEntries[i])) {
@@ -689,27 +674,10 @@ function completedRunsSinceCompaction(branchEntries) {
689
674
  break;
690
675
  }
691
676
  }
692
- const firstMarkerIndex = branchEntries.findIndex((entry, index) => index >= start && completedAgentRunData(entry) !== undefined);
693
- if (firstMarkerIndex < 0) {
694
- let completed2 = 0;
695
- for (let i = start;i < branchEntries.length; i += 1) {
696
- const entry = branchEntries[i];
697
- if (isMessageEntry(entry) && isTerminalAssistant(entry.message))
698
- completed2 += 1;
699
- }
700
- return completed2;
701
- }
702
- const firstMarker = completedAgentRunData(branchEntries[firstMarkerIndex]);
703
- const boundaryIndex = firstMarker?.startedAfterEntryId ? branchEntries.findIndex((entry) => entry.id === firstMarker.startedAfterEntryId) : start - 1;
704
- const legacyEnd = boundaryIndex >= start ? boundaryIndex + 1 : start;
705
677
  let completed = 0;
706
- for (let i = start;i < legacyEnd; i += 1) {
678
+ for (let i = start;i < branchEntries.length; i += 1) {
707
679
  const entry = branchEntries[i];
708
- if (isMessageEntry(entry) && isTerminalAssistant(entry.message))
709
- completed += 1;
710
- }
711
- for (let i = firstMarkerIndex;i < branchEntries.length; i += 1) {
712
- if (completedAgentRunData(branchEntries[i]) !== undefined)
680
+ if (isMessageEntry(entry) && isCompletedAssistant(entry.message))
713
681
  completed += 1;
714
682
  }
715
683
  return completed;
@@ -877,7 +845,7 @@ async function runSettingsEditor(ctx) {
877
845
  const specs = [
878
846
  {
879
847
  key: "turnsBeforeCompact",
880
- description: "Completed agent runs before compaction; 0 disables.",
848
+ description: "LLM turns before compaction; 0 disables.",
881
849
  min: TURNS_BEFORE_COMPACT_MIN,
882
850
  max: TURNS_BEFORE_COMPACT_MAX
883
851
  },
@@ -1443,8 +1411,7 @@ function createGoosedumpIntegration(options = {}) {
1443
1411
  let operationGeneration = 0;
1444
1412
  let activeSessionFile = null;
1445
1413
  let sessionActive = false;
1446
- let agentRunObserved = false;
1447
- let agentRunStartEntryId = null;
1414
+ let activePi = null;
1448
1415
  const rememberQueue = [];
1449
1416
  let rememberRunning = null;
1450
1417
  function enqueueRemember(target, reportError) {
@@ -1482,9 +1449,9 @@ function createGoosedumpIntegration(options = {}) {
1482
1449
  }
1483
1450
  function syncCompactCounters(ctx) {
1484
1451
  compactSettings = resolveGoosedumpSettings(ctx.cwd, projectSettingsTrusted(ctx));
1485
- const completedRuns = completedRunsSinceCompaction(ctx.sessionManager.getBranch());
1486
- turnsRemaining = Math.max(0, compactSettings.turnsBeforeCompact - completedRuns);
1487
- counterTriggered = compactSettings.turnsBeforeCompact > 0 && completedRuns >= compactSettings.turnsBeforeCompact;
1452
+ const completedTurns = completedTurnsSinceCompaction(ctx.sessionManager.getBranch());
1453
+ turnsRemaining = Math.max(0, compactSettings.turnsBeforeCompact - completedTurns);
1454
+ counterTriggered = compactSettings.turnsBeforeCompact > 0 && completedTurns >= compactSettings.turnsBeforeCompact;
1488
1455
  }
1489
1456
  function updateStatus(ctx) {
1490
1457
  if (!goosedumpAvailable)
@@ -1509,13 +1476,44 @@ function createGoosedumpIntegration(options = {}) {
1509
1476
  compactionState = state;
1510
1477
  updateStatus(ctx);
1511
1478
  }
1512
- function noteCompletedRun(ctx) {
1479
+ function noteCompletedTurn(ctx) {
1513
1480
  if (!goosedumpAvailable || !shouldOverrideDefaultCompaction)
1514
1481
  return;
1515
1482
  syncCompactCounters(ctx);
1516
1483
  updateStatus(ctx);
1517
1484
  queueTurnCompaction(ctx, settlementGeneration + 1);
1518
1485
  }
1486
+ function resumeAfterSuccessfulCompaction(ctx, resume) {
1487
+ if (!resume || !activePi || ctx.hasPendingMessages())
1488
+ return;
1489
+ activePi.sendMessage({
1490
+ customType: "goosedump-autocompact-resume",
1491
+ content: "The session was compacted to reduce context size. Continue the task you were working on from where you left off.",
1492
+ display: false
1493
+ }, { triggerTurn: true });
1494
+ }
1495
+ function tryMidRunCounterCompaction(ctx) {
1496
+ if (!goosedumpAvailable || !shouldOverrideDefaultCompaction)
1497
+ return false;
1498
+ if (!counterTriggered)
1499
+ return false;
1500
+ if (ctx.hasPendingMessages())
1501
+ return false;
1502
+ if (!hasCompactableHistory(ctx))
1503
+ return false;
1504
+ if (compactionState.phase === "idle") {
1505
+ queueTurnCompaction(ctx, settlementGeneration);
1506
+ } else if (compactionState.phase === "queued") {
1507
+ setCompactionState(ctx, {
1508
+ phase: "queued",
1509
+ request: {
1510
+ ...compactionState.request,
1511
+ eligibleSettlement: Math.min(compactionState.request.eligibleSettlement, settlementGeneration)
1512
+ }
1513
+ });
1514
+ }
1515
+ return startQueuedCompaction(ctx, { resumeAfter: true, allowActiveRun: true });
1516
+ }
1519
1517
  function checkGoosedump(ctx) {
1520
1518
  try {
1521
1519
  execFileSync(process.execPath, [resolveGoosedumpBinary(), "--version"], { stdio: "ignore" });
@@ -1567,7 +1565,7 @@ function createGoosedumpIntegration(options = {}) {
1567
1565
  }
1568
1566
  });
1569
1567
  }
1570
- function startQueuedCompaction(ctx) {
1568
+ function startQueuedCompaction(ctx, options2 = {}) {
1571
1569
  if (compactionState.phase !== "queued")
1572
1570
  return false;
1573
1571
  const request = compactionState.request;
@@ -1575,7 +1573,9 @@ function createGoosedumpIntegration(options = {}) {
1575
1573
  setCompactionState(ctx, { phase: "idle" });
1576
1574
  return false;
1577
1575
  }
1578
- if (!ctx.isIdle() || ctx.hasPendingMessages())
1576
+ if (ctx.hasPendingMessages())
1577
+ return false;
1578
+ if (!options2.allowActiveRun && !ctx.isIdle())
1579
1579
  return false;
1580
1580
  if (!requestHasCompactableHistory(ctx, request.keep)) {
1581
1581
  setCompactionState(ctx, { phase: "idle" });
@@ -1584,6 +1584,7 @@ function createGoosedumpIntegration(options = {}) {
1584
1584
  const operation = ++operationGeneration;
1585
1585
  const generation = request.sessionGeneration;
1586
1586
  const sessionFile = request.sessionFile;
1587
+ const resumeAfter = options2.resumeAfter === true;
1587
1588
  setCompactionState(ctx, {
1588
1589
  phase: "running",
1589
1590
  request,
@@ -1598,11 +1599,16 @@ function createGoosedumpIntegration(options = {}) {
1598
1599
  if (!runningOperationIsCurrent(operation, generation, sessionFile))
1599
1600
  return;
1600
1601
  setCompactionState(ctx, { phase: "idle" });
1602
+ syncCompactCounters(ctx);
1603
+ updateStatus(ctx);
1604
+ resumeAfterSuccessfulCompaction(ctx, resumeAfter);
1601
1605
  },
1602
1606
  onError: (error) => {
1603
1607
  if (!runningOperationIsCurrent(operation, generation, sessionFile))
1604
1608
  return;
1605
1609
  setCompactionState(ctx, { phase: "idle" });
1610
+ syncCompactCounters(ctx);
1611
+ updateStatus(ctx);
1606
1612
  const message = error instanceof Error ? error.message : String(error);
1607
1613
  if (ctx.hasUI)
1608
1614
  ctx.ui.notify(`goosedump compaction failed: ${message}`, "error");
@@ -1638,6 +1644,7 @@ function createGoosedumpIntegration(options = {}) {
1638
1644
  return startQueuedCompaction(ctx) ? "started" : "queued";
1639
1645
  }
1640
1646
  function register(pi) {
1647
+ activePi = pi;
1641
1648
  if (shouldRegisterTool) {
1642
1649
  pi.registerTool(defineTool({
1643
1650
  name: "goose_remember",
@@ -1989,6 +1996,8 @@ ${lines.join(`
1989
1996
  if (!goosedumpAvailable || !shouldOverrideDefaultCompaction)
1990
1997
  return;
1991
1998
  settlementGeneration += 1;
1999
+ syncCompactCounters(ctx);
2000
+ updateStatus(ctx);
1992
2001
  if (compactionState.phase === "running" && compactionState.owner === "pi") {
1993
2002
  const request = compactionState.request;
1994
2003
  setCompactionState(ctx, request && requestIsCurrent(request) ? {
@@ -2012,8 +2021,6 @@ ${lines.join(`
2012
2021
  settlementGeneration = 0;
2013
2022
  activeSessionFile = ctx.sessionManager.getSessionFile?.() ?? null;
2014
2023
  sessionActive = true;
2015
- agentRunObserved = false;
2016
- agentRunStartEntryId = null;
2017
2024
  compactionState = { phase: "idle" };
2018
2025
  goosedumpAvailable = checkGoosedump(ctx);
2019
2026
  notifyInvalidSettings(ctx);
@@ -2027,8 +2034,6 @@ ${lines.join(`
2027
2034
  settlementGeneration = 0;
2028
2035
  activeSessionFile = null;
2029
2036
  sessionActive = false;
2030
- agentRunObserved = false;
2031
- agentRunStartEntryId = null;
2032
2037
  compactionState = { phase: "idle" };
2033
2038
  });
2034
2039
  pi.on("session_tree", async (_event, ctx) => {
@@ -2036,8 +2041,6 @@ ${lines.join(`
2036
2041
  settlementGeneration = 0;
2037
2042
  activeSessionFile = ctx.sessionManager.getSessionFile?.() ?? null;
2038
2043
  sessionActive = true;
2039
- agentRunObserved = false;
2040
- agentRunStartEntryId = null;
2041
2044
  compactionState = { phase: "idle" };
2042
2045
  syncCompactCounters(ctx);
2043
2046
  updateStatus(ctx);
@@ -2045,10 +2048,6 @@ ${lines.join(`
2045
2048
  deferQueuedCompaction(ctx);
2046
2049
  });
2047
2050
  pi.on("agent_start", async (_event, ctx) => {
2048
- if (!agentRunObserved) {
2049
- agentRunObserved = true;
2050
- agentRunStartEntryId = ctx.sessionManager.getBranch().at(-1)?.id ?? null;
2051
- }
2052
2051
  if (compactionState.phase !== "running" || compactionState.owner !== "pi")
2053
2052
  return;
2054
2053
  const request = compactionState.request;
@@ -2057,20 +2056,13 @@ ${lines.join(`
2057
2056
  request: { ...request, eligibleSettlement: settlementGeneration + 1 }
2058
2057
  } : { phase: "idle" });
2059
2058
  });
2059
+ pi.on("turn_end", async (event, ctx) => {
2060
+ if (isCompletedAssistant(event.message))
2061
+ noteCompletedTurn(ctx);
2062
+ if (event.toolResults.length > 0)
2063
+ tryMidRunCounterCompaction(ctx);
2064
+ });
2060
2065
  pi.on("agent_settled", async (_event, ctx) => {
2061
- if (agentRunObserved) {
2062
- const countRun = shouldOverrideDefaultCompaction && compactSettings.turnsBeforeCompact > 0;
2063
- if (countRun) {
2064
- pi.appendEntry(COMPLETED_AGENT_RUN_ENTRY, {
2065
- version: 1,
2066
- startedAfterEntryId: agentRunStartEntryId
2067
- });
2068
- }
2069
- agentRunObserved = false;
2070
- agentRunStartEntryId = null;
2071
- if (countRun)
2072
- noteCompletedRun(ctx);
2073
- }
2074
2066
  handleAgentSettled(ctx);
2075
2067
  });
2076
2068
  pi.on("session_before_compact", async (event, ctx) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goosedump",
3
- "version": "0.12.18",
3
+ "version": "0.12.20",
4
4
  "description": "Pi extension for goosedump session search, durable memory, and compaction",
5
5
  "keywords": [
6
6
  "compaction",
@@ -38,7 +38,7 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@earendil-works/pi-tui": "^0.82.1",
41
- "@jarkkojs/goosedump": "^0.12.18",
41
+ "@jarkkojs/goosedump": "^0.12.20",
42
42
  "@sinclair/typebox": "^0.34.49"
43
43
  },
44
44
  "devDependencies": {