pi-goosedump 0.12.15 → 0.12.17

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 +5 -4
  2. package/dist/index.ts +118 -56
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -97,10 +97,11 @@ compaction. Each completed model response counts as one turn, including response
97
97
  that call tools. The countdown is restored when a session resumes and appears in
98
98
  the footer with a status dot (green when >25% turns remain, yellow when ≤25%, red
99
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. Pi remains the
101
- sole scheduler for its normal context threshold. Whichever limit is reached first
102
- compacts once, and every successful compaction resets the countdown. A turn or
103
- tool request waits until the agent run is fully settled. A failed or cancelled
100
+ remain, `● GD:00` is ready to compact, and `● GD:--` is disabled. The footer
101
+ adds `queued` while compaction is waiting for the active agent run to settle and
102
+ `compacting` while the summary is being built. Pi remains the sole scheduler for
103
+ its normal context threshold. Whichever limit is reached first compacts once,
104
+ and every successful compaction resets the countdown. A failed or cancelled
104
105
  compaction leaves the counter armed at zero and is not retried in the same
105
106
  settlement.
106
107
 
package/dist/index.ts CHANGED
@@ -1467,9 +1467,14 @@ function createGoosedumpIntegration(options = {}) {
1467
1467
  }
1468
1468
  const dot = ctx.ui.theme.fg(dotColor, "●");
1469
1469
  const counter = active ? Math.max(0, turnsRemaining).toString(16).toUpperCase().padStart(2, "0") : "--";
1470
- const label = ctx.ui.theme.fg("text", `${ctx.ui.theme.bold?.("GD") ?? "GD"}:${counter}`);
1470
+ const activity = compactionState.phase === "queued" ? " queued" : compactionState.phase === "running" ? " compacting" : "";
1471
+ const label = ctx.ui.theme.fg("text", `${ctx.ui.theme.bold?.("GD") ?? "GD"}:${counter}${activity}`);
1471
1472
  ctx.ui.setStatus("goosedump", `${dot} ${label}`);
1472
1473
  }
