pi-condense 2.4.1 → 2.4.2
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 +5 -0
- package/index.ts +5 -3
- package/package.json +3 -3
- package/src/pruner.test.ts +7 -4
- package/src/pruner.ts +9 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,11 @@ 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.4.2] - 2026-08-04
|
|
11
|
+
|
|
12
|
+
- **No-op renders skip context serialization.** `pruneMessages` (`src/pruner.ts`) computed `sizeMessages` (a full `JSON.stringify` over the entire message array) unconditionally on every `context` render, including no-ops where the result is never read (`index.ts` consumes `beforeChars`/`afterChars` only under `if (result.pruned)`). It now computes both sizes lazily in the pruned branch and returns a `{ beforeChars: 0, afterChars: 0 }` sentinel on a no-op, so a render that prunes nothing does zero `JSON.stringify` over the array. CPU/GC only - zero token cost, no wire or return-shape change. Also corrects a stale fast-path comment in the `context` handler (`index.ts`): index/registry emptiness alone does not imply a no-op, because error-purge and thinking-strip prune independently.
|
|
13
|
+
- **Bumped `engines.node` to `>=22.19.0`** to match the host pi runtime (`@earendil-works/pi-coding-agent@0.83.0`); drops advertised support for Node 20/21, which cannot run current pi. Bumped the `@sinclair/typebox` devDependency floor to `^0.34.52` (freshness only; the caret already resolved there).
|
|
14
|
+
|
|
10
15
|
## [2.4.1] - 2026-07-30
|
|
11
16
|
|
|
12
17
|
- **Fix 421 Misdirected Request for GitHub Copilot business/enterprise seats.** The summarizer called pi-ai's `stream()` with the static model definition, whose shipped `baseUrl` pins the individual Copilot host (`api.individual.githubcopilot.com`); business/enterprise tokens are rejected there with `421 Misdirected Request`, so any `github-copilot/*` `summarizerModel` failed on those seats. `runOnce` now mirrors the main agent loop (pi's `model-runtime`): it resolves provider auth via `ctx.modelRegistry.getProviderAuth(model.provider)` and, when the resolved auth carries a seat-specific `baseUrl`, rebases the model onto it before streaming. Also bumps `@earendil-works/*` devDependencies to `^0.83.0` (needed for `getProviderAuth` on the extension-facing `ModelRegistry`) and imports `stream` from `@earendil-works/pi-ai/compat` (its export moved off the root in current pi-ai).
|
package/index.ts
CHANGED
|
@@ -818,9 +818,11 @@ export default function (pi: ExtensionAPI) {
|
|
|
818
818
|
let changed = false;
|
|
819
819
|
|
|
820
820
|
// pruneMessages is the single source of truth for "is there work to do".
|
|
821
|
-
// It
|
|
822
|
-
//
|
|
823
|
-
//
|
|
821
|
+
// It returns the original array reference (pruned: false) only when none of
|
|
822
|
+
// the four phases changed anything; index/registry emptiness alone does not
|
|
823
|
+
// imply a no-op, since error-purge (phase 2) and thinking-strip (phase 4)
|
|
824
|
+
// prune independently of them. Calling it unconditionally is safe and avoids
|
|
825
|
+
// a split gate here.
|
|
824
826
|
const result = pruneMessages(
|
|
825
827
|
messages,
|
|
826
828
|
indexer,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-condense",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.2",
|
|
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",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"ai"
|
|
28
28
|
],
|
|
29
29
|
"engines": {
|
|
30
|
-
"node": ">=
|
|
30
|
+
"node": ">=22.19.0"
|
|
31
31
|
},
|
|
32
32
|
"files": [
|
|
33
33
|
"index.ts",
|
|
@@ -60,6 +60,6 @@
|
|
|
60
60
|
"@earendil-works/pi-ai": "^0.83.0",
|
|
61
61
|
"@earendil-works/pi-coding-agent": "^0.83.0",
|
|
62
62
|
"@earendil-works/pi-tui": "^0.83.0",
|
|
63
|
-
"@sinclair/typebox": "^0.34.
|
|
63
|
+
"@sinclair/typebox": "^0.34.52"
|
|
64
64
|
}
|
|
65
65
|
}
|
package/src/pruner.test.ts
CHANGED
|
@@ -591,14 +591,17 @@ describe("sizeMessages", () => {
|
|
|
591
591
|
});
|
|
592
592
|
|
|
593
593
|
describe("pruneMessages beforeChars/afterChars", () => {
|
|
594
|
-
it("no-op
|
|
594
|
+
it("no-op returns {0,0} sentinel and pruned false (no serialization on no-op path)", () => {
|
|
595
595
|
const indexer = makeMockIndexer();
|
|
596
|
+
// Non-empty input: a reverted fix that recomputes sizeMessages(messages)
|
|
597
|
+
// on the unconditional path would yield a nonzero beforeChars here and fail.
|
|
596
598
|
const messages = [{ role: "user", content: "hello", timestamp: 1 }];
|
|
597
599
|
const result = pruneMessages(messages, indexer);
|
|
598
600
|
expect(result.pruned).toBe(false);
|
|
599
|
-
|
|
600
|
-
expect(result.
|
|
601
|
-
|
|
601
|
+
expect(result.beforeChars).toBe(0);
|
|
602
|
+
expect(result.afterChars).toBe(0);
|
|
603
|
+
// No-op returns the original array reference unchanged.
|
|
604
|
+
expect(result.messages).toBe(messages);
|
|
602
605
|
});
|
|
603
606
|
|
|
604
607
|
it("pruning path: beforeChars > afterChars when stubs shrink content", () => {
|
package/src/pruner.ts
CHANGED
|
@@ -55,6 +55,12 @@ export function sizeMessages(messages: any[]): number {
|
|
|
55
55
|
* - `pruned: false` — nothing matched; the returned `messages` is the
|
|
56
56
|
* **original input array reference** so the caller can cheaply skip
|
|
57
57
|
* the reconstruction path.
|
|
58
|
+
* - `beforeChars` / `afterChars` — serialized context size (`sizeMessages`)
|
|
59
|
+
* before and after pruning when `pruned` is true. When `pruned` is false
|
|
60
|
+
* both are `0`: a no-op sentinel, not a measurement — the size is never
|
|
61
|
+
* computed on the no-op path (zero `JSON.stringify` over the array), and
|
|
62
|
+
* the only consumer (`index.ts` live-reclaim) reads them solely under
|
|
63
|
+
* `if (result.pruned)`.
|
|
58
64
|
*
|
|
59
65
|
* AssistantMessage tool-call blocks (which carry the IDs) are kept
|
|
60
66
|
* unchanged so the model can still reference them by id when calling
|
|
@@ -69,7 +75,6 @@ export function pruneMessages(
|
|
|
69
75
|
protection?: ProtectionConfig,
|
|
70
76
|
recoveryGraceTurns: number = 0,
|
|
71
77
|
): { messages: any[]; pruned: boolean; beforeChars: number; afterChars: number } {
|
|
72
|
-
const beforeChars = sizeMessages(messages);
|
|
73
78
|
// Phase 1: stub-replace summarized tool results
|
|
74
79
|
let pruned = false;
|
|
75
80
|
const inGrace = inGraceRecoveryToolCallIds(messages, recoveryGraceTurns);
|
|
@@ -158,5 +163,7 @@ export function pruneMessages(
|
|
|
158
163
|
}
|
|
159
164
|
}
|
|
160
165
|
|
|
161
|
-
return
|
|
166
|
+
return pruned
|
|
167
|
+
? { messages: current, pruned, beforeChars: sizeMessages(messages), afterChars: sizeMessages(current) }
|
|
168
|
+
: { messages, pruned, beforeChars: 0, afterChars: 0 };
|
|
162
169
|
}
|