pi-codex-compaction 0.2.2 → 0.2.3
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 +7 -0
- package/README.md +23 -1
- package/SECURITY.md +7 -2
- package/package.json +1 -1
- package/src/automatic-compaction.ts +39 -14
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,13 @@ This project follows the spirit of [Keep a Changelog](https://keepachangelog.com
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.2.3] - 2026-10-09
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Load managed npm installations without a physical Pi AI dependency. Remove host filesystem resolution and internal serializer imports; verify retained output through the configured provider's public serialization boundary with transport disabled.
|
|
14
|
+
- Test the actual packed entry outside the checkout without installing or linking Pi peers, including CLI startup and checkpoint/tool continuation/reload in print, JSON, and RPC modes.
|
|
15
|
+
|
|
9
16
|
## [0.2.2] - 2026-10-09
|
|
10
17
|
|
|
11
18
|
### Fixed
|
package/README.md
CHANGED
|
@@ -33,6 +33,10 @@ Or load it for one run:
|
|
|
33
33
|
pi -e /path/to/pi-mono/packages/pi-codex-compaction
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
+
Pi supplies the host packages through its extension loader; this package does
|
|
37
|
+
not install or resolve a separate physical copy of Pi AI. No manual dependency
|
|
38
|
+
installation is required. See Pi's [package dependency contract](https://github.com/earendil-works/pi/blob/v1.1.0/packages/coding-agent/docs/packages.md#declare-dependencies).
|
|
39
|
+
|
|
36
40
|
Select a GPT-5/GPT-6 model on provider `openai`, API `openai-responses`, at
|
|
37
41
|
`https://api.openai.com/v1`. No enable command is needed. A previously saved
|
|
38
42
|
`off` setting on the current session branch remains respected.
|
|
@@ -84,6 +88,13 @@ On subsequent requests, it replaces the readable fallback and the retained
|
|
|
84
88
|
assistant's serialized items with that checkpoint and output suffix. Later
|
|
85
89
|
tool results and messages remain.
|
|
86
90
|
|
|
91
|
+
Before adopting a checkpoint, the extension verifies the retained assistant
|
|
92
|
+
through the configured provider's public `streamSimple` / `onPayload` boundary.
|
|
93
|
+
This is a local serialization-only operation: it stops before transport, uses no
|
|
94
|
+
real credential, blocks fetch, and adds no model request or usage. It does not
|
|
95
|
+
import Pi's internal serializers or locate host files on disk. A serialization
|
|
96
|
+
failure leaves history intact.
|
|
97
|
+
|
|
87
98
|
The original transcript stays in the session file. Reload and tree navigation
|
|
88
99
|
use the selected branch, not a global cache. Turning the mode off stops requesting
|
|
89
100
|
new checkpoints but still replays a compatible saved checkpoint while the
|
|
@@ -171,7 +182,12 @@ incompatible in the public API.
|
|
|
171
182
|
Tests use real Pi sessions with mocked transport to cover effective loadouts,
|
|
172
183
|
streaming, tool continuations, checkpoint ordering, branches, reloads,
|
|
173
184
|
cancellation, credential changes, context edits, default-on behavior, and
|
|
174
|
-
the standard compaction safety net.
|
|
185
|
+
the standard compaction safety net. The loading regression test packs the actual
|
|
186
|
+
npm tarball and loads it outside the checkout with only declared runtime
|
|
187
|
+
dependencies—no physical Pi peers. It uses the shipped bundled host's embedded
|
|
188
|
+
module mapping, not just the ordinary SDK's filesystem aliases. It checks CLI
|
|
189
|
+
flag registration and checkpoint adoption, tool continuation, and reload in
|
|
190
|
+
print, JSON, and RPC modes.
|
|
175
191
|
|
|
176
192
|
A small live subscription test with `gpt-6-astra` verified checkpoint adoption,
|
|
177
193
|
replay, and synthetic fact recall. At a deliberately low 1,000-token threshold,
|
|
@@ -191,6 +207,12 @@ npm test -w packages/pi-codex-compaction
|
|
|
191
207
|
npm run -w packages/pi-codex-compaction pack:dry-run
|
|
192
208
|
```
|
|
193
209
|
|
|
210
|
+
Before releasing, also install the candidate tarball outside the checkout with
|
|
211
|
+
host-peer installation suppressed, then load it through the installed Pi CLI.
|
|
212
|
+
`pi -ne -e /path/to/installed/package --offline --help` must show both
|
|
213
|
+
`--server-compaction` flags without loading errors or host-dependency warnings.
|
|
214
|
+
Never add Pi peers to the candidate directory to make this smoke test pass.
|
|
215
|
+
|
|
194
216
|
For an approved TUI smoke test, cross the configured threshold in a synthetic
|
|
195
217
|
session, then check facts from earlier turns and a tool continuation. Check replay
|
|
196
218
|
after `/reload`, mode-off, and branch navigation. Successful adoption should
|
package/SECURITY.md
CHANGED
|
@@ -49,8 +49,13 @@ a bounded transcript excerpt, not a complete summary. The original history and
|
|
|
49
49
|
fallback remain subject to the user's Pi session-file permissions and retention.
|
|
50
50
|
|
|
51
51
|
Replay checks the canonical retained assistant and serialized output
|
|
52
|
-
fingerprints.
|
|
53
|
-
|
|
52
|
+
fingerprints. Before adoption, serialization uses the configured provider's
|
|
53
|
+
public payload hook and aborts before transport. It uses a non-credential
|
|
54
|
+
placeholder, a fetch function that always throws, and zero retries; it does not
|
|
55
|
+
resolve real authentication or send an additional inference request. Missing
|
|
56
|
+
hooks, serialization errors, or an attempted fetch prevent adoption. No host
|
|
57
|
+
filesystem lookup or private serializer imports are required. Omitted, edited,
|
|
58
|
+
or transformed messages fall back rather than restoring stale raw content. As with Pi text summaries, edits to already-compacted
|
|
54
59
|
source history cannot alter an opaque checkpoint. Legacy checkpoint details are
|
|
55
60
|
not migrated or replayed across provider/authentication boundaries.
|
|
56
61
|
|
package/package.json
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import { createHash, createHmac, randomUUID } from "node:crypto";
|
|
2
|
-
import type
|
|
3
|
-
// Resolve before importing: Pi's jiti root alias also matches subpaths.
|
|
4
|
-
const aiEntry = import.meta.resolve("@earendil-works/pi-ai");
|
|
5
|
-
const { convertResponsesMessages } = await import(new URL("./api/openai-responses-shared.js", aiEntry).href) as typeof import("@earendil-works/pi-ai/api/openai-responses-shared");
|
|
6
|
-
const { createGrammarToolInputProperties } = await import(new URL("./api/constrained-sampling.js", aiEntry).href) as typeof import("@earendil-works/pi-ai/api/constrained-sampling");
|
|
7
|
-
const { getCurrentSystemMessage, normalizeContext } = await import(new URL("./utils/transcript.js", aiEntry).href) as typeof import("@earendil-works/pi-ai/utils/transcript");
|
|
2
|
+
import { getCurrentSystemMessage, normalizeContext, type AssistantMessage, type Model } from "@earendil-works/pi-ai";
|
|
8
3
|
import {
|
|
9
4
|
convertToLlm,
|
|
10
5
|
serializeConversation,
|
|
@@ -185,6 +180,41 @@ function fallbackSummary(entries: SessionEntry[], ctx: ExtensionContext): string
|
|
|
185
180
|
return `[${KIND}:${randomUUID()}]\nProvider checkpoint; the following is only a bounded readable fallback, not a full summary.\n${excerpt}`;
|
|
186
181
|
}
|
|
187
182
|
|
|
183
|
+
/**
|
|
184
|
+
* Ask the configured provider to serialize, then stop at its public onPayload
|
|
185
|
+
* boundary. Never resolve host files or reproduce its private serializer.
|
|
186
|
+
* The dummy credential, throwing fetch and zero retries prohibit inference,
|
|
187
|
+
* even if a future host accidentally continues past the payload callback.
|
|
188
|
+
*/
|
|
189
|
+
async function serializedAssistant(ctx: ExtensionContext, message: AssistantMessage): Promise<unknown[] | undefined> {
|
|
190
|
+
if (!supports(ctx.model) || ctx.signal?.aborted) return;
|
|
191
|
+
const provider = ctx.modelRegistry.getProvider(ctx.model.provider);
|
|
192
|
+
if (!provider) return;
|
|
193
|
+
const system = getCurrentSystemMessage(ctx.sessionManager.buildSessionProjection().messages);
|
|
194
|
+
const controller = new AbortController();
|
|
195
|
+
const stop = new Error("Serialization only; no provider request allowed");
|
|
196
|
+
let input: unknown[] | undefined;
|
|
197
|
+
let fetchAttempted = false;
|
|
198
|
+
const result = await provider.streamSimple(ctx.model, normalizeContext({
|
|
199
|
+
messages: [...(system ? [system] : []), message],
|
|
200
|
+
}), {
|
|
201
|
+
// Not an API key. No real credential is resolved or sent by this dry run.
|
|
202
|
+
apiKey: "serialization-only",
|
|
203
|
+
signal: controller.signal,
|
|
204
|
+
cacheRetention: "none",
|
|
205
|
+
maxRetries: 0,
|
|
206
|
+
fetch: async () => { fetchAttempted = true; throw stop; },
|
|
207
|
+
onPayload: (payload) => {
|
|
208
|
+
if (record(payload) && Array.isArray(payload.input) && bytes(payload.input) <= MAX_BYTES) {
|
|
209
|
+
input = structuredClone(payload.input);
|
|
210
|
+
}
|
|
211
|
+
controller.abort();
|
|
212
|
+
throw stop;
|
|
213
|
+
},
|
|
214
|
+
}).result();
|
|
215
|
+
return result.stopReason === "aborted" && !fetchAttempted && !ctx.signal?.aborted ? input : undefined;
|
|
216
|
+
}
|
|
217
|
+
|
|
188
218
|
/** Public Pi hooks only. No provider replacement, synthetic model turn, or private registry access. */
|
|
189
219
|
export function registerAutomaticCompaction(pi: ExtensionAPI): void {
|
|
190
220
|
pi.registerFlag("server-compaction", { type: "boolean", default: true,
|
|
@@ -332,7 +362,7 @@ export function registerAutomaticCompaction(pi: ExtensionAPI): void {
|
|
|
332
362
|
attempt.items.clear();
|
|
333
363
|
});
|
|
334
364
|
|
|
335
|
-
pi.on("turn_end", (event, ctx) => {
|
|
365
|
+
pi.on("turn_end", async (event, ctx) => {
|
|
336
366
|
const current = attempt;
|
|
337
367
|
attempt = undefined;
|
|
338
368
|
inTurn = false;
|
|
@@ -364,13 +394,8 @@ export function registerAutomaticCompaction(pi: ExtensionAPI): void {
|
|
|
364
394
|
// output before discarding any history. In particular, message_end redaction
|
|
365
395
|
// must not later be undone by replaying an unredacted raw suffix.
|
|
366
396
|
try {
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
normalizeContext({ messages: [...(system ? [system] : []), event.message] }), new Set(["openai"]), {
|
|
370
|
-
includeSystemPrompt: false,
|
|
371
|
-
grammarToolInputProperties: createGrammarToolInputProperties(
|
|
372
|
-
system?.toolsAdded ?? [], ctx.model.compat?.supportsOpenAIGrammarTools ?? false),
|
|
373
|
-
});
|
|
397
|
+
const normalized = await serializedAssistant(ctx, event.message);
|
|
398
|
+
if (!normalized) return;
|
|
374
399
|
const actualHashes = normalized.map(outputHash).filter((value) => value !== undefined);
|
|
375
400
|
if (hash(actualHashes) !== hash(hashes)) return;
|
|
376
401
|
} catch { return; }
|