pi-condense 2.11.0 → 2.11.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
@@ -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.11.1] - 2026-09-29
11
+
12
+ ### Fixed
13
+
14
+ - The frontier-gap metric prices image blocks at a flat estimate instead of counting base64 as text, so screenshot reads no longer trigger a premature frontier-gap flush that prunes images before the model sees them.
15
+
10
16
  ## [2.11.0] - 2026-09-24
11
17
 
12
18
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-condense",
3
- "version": "2.11.0",
3
+ "version": "2.11.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",
@@ -260,6 +260,22 @@ describe("computeContextMetrics", () => {
260
260
  expect(result.frontierGapTokens).toBe(expected);
261
261
  });
262
262
 
263
+ test("frontierGapTokens: image blocks use a flat estimate instead of base64 length", () => {
264
+ const msgs = [userMsg(100), assistantWithTools(200, ["tc1", "tc2", "tc3"])];
265
+ for (let i = 0; i < 3; i++) {
266
+ const result = toolResult(300 + i, `tc${i + 1}`, "read", "screenshot");
267
+ result.content.push({ type: "image", data: "A".repeat(137000), mimeType: "image/png" });
268
+ msgs.push(result);
269
+ }
270
+ const expected = msgs.slice(2).reduce((sum, msg) => {
271
+ const stub = { ...msg, content: msg.content.map((block: any) => block.type === "image" ? { type: "image" } : block) };
272
+ return sum + Math.round((JSON.stringify(stub).length + 1600 * 4) / 4);
273
+ }, 0);
274
+ const result = computeContextMetrics(msgs, null, noSummarized, noProtected);
275
+ expect(result.frontierGapTokens).toBe(expected);
276
+ expect(result.frontierGapTokens).toBeLessThan(20000);
277
+ });
278
+
263
279
  test("frontierGapTokens: boundary mid-turn split excludes at-or-before calls, includes later calls in same turn", () => {
264
280
  const msgs = [userMsg(100), assistantWithTools(200, ["tc1", "tc2"]), toolResult(300, "tc1"), toolResult(310, "tc2")];
265
281
  const frontier = fullFrontier({ lastAttemptedToolCallId: "tc1", lastAttemptedTurnIndex: 0 });
@@ -2,8 +2,17 @@ import { detectChains, isChainAnchorCustom } from "./chain-detector.js";
2
2
  import { occKey, resultTimestampOf } from "./occurrence-key.js";
3
3
  import type { ContextMetricsSnapshot, PruneFrontier } from "./types.js";
4
4
 
5
+ const IMAGE_TOKEN_ESTIMATE = 1600;
6
+
5
7
  function charsOf(msg: any): number {
6
- return JSON.stringify(msg).length;
8
+ if (!Array.isArray(msg.content)) return JSON.stringify(msg).length;
9
+ let imageCount = 0;
10
+ const content = msg.content.map((block: any) => {
11
+ if (block?.type !== "image") return block;
12
+ imageCount++;
13
+ return { type: "image" };
14
+ });
15
+ return JSON.stringify({ ...msg, content }).length + imageCount * IMAGE_TOKEN_ESTIMATE * 4;
7
16
  }
8
17
 
9
18
  function tokensOf(msg: any): number {