1474
+ function setCompactionState(ctx, state) {
1475
+ compactionState = state;
1476
+ updateStatus(ctx);
1477
+ }
1473
1478
  function noteCompletedTurn(ctx) {
1474
1479
  if (!goosedumpAvailable || !shouldOverrideDefaultCompaction)
1475
1480
  return;
@@ -1497,6 +1502,19 @@ function createGoosedumpIntegration(options = {}) {
1497
1502
  function runningOperationIsCurrent(operation, generation, sessionFile) {
1498
1503
  return generation === sessionGeneration && sessionFile === activeSessionFile && compactionState.phase === "running" && compactionState.operation === operation;
1499
1504
  }
1505
+ function releasePiCompactionOnAbort(ctx, signal, operation, generation, sessionFile, request, reason) {
1506
+ signal?.addEventListener("abort", () => {
1507
+ if (!runningOperationIsCurrent(operation, generation, sessionFile))
1508
+ return;
1509
+ setCompactionState(ctx, reason !== "manual" && request && requestIsCurrent(request) ? {
1510
+ phase: "queued",
1511
+ request: {
1512
+ ...request,
1513
+ eligibleSettlement: Math.max(request.eligibleSettlement, settlementGeneration + 2)
1514
+ }
1515
+ } : { phase: "idle" });
1516
+ }, { once: true });
1517
+ }
1500
1518
  function requestHasCompactableHistory(ctx, keep) {
1501
1519
  return keep === null ? hasCompactableHistory(ctx) : keepBoundaryId(ctx.sessionManager.getBranch(), keep) !== undefined;
1502
1520
  }
@@ -1505,53 +1523,52 @@ function createGoosedumpIntegration(options = {}) {
1505
1523
  return;
1506
1524
  if (!hasCompactableHistory(ctx))
1507
1525
  return;
1508
- compactionState = {
1526
+ setCompactionState(ctx, {
1509
1527
  phase: "queued",
1510
1528
  request: {
1511
1529
  keep: null,
1512
- origin: "turn",
1513
1530
  sessionGeneration,
1514
1531
  sessionFile: activeSessionFile,
1515
1532
  eligibleSettlement
1516
1533
  }
1517
- };
1534
+ });
1518
1535
  }
1519
1536
  function startQueuedCompaction(ctx) {
1520
1537
  if (compactionState.phase !== "queued")
1521
1538
  return false;
1522
1539
  const request = compactionState.request;
1523
1540
  if (!requestIsCurrent(request)) {
1524
- compactionState = { phase: "idle" };
1541
+ setCompactionState(ctx, { phase: "idle" });
1525
1542
  return false;
1526
1543
  }
1527
1544
  if (!ctx.isIdle() || ctx.hasPendingMessages())
1528
1545
  return false;
1529
1546
  if (!requestHasCompactableHistory(ctx, request.keep)) {
1530
- compactionState = { phase: "idle" };
1547
+ setCompactionState(ctx, { phase: "idle" });
1531
1548
  return false;
1532
1549
  }
1533
1550
  const operation = ++operationGeneration;
1534
1551
  const generation = request.sessionGeneration;
1535
1552
  const sessionFile = request.sessionFile;
1536
- compactionState = {
1553
+ setCompactionState(ctx, {
1537
1554
  phase: "running",
1538
1555
  request,
1539
1556
  owner: "extension",
1540
1557
  operation,
1541
1558
  sessionGeneration: generation,
1542
1559
  sessionFile,
1543
- hookActive: false
1544
- };
1560
+ hookClaimed: false
1561
+ });
1545
1562
  ctx.compact({
1546
1563
  onComplete: () => {
1547
1564
  if (!runningOperationIsCurrent(operation, generation, sessionFile))
1548
1565
  return;
1549
- compactionState = { phase: "idle" };
1566
+ setCompactionState(ctx, { phase: "idle" });
1550
1567
  },
1551
1568
  onError: (error) => {
1552
1569
  if (!runningOperationIsCurrent(operation, generation, sessionFile))
1553
1570
  return;
1554
- compactionState = { phase: "idle" };
1571
+ setCompactionState(ctx, { phase: "idle" });
1555
1572
  const message = error instanceof Error ? error.message : String(error);
1556
1573
  if (ctx.hasUI)
1557
1574
  ctx.ui.notify(`goosedump compaction failed: ${message}`, "error");
@@ -1559,21 +1576,31 @@ function createGoosedumpIntegration(options = {}) {
1559
1576
  });
1560
1577
  return true;
1561
1578
  }
1562
- function requestCompaction(ctx, keep, origin) {
1579
+ function deferQueuedCompaction(ctx) {
1580
+ if (compactionState.phase !== "queued")
1581
+ return;
1582
+ const request = compactionState.request;
1583
+ setImmediate(() => {
1584
+ if (compactionState.phase !== "queued" || compactionState.request !== request || !contextIsCurrent(ctx)) {
1585
+ return;
1586
+ }
1587
+ startQueuedCompaction(ctx);
1588
+ });
1589
+ }
1590
+ function requestCompaction(ctx, keep) {
1563
1591
  if (compactionState.phase !== "idle")
1564
1592
  return "busy";
1565
1593
  if (!requestHasCompactableHistory(ctx, keep))
1566
1594
  return "nothing-to-compact";
1567
- compactionState = {
1595
+ setCompactionState(ctx, {
1568
1596
  phase: "queued",
1569
1597
  request: {
1570
1598
  keep,
1571
- origin,
1572
1599
  sessionGeneration,
1573
1600
  sessionFile: activeSessionFile,
1574
1601
  eligibleSettlement: settlementGeneration + 1
1575
1602
  }
1576
- };
1603
+ });
1577
1604
  return startQueuedCompaction(ctx) ? "started" : "queued";
1578
1605
  }
1579
1606
  function register(pi) {
@@ -1848,7 +1875,7 @@ function createGoosedumpIntegration(options = {}) {
1848
1875
  if (!goosedumpAvailable)
1849
1876
  return goosedumpUnavailableResult();
1850
1877
  const keep = params.keep ?? null;
1851
- const result = requestCompaction(ctx, keep, "tool");
1878
+ const result = requestCompaction(ctx, keep);
1852
1879
  if (result === "busy")
1853
1880
  return textResult("Compaction already in progress.");
1854
1881
  if (result === "nothing-to-compact") {
@@ -1930,20 +1957,20 @@ ${lines.join(`
1930
1957
  settlementGeneration += 1;
1931
1958
  if (compactionState.phase === "running" && compactionState.owner === "pi") {
1932
1959
  const request = compactionState.request;
1933
- compactionState = request && requestIsCurrent(request) ? {
1960
+ setCompactionState(ctx, request && requestIsCurrent(request) ? {
1934
1961
  phase: "queued",
1935
1962
  request: { ...request, eligibleSettlement: settlementGeneration + 1 }
1936
- } : { phase: "idle" };
1963
+ } : { phase: "idle" });
1937
1964
  return;
1938
1965
  }
1939
1966
  if (compactionState.phase === "queued" && !requestIsCurrent(compactionState.request)) {
1940
- compactionState = { phase: "idle" };
1967
+ setCompactionState(ctx, { phase: "idle" });
1941
1968
  }
1942
1969
  if (compactionState.phase === "idle") {
1943
1970
  queueTurnCompaction(ctx, settlementGeneration);
1944
1971
  }
1945
1972
  if (compactionState.phase === "queued" && compactionState.request.eligibleSettlement <= settlementGeneration) {
1946
- startQueuedCompaction(ctx);
1973
+ deferQueuedCompaction(ctx);
1947
1974
  }
1948
1975
  }
1949
1976
  pi.on("session_start", async (_event, ctx) => {
@@ -1957,6 +1984,7 @@ ${lines.join(`
1957
1984
  syncCompactCounters(ctx);
1958
1985
  updateStatus(ctx);
1959
1986
  queueTurnCompaction(ctx, 1);
1987
+ deferQueuedCompaction(ctx);
1960
1988
  });
1961
1989
  pi.on("session_shutdown", async () => {
1962
1990
  sessionGeneration += 1;
@@ -1965,6 +1993,15 @@ ${lines.join(`
1965
1993
  sessionActive = false;
1966
1994
  compactionState = { phase: "idle" };
1967
1995
  });
1996
+ pi.on("agent_start", async (_event, ctx) => {
1997
+ if (compactionState.phase !== "running" || compactionState.owner !== "pi")
1998
+ return;
1999
+ const request = compactionState.request;
2000
+ setCompactionState(ctx, request && requestIsCurrent(request) ? {
2001
+ phase: "queued",
2002
+ request: { ...request, eligibleSettlement: settlementGeneration + 1 }
2003
+ } : { phase: "idle" });
2004
+ });
1968
2005
  pi.on("turn_end", async (event, ctx) => {
1969
2006
  if (isCompletedAssistant(event.message))
1970
2007
  noteCompletedTurn(ctx);
@@ -1975,32 +2012,33 @@ ${lines.join(`
1975
2012
  pi.on("session_before_compact", async (event, ctx) => {
1976
2013
  if (!shouldOverrideDefaultCompaction || !goosedumpAvailable)
1977
2014
  return;
1978
- if (event.customInstructions?.trim()) {
1979
- if (compactionState.phase === "running")
1980
- return { cancel: true };
1981
- if (compactionState.phase === "queued") {
1982
- if (compactionState.request.origin !== "turn")
1983
- return { cancel: true };
1984
- compactionState = {
1985
- phase: "queued",
1986
- request: {
1987
- ...compactionState.request,
1988
- eligibleSettlement: Math.max(compactionState.request.eligibleSettlement, settlementGeneration + 2)
1989
- }
1990
- };
1991
- }
1992
- return;
1993
- }
1994
- if (event.signal.aborted) {
2015
+ const signal = event.signal;
2016
+ if (signal?.aborted) {
1995
2017
  if (compactionState.phase === "queued" && requestIsCurrent(compactionState.request)) {
1996
- compactionState = event.reason === "manual" ? { phase: "idle" } : {
2018
+ setCompactionState(ctx, event.reason === "manual" ? { phase: "idle" } : {
1997
2019
  phase: "queued",
1998
2020
  request: {
1999
2021
  ...compactionState.request,
2000
2022
  eligibleSettlement: Math.max(compactionState.request.eligibleSettlement, settlementGeneration + 2)
2001
2023
  }
2002
- };
2024
+ });
2003
2025
  }
2026
+ return { cancel: true };
2027
+ }
2028
+ if (event.customInstructions?.trim()) {
2029
+ if (compactionState.phase === "running")
2030
+ return { cancel: true };
2031
+ const operation2 = ++operationGeneration;
2032
+ setCompactionState(ctx, {
2033
+ phase: "running",
2034
+ request: null,
2035
+ owner: "pi",
2036
+ operation: operation2,
2037
+ sessionGeneration,
2038
+ sessionFile: activeSessionFile,
2039
+ hookClaimed: true
2040
+ });
2041
+ releasePiCompactionOnAbort(ctx, signal, operation2, sessionGeneration, activeSessionFile, null, event.reason);
2004
2042
  return;
2005
2043
  }
2006
2044
  let operation;
@@ -2009,7 +2047,7 @@ ${lines.join(`
2009
2047
  let request;
2010
2048
  let owner;
2011
2049
  if (compactionState.phase === "running") {
2012
- if (compactionState.hookActive || compactionState.owner === "pi") {
2050
+ if (compactionState.hookClaimed || compactionState.owner === "pi") {
2013
2051
  return { cancel: true };
2014
2052
  }
2015
2053
  operation = compactionState.operation;
@@ -2017,22 +2055,25 @@ ${lines.join(`
2017
2055
  sessionFile = compactionState.sessionFile;
2018
2056
  request = compactionState.request;
2019
2057
  owner = "extension";
2020
- compactionState = { ...compactionState, hookActive: true };
2058
+ compactionState = { ...compactionState, hookClaimed: true };
2021
2059
  } else {
2022
2060
  request = compactionState.phase === "queued" && requestIsCurrent(compactionState.request) ? compactionState.request : null;
2023
2061
  operation = ++operationGeneration;
2024
2062
  generation = sessionGeneration;
2025
2063
  sessionFile = activeSessionFile;
2026
2064
  owner = "pi";
2027
- compactionState = {
2065
+ setCompactionState(ctx, {
2028
2066
  phase: "running",
2029
2067
  request,
2030
2068
  owner,
2031
2069
  operation,
2032
2070
  sessionGeneration: generation,
2033
2071
  sessionFile,
2034
- hookActive: true
2035
- };
2072
+ hookClaimed: true
2073
+ });
2074
+ }
2075
+ if (owner === "pi") {
2076
+ releasePiCompactionOnAbort(ctx, signal, operation, generation, sessionFile, request, event.reason);
2036
2077
  }
2037
2078
  try {
2038
2079
  const { messagesToSummarize, turnPrefixMessages } = event.preparation;
@@ -2049,17 +2090,26 @@ ${lines.join(`
2049
2090
  }
2050
2091
  keep = undefined;
2051
2092
  if (runningOperationIsCurrent(operation, generation, sessionFile)) {
2052
- compactionState = { ...compactionState, request: null };
2093
+ request = null;
2094
+ setCompactionState(ctx, {
2095
+ phase: "running",
2096
+ request,
2097
+ owner,
2098
+ operation,
2099
+ sessionGeneration: generation,
2100
+ sessionFile,
2101
+ hookClaimed: true
2102
+ });
2053
2103
  }
2054
2104
  }
2055
- const compaction = await buildGoosedumpCompaction(target, event.preparation, event.branchEntries, effectiveSummaryMaxTokens(ctx.cwd, projectSettingsTrusted(ctx)), keep, event.signal, nativeExecutablePath);
2105
+ const compaction = await buildGoosedumpCompaction(target, event.preparation, event.branchEntries, effectiveSummaryMaxTokens(ctx.cwd, projectSettingsTrusted(ctx)), keep, signal, nativeExecutablePath);
2056
2106
  if (!runningOperationIsCurrent(operation, generation, sessionFile)) {
2057
2107
  return { cancel: true };
2058
2108
  }
2059
2109
  if (compaction && "cancel" in compaction) {
2060
2110
  if (ctx.hasUI)
2061
2111
  ctx.ui.notify(compaction.reason, "info");
2062
- return owner === "pi" && event.reason === "manual" ? { cancel: true } : undefined;
2112
+ return;
2063
2113
  }
2064
2114
  const warning = compaction?.details?.warning;
2065
2115
  if (warning && ctx.hasUI) {
@@ -2067,23 +2117,33 @@ ${lines.join(`
2067
2117
  }
2068
2118
  if (compaction)
2069
2119
  return { compaction };
2070
- return owner === "pi" && event.reason === "manual" ? { cancel: true } : undefined;
2120
+ return;
2071
2121
  } catch (err) {
2072
2122
  if (!runningOperationIsCurrent(operation, generation, sessionFile)) {
2073
2123
  return { cancel: true };
2074
2124
  }
2075
- if (event.signal.aborted)
2125
+ if (signal?.aborted) {
2126
+ if (owner === "pi" && event.reason === "manual") {
2127
+ setCompactionState(ctx, { phase: "idle" });
2128
+ }
2076
2129
  return { cancel: true };
2077
- const useFallback = owner === "extension" || event.reason !== "manual";
2130
+ }
2078
2131
  if (ctx.hasUI) {
2079
2132
  const message = err instanceof Error ? err.message : String(err);
2080
- const action = useFallback ? "; falling back to Pi compaction" : "";
2081
- ctx.ui.notify(`goosedump compaction failed${action}: ${message}`, "error");
2133
+ ctx.ui.notify(`goosedump compaction failed; falling back to Pi compaction: ${message}`, "error");
2082
2134
  }
2083
- return useFallback ? undefined : { cancel: true };
2135
+ return;
2084
2136
  } finally {
2085
2137
  if (runningOperationIsCurrent(operation, generation, sessionFile)) {
2086
- compactionState = owner === "pi" && event.reason === "manual" ? { phase: "idle" } : { ...compactionState, hookActive: false };
2138
+ setCompactionState(ctx, {
2139
+ phase: "running",
2140
+ request,
2141
+ owner,
2142
+ operation,
2143
+ sessionGeneration: generation,
2144
+ sessionFile,
2145
+ hookClaimed: true
2146
+ });
2087
2147
  }
2088
2148
  }
2089
2149
  });
@@ -2095,7 +2155,9 @@ ${lines.join(`
2095
2155
  if (compactionState.phase === "queued" && compactionState.request.keep !== null && ctx.hasUI) {
2096
2156
  ctx.ui.notify("Queued keep request was superseded by another compaction.", "info");
2097
2157
  }
2098
- compactionState = { phase: "idle" };
2158
+ const extensionOwned = compactionState.phase === "running" && compactionState.owner === "extension";
2159
+ if (!extensionOwned)
2160
+ compactionState = { phase: "idle" };
2099
2161
  syncCompactCounters(ctx);
2100
2162
  updateStatus(ctx);
2101
2163
  if (!goosedumpAvailable)
@@ -2158,7 +2220,7 @@ ${lines.join(`
2158
2220
  return;
2159
2221
  }
2160
2222
  }
2161
- const result = requestCompaction(ctx, keep, "command");
2223
+ const result = requestCompaction(ctx, keep);
2162
2224
  if (result === "busy") {
2163
2225
  ctx.ui.notify("Compaction already in progress", "info");
2164
2226
  } else if (result === "nothing-to-compact") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-goosedump",
3
- "version": "0.12.15",
3
+ "version": "0.12.17",
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.15",
41
+ "@jarkkojs/goosedump": "^0.12.17",
42
42
  "@sinclair/typebox": "^0.34.49"
43
43
  },
44
44
  "devDependencies": {