pi-condense 2.2.0 → 2.2.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 CHANGED
@@ -9,6 +9,10 @@ publishes via OIDC trusted publishing. See `.agents/skills/release/SKILL.md`.
9
9
 
10
10
  ## [Unreleased]
11
11
 
12
+ ## [2.2.1] - 2026-07-06
13
+
14
+ - **Fix probe starvation in the summarizer outage fallback.** `FallbackController.onFallbackOnlyFail` reset the re-probe cooldown on every steady-state fallback failure, so a fallback (session) model that failed at least once per 10-minute cooldown perpetually pushed out the primary re-probe - a recovered `summarizerModel` was never re-tested and summarization stayed on the pricier session model indefinitely (the exact stall the feature exists to kill, in the fallback direction). The method is now a no-op on `lastProbeAt`: the primary re-probe fires on schedule regardless of fallback failures. In-memory only; no wire/config change.
15
+
12
16
  ## [2.2.0] - 2026-07-06
13
17
 
14
18
  - **Summarizer outage fallback to the session model.** Per-model provider outages (e.g. a cheap `summarizerModel` like Haiku degraded while the session's main model stays healthy) previously stalled pruning for the whole outage - `runSummarization` returned null and the batch retried the same dead model every flush, growing context unbounded. A new sticky in-memory `FallbackController` (`src/summarizer-fallback.ts`) now routes summarization to `ctx.model` on a **transient** failure of the configured model, retrying the failed call once on the session model. Fallback is sticky: while engaged, all calls use the session model until a single probe batch re-tests the configured model after a 10-minute cooldown, then auto-recovers. Trigger is transient-only - auth (pre-flight key failure), unusable (empty/truncated), and abort never trip it. A one-time `warning` fires on enter and an `info` on recovery via `ctx.ui.notify` (UI only, never injected into LLM context). No config key: the target is always `ctx.model`, and the controller is inert when no distinct fallback model exists (`summarizerModel: default` or the resolved model equals `ctx.model`), preserving today's single-attempt behavior byte-for-byte. State is in-memory only (reset on `session_start`, no `context-prune-*` entry).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-condense",
3
- "version": "2.2.0",
3
+ "version": "2.2.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",
@@ -133,6 +133,23 @@ describe("unusable probe does not falsely recover", () => {
133
133
  });
134
134
  });
135
135
 
136
+ describe("probe schedule survives steady-state fallback failures", () => {
137
+ it("onFallbackOnlyFail does not push out the next primary probe", () => {
138
+ const t = { now: 0 };
139
+ const c = new FallbackController(clockAt(t));
140
+ c.onPrimaryFailFallbackOk(false); // enter, lastProbeAt = 0
141
+ // fallback keeps failing every minute, well within the cooldown
142
+ for (let i = 1; i < 10; i++) {
143
+ t.now = i * 60_000;
144
+ expect(c.chooseTarget()).toEqual({ target: "fallback", wasProbe: false });
145
+ c.onFallbackOnlyFail();
146
+ }
147
+ // at COOLDOWN_MS the primary must still be probed despite the failures
148
+ t.now = COOLDOWN_MS;
149
+ expect(c.chooseTarget()).toEqual({ target: "primary", wasProbe: true });
150
+ });
151
+ });
152
+
136
153
  describe("reset", () => {
137
154
  it("clears all state", () => {
138
155
  const t = { now: 5 };
@@ -96,10 +96,14 @@ export class FallbackController {
96
96
  }
97
97
  }
98
98
 
99
- /** A steady-state fallback call (already in fallback) failed transiently. */
100
- onFallbackOnlyFail(): void {
101
- this.lastProbeAt = this.now();
102
- }
99
+ /**
100
+ * A steady-state fallback call (already in fallback) failed transiently.
101
+ * Deliberately a no-op on `lastProbeAt`: a fallback failure is not a probe,
102
+ * so it must not push out the next primary re-probe. Resetting the cooldown
103
+ * here starves the probe whenever the fallback fails at least once per
104
+ * COOLDOWN_MS, leaving a recovered primary undetected indefinitely.
105
+ */
106
+ onFallbackOnlyFail(): void {}
103
107
 
104
108
  /** A primary call succeeded. Recover only when it was the probe. */
105
109
  onPrimarySuccess(wasProbe: boolean): FallbackTransition {