pi-condense 2.15.0 → 2.15.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/CHANGELOG.md +6 -0
- package/PRUNING.md +2 -2
- package/index.ts +20 -19
- package/package.json +1 -1
- package/src/commands.test.ts +1 -0
- package/src/commands.ts +5 -0
- package/src/reload-rearm.integration.test.ts +20 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,12 @@ Published to npm as [`pi-condense`](https://www.npmjs.com/package/pi-condense) (
|
|
|
7
7
|
Pushing a `vX.Y.Z` tag triggers `.github/workflows/release.yml`, which runs the tests and
|
|
8
8
|
publishes via OIDC trusted publishing. See `.agents/skills/release/SKILL.md`.
|
|
9
9
|
|
|
10
|
+
## [2.15.1] - 2026-11-10
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- `/pruner on` runs the reload rearm probe when it flips pruning from off to on, so tool results produced while pruning was disabled, or recovered by a reload that happened while it was disabled, reach the next `turn_end` gate. Before, only `session_start` and `session_tree` probed, and enabling later left that work unflushed until a fresh batch arrived. (#29)
|
|
15
|
+
|
|
10
16
|
## [2.15.0] - 2026-11-09
|
|
11
17
|
|
|
12
18
|
### Changed
|
package/PRUNING.md
CHANGED
|
@@ -1074,9 +1074,9 @@ See `doc/specs/2026-08-12-single-chain-observability-trigger-repair.md` for the
|
|
|
1074
1074
|
|
|
1075
1075
|
### Reload rearm
|
|
1076
1076
|
|
|
1077
|
-
A reload (`session_start` or `session_tree`) rescans the branch for completed-but-unflushed tool-call batches past the frontier (the same rescan `flushPending` itself uses, no queue reconstruction). If that rescan finds recoverable work, a transient in-memory flag arms: the next `turn_end` evaluates the budget/delta trigger even on a turn that contributes no new tool results itself, instead of silently requiring a fresh batch to reach the gate. No flush runs at reload time - arming only changes what an *existing* trigger sees on the next turn.
|
|
1077
|
+
A reload (`session_start` or `session_tree`), and `/pruner on` when it flips pruning from off to on, rescans the branch for completed-but-unflushed tool-call batches past the frontier (the same rescan `flushPending` itself uses, no queue reconstruction). If that rescan finds recoverable work, a transient in-memory flag arms: the next `turn_end` evaluates the budget/delta trigger even on a turn that contributes no new tool results itself, instead of silently requiring a fresh batch to reach the gate. No flush runs at reload time - arming only changes what an *existing* trigger sees on the next turn.
|
|
1078
1078
|
|
|
1079
|
-
The flag clears the moment any flush attempt runs (any outcome) and is re-evaluated on the next reload. A reload followed by total idleness - no further turns at all - gets visibility only: the footer, `/pruner status`, and the `agent_end` pending notice (`prune: recovered pending (reload)`) all reflect the recoverable work, but nothing flushes automatically. This is by design - no immediate flush at boot (print-mode sessions may die under it; a boot-time compact was not requested by the user).
|
|
1079
|
+
The flag clears the moment any flush attempt runs (any outcome) and is re-evaluated on the next reload or enable. `turn_end` captures nothing while pruning is disabled, so the `/pruner on` probe is what puts tool results from a disabled stretch back in front of the gate; a `/pruner on` while already enabled does not probe. A reload followed by total idleness - no further turns at all - gets visibility only: the footer, `/pruner status`, and the `agent_end` pending notice (`prune: recovered pending (reload)`) all reflect the recoverable work, but nothing flushes automatically. This is by design - no immediate flush at boot (print-mode sessions may die under it; a boot-time compact was not requested by the user).
|
|
1080
1080
|
|
|
1081
1081
|
---
|
|
1082
1082
|
|
package/index.ts
CHANGED
|
@@ -180,9 +180,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
180
180
|
// ── Helper: capture + trim + group pending batches (no LLM work) ──────────
|
|
181
181
|
// Exposed to commands.ts via registerCommands so /pruner now can preview the
|
|
182
182
|
// queue before opening the multi-row progress overlay.
|
|
183
|
-
// `rethrow` is for
|
|
184
|
-
// it
|
|
185
|
-
//
|
|
183
|
+
// `rethrow` is for probeRearm only: it needs to observe a rescan failure so
|
|
184
|
+
// it can console.error and leave rearmedPending false, per spec. Every
|
|
185
|
+
// other caller (turn_end capture path,
|
|
186
186
|
// flushPending, /pruner commands) keeps the existing swallow-and-fall-back
|
|
187
187
|
// behavior so a transient getBranch failure there never blocks the turn.
|
|
188
188
|
const capturePendingBatches = (ctx: any, opts?: { rethrow?: boolean }): CapturedBatch[] => {
|
|
@@ -200,6 +200,20 @@ export default function (pi: ExtensionAPI) {
|
|
|
200
200
|
return groupBatchesByMode(batches, currentConfig.value.batchingMode);
|
|
201
201
|
};
|
|
202
202
|
|
|
203
|
+
// Arms the one-shot rearm flag when the persisted branch holds un-pruned
|
|
204
|
+
// tool results the in-memory queue does not know about: after a reload, a
|
|
205
|
+
// branch switch, or a stretch with pruning disabled (turn_end captures
|
|
206
|
+
// nothing while disabled). A disabled pruner never arms.
|
|
207
|
+
const probeRearm = (ctx: any): void => {
|
|
208
|
+
rearmedPending = false;
|
|
209
|
+
if (!currentConfig.value.enabled) return;
|
|
210
|
+
try {
|
|
211
|
+
rearmedPending = capturePendingBatches(ctx, { rethrow: true }).length > 0;
|
|
212
|
+
} catch (err) {
|
|
213
|
+
console.error("pi-condense: reload rearm probe failed", err);
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
|
|
203
217
|
// Summarizes + indexes all pending batches.
|
|
204
218
|
// When options.onProgress is provided batches are processed sequentially
|
|
205
219
|
// (one LLM call each) so the caller can update per-row UI. Otherwise all
|
|
@@ -817,14 +831,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
817
831
|
// Clear any batches queued before the session reload
|
|
818
832
|
pendingBatches.length = 0;
|
|
819
833
|
previousFraction = null;
|
|
820
|
-
|
|
821
|
-
if (currentConfig.value.enabled) {
|
|
822
|
-
try {
|
|
823
|
-
rearmedPending = capturePendingBatches(ctx, { rethrow: true }).length > 0;
|
|
824
|
-
} catch (err) {
|
|
825
|
-
console.error("pi-condense: reload rearm probe failed", err);
|
|
826
|
-
}
|
|
827
|
-
}
|
|
834
|
+
probeRearm(ctx);
|
|
828
835
|
|
|
829
836
|
// Update footer status
|
|
830
837
|
setPruneStatusWidget(ctx, currentConfig.value, statsAccum.getLiveReclaim(), diagnostics.counts());
|
|
@@ -859,14 +866,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
859
866
|
// Pending batches belong to the old branch — discard them
|
|
860
867
|
pendingBatches.length = 0;
|
|
861
868
|
previousFraction = null;
|
|
862
|
-
|
|
863
|
-
if (currentConfig.value.enabled) {
|
|
864
|
-
try {
|
|
865
|
-
rearmedPending = capturePendingBatches(ctx, { rethrow: true }).length > 0;
|
|
866
|
-
} catch (err) {
|
|
867
|
-
console.error("pi-condense: reload rearm probe failed", err);
|
|
868
|
-
}
|
|
869
|
-
}
|
|
869
|
+
probeRearm(ctx);
|
|
870
870
|
|
|
871
871
|
setPruneStatusWidget(ctx, currentConfig.value, statsAccum.getLiveReclaim(), diagnostics.counts());
|
|
872
872
|
});
|
|
@@ -1138,5 +1138,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
1138
1138
|
() => diagnostics.counts(),
|
|
1139
1139
|
(ctx: any) => computeMetricsSnapshot(ctx) ?? EMPTY_METRICS_SNAPSHOT,
|
|
1140
1140
|
() => rearmedPending,
|
|
1141
|
+
probeRearm,
|
|
1141
1142
|
);
|
|
1142
1143
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-condense",
|
|
3
|
-
"version": "2.15.
|
|
3
|
+
"version": "2.15.1",
|
|
4
4
|
"description": "Pi coding-agent extension that summarizes completed tool-call batches, replaces raw outputs with short stubs, compresses closed tool-call chains, and recovers any original on demand via context_tree_query.",
|
|
5
5
|
"author": "Jacek Juraszek",
|
|
6
6
|
"license": "MIT",
|
package/src/commands.test.ts
CHANGED
package/src/commands.ts
CHANGED
|
@@ -477,6 +477,7 @@ export function registerCommands(
|
|
|
477
477
|
getDiagnosticCounts?: () => Record<DiagnosticKind, number>,
|
|
478
478
|
getContextMetrics?: (ctx: ExtensionCommandContext) => ContextMetricsSnapshot,
|
|
479
479
|
getRearmed?: () => boolean,
|
|
480
|
+
probeRearm?: (ctx: ExtensionCommandContext) => void,
|
|
480
481
|
save: (config: ContextPruneConfig) => Promise<void> = saveConfig,
|
|
481
482
|
): void {
|
|
482
483
|
// Register the /pruner command
|
|
@@ -856,8 +857,12 @@ export function registerCommands(
|
|
|
856
857
|
|
|
857
858
|
// ── /pruner on ──
|
|
858
859
|
case "on": {
|
|
860
|
+
const wasEnabled = currentConfig.value.enabled;
|
|
859
861
|
currentConfig.value = { ...currentConfig.value, enabled: true };
|
|
860
862
|
void persistConfig((m, t) => ctx.ui.notify(m, t), currentConfig.value, save);
|
|
863
|
+
// Tool results produced while disabled were never queued; the probe
|
|
864
|
+
// puts them back in front of the next gate check.
|
|
865
|
+
if (!wasEnabled) probeRearm?.(ctx);
|
|
861
866
|
ctx.ui.notify("Context pruning enabled.");
|
|
862
867
|
setPruneStatusWidget(ctx, currentConfig.value, getLiveReclaim(), getDiagnosticCounts?.());
|
|
863
868
|
break;
|
|
@@ -214,12 +214,13 @@ function bootExtension(
|
|
|
214
214
|
budgetTurnDelta?: number | null;
|
|
215
215
|
frontierGapThresholdTokens?: number | null;
|
|
216
216
|
showPruneStatusLine?: boolean;
|
|
217
|
+
enabled?: boolean;
|
|
217
218
|
} = {},
|
|
218
219
|
) {
|
|
219
220
|
const agentDir = mkdtempSync(join(tmpdir(), "pi-condense-rearm-"));
|
|
220
221
|
process.env.PI_CODING_AGENT_DIR = agentDir;
|
|
221
222
|
const contextPruneSettings: any = {
|
|
222
|
-
enabled: true,
|
|
223
|
+
enabled: options.enabled ?? true,
|
|
223
224
|
pruneOn: "agent-message",
|
|
224
225
|
batchingMode: "agent-message",
|
|
225
226
|
autoBudgetThreshold: options.autoBudgetThreshold === undefined ? 0.5 : options.autoBudgetThreshold,
|
|
@@ -557,6 +558,24 @@ describe("reload rearm (issue #6)", () => {
|
|
|
557
558
|
expect(statusCalls).toContain("\u2502 prune: recovered pending (reload)");
|
|
558
559
|
});
|
|
559
560
|
|
|
561
|
+
it("/pruner on rearms the gate so work recovered from a disabled stretch flushes", async () => {
|
|
562
|
+
const { handlers, commands, ctx, appended } = await boot({ enabled: false });
|
|
563
|
+
await handlers.get("session_start")!({}, ctx);
|
|
564
|
+
const callsBefore = summarizerCalls;
|
|
565
|
+
|
|
566
|
+
const budgetTurn = { toolResults: [], message: { role: "assistant", content: [{ type: "text", text: "hi" }] }, turnIndex: 2 };
|
|
567
|
+
await handlers.get("turn_end")!(budgetTurn, ctx);
|
|
568
|
+
expect(summarizerCalls).toBe(callsBefore);
|
|
569
|
+
|
|
570
|
+
await commands.get("pruner")!("on", ctx);
|
|
571
|
+
await handlers.get("turn_end")!(budgetTurn, ctx);
|
|
572
|
+
|
|
573
|
+
expect(summarizerCalls).toBe(callsBefore + 1);
|
|
574
|
+
const fm = appended.filter((e) => e.type === "context-prune-flush-metrics");
|
|
575
|
+
expect(fm.length).toBe(1);
|
|
576
|
+
expect((fm[0].data as any).trigger).toBe("rearmed");
|
|
577
|
+
});
|
|
578
|
+
|
|
560
579
|
it("reports a rescan failure to console.error and leaves rearmedPending false, without failing session_start (G4)", async () => {
|
|
561
580
|
// Spec (Component 2, Rescan failure): "if the reload rearm probe throws,
|
|
562
581
|
// console.error ... and leave rearmedPending = false - reload must never
|