@xynogen/pix-core 0.6.2 → 0.6.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/package.json +6 -6
- package/src/compaction.ts +86 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xynogen/pix-core",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.3",
|
|
4
4
|
"description": "Pi extension bundle \u2014 installs and activates all core pix-* extensions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/extension.ts",
|
|
@@ -46,11 +46,11 @@
|
|
|
46
46
|
"@xynogen/pix-runtime": "^0.13.0",
|
|
47
47
|
"@xynogen/pix-data": "^0.5.4",
|
|
48
48
|
"@xynogen/pix-pretty": "^1.30.0",
|
|
49
|
-
"@xynogen/pix-welcome": "^0.2.
|
|
49
|
+
"@xynogen/pix-welcome": "^0.2.7",
|
|
50
50
|
"@xynogen/pix-footer": "^0.2.8",
|
|
51
51
|
"@xynogen/pix-diagnostics": "^0.3.6",
|
|
52
52
|
"@xynogen/pix-display": "^0.3.1",
|
|
53
|
-
"@xynogen/pix-prompts": "^0.5.
|
|
53
|
+
"@xynogen/pix-prompts": "^0.5.6",
|
|
54
54
|
"@xynogen/pix-skills": "^0.9.2",
|
|
55
55
|
"@xynogen/pix-models": "^0.3.1",
|
|
56
56
|
"@xynogen/pix-update": "^0.2.5",
|
|
@@ -65,10 +65,10 @@
|
|
|
65
65
|
"@xynogen/pix-bash": "^0.2.10",
|
|
66
66
|
"@xynogen/pix-powershell": "^0.1.1",
|
|
67
67
|
"@xynogen/pix-todo": "^0.4.6",
|
|
68
|
-
"@xynogen/pix-ask": "^0.3.
|
|
69
|
-
"@xynogen/pix-optimizer": "^1.3.
|
|
68
|
+
"@xynogen/pix-ask": "^0.3.2",
|
|
69
|
+
"@xynogen/pix-optimizer": "^1.3.2",
|
|
70
70
|
"@xynogen/pix-gate": "^0.4.1",
|
|
71
|
-
"@xynogen/pix-subagent": "^0.10.
|
|
71
|
+
"@xynogen/pix-subagent": "^0.10.7"
|
|
72
72
|
},
|
|
73
73
|
"peerDependencies": {
|
|
74
74
|
"@earendil-works/pi-ai": "*",
|
package/src/compaction.ts
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
*/
|
|
27
27
|
|
|
28
28
|
import { complete } from "@earendil-works/pi-ai/compat";
|
|
29
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
29
|
+
import type { ContextEditableContent, ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
30
30
|
import { convertToLlm, serializeConversation } from "@earendil-works/pi-coding-agent";
|
|
31
31
|
import { getErrorMessage } from "@xynogen/pix-pretty/utils";
|
|
32
32
|
import { config } from "@xynogen/pix-runtime/config";
|
|
@@ -95,6 +95,43 @@ export function resumeDecisionAfterCompaction(args: {
|
|
|
95
95
|
return "resume";
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
// Pi cuts compaction only at message boundaries, so one huge message (a pasted
|
|
99
|
+
// HAR/log, a giant tool result) can't be summarized away: "Nothing to compact".
|
|
100
|
+
// Over threshold we instead trim oversized text to head+tail via a visible,
|
|
101
|
+
// append-only context_edit — the original stays in the session file.
|
|
102
|
+
// ponytail: fixed char limits (~20k tok kept per message); make them config if
|
|
103
|
+
// someone needs to tune per model.
|
|
104
|
+
const MAX_MESSAGE_CHARS = 80_000;
|
|
105
|
+
const KEEP_CHARS = 20_000;
|
|
106
|
+
|
|
107
|
+
type TextOrImage = { type: string; text?: string };
|
|
108
|
+
|
|
109
|
+
/** Head+tail trim of oversized text. Undefined when the text already fits. */
|
|
110
|
+
export function truncateLargeText(text: string): string | undefined {
|
|
111
|
+
if (text.length <= MAX_MESSAGE_CHARS) return undefined;
|
|
112
|
+
const cut = text.length - 2 * KEEP_CHARS;
|
|
113
|
+
return `${text.slice(0, KEEP_CHARS)}\n\n[… ${cut.toLocaleString()} chars trimmed by pix compaction (original kept in session file) …]\n\n${text.slice(-KEEP_CHARS)}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Trimmed copy of user/tool-result content, or undefined when nothing is oversized. */
|
|
117
|
+
export function shrinkContent(
|
|
118
|
+
content: string | TextOrImage[],
|
|
119
|
+
): { content: string | TextOrImage[]; saved: number } | undefined {
|
|
120
|
+
if (typeof content === "string") {
|
|
121
|
+
const t = truncateLargeText(content);
|
|
122
|
+
return t === undefined ? undefined : { content: t, saved: content.length - t.length };
|
|
123
|
+
}
|
|
124
|
+
let saved = 0;
|
|
125
|
+
const next = content.map((part) => {
|
|
126
|
+
if (part.type !== "text" || typeof part.text !== "string") return part;
|
|
127
|
+
const t = truncateLargeText(part.text);
|
|
128
|
+
if (t === undefined) return part;
|
|
129
|
+
saved += part.text.length - t.length;
|
|
130
|
+
return { ...part, text: t };
|
|
131
|
+
});
|
|
132
|
+
return saved > 0 ? { content: next, saved } : undefined;
|
|
133
|
+
}
|
|
134
|
+
|
|
98
135
|
// Sent as a user message after a self-triggered compaction so the agent picks
|
|
99
136
|
// its task back up on its own. Kept short — the freshly written summary already
|
|
100
137
|
// carries the goal, progress, and next steps. Edit to taste.
|
|
@@ -181,6 +218,44 @@ export default function registerCompaction(pi: ExtensionAPI): void {
|
|
|
181
218
|
// agent_settled re-fires, and we compact again every turn. Reset by /compact or
|
|
182
219
|
// a new session; a session-scoped bool is the smallest fix.
|
|
183
220
|
let triggerDisabled = false;
|
|
221
|
+
|
|
222
|
+
// Runs before agent_settled; committed edits shrink the context the trigger
|
|
223
|
+
// below measures, so a single oversized message no longer blocks compaction.
|
|
224
|
+
pi.on("agent_before_settle", (event, ctx) => {
|
|
225
|
+
const { triggerPercent, minimumTokens } = config(compactionSection);
|
|
226
|
+
if (triggerPercent <= 0) return;
|
|
227
|
+
const usage = ctx.getContextUsage?.();
|
|
228
|
+
if (!usage || usage.tokens === null) return;
|
|
229
|
+
if (
|
|
230
|
+
usage.tokens < compactionThresholdTokens(usage.contextWindow, triggerPercent, minimumTokens)
|
|
231
|
+
)
|
|
232
|
+
return;
|
|
233
|
+
|
|
234
|
+
const edits: typeof event.entries = [];
|
|
235
|
+
let saved = 0;
|
|
236
|
+
for (const { sourceEntry, messages } of event.context.contextEntries) {
|
|
237
|
+
const msg = messages[0];
|
|
238
|
+
const editable =
|
|
239
|
+
sourceEntry.type === "custom_message" ||
|
|
240
|
+
(sourceEntry.type === "message" && (msg?.role === "user" || msg?.role === "toolResult"));
|
|
241
|
+
if (!editable || !msg || !("content" in msg)) continue;
|
|
242
|
+
const shrunk = shrinkContent(msg.content as string | TextOrImage[]);
|
|
243
|
+
if (!shrunk) continue;
|
|
244
|
+
saved += shrunk.saved;
|
|
245
|
+
edits.push({
|
|
246
|
+
type: "context_edit",
|
|
247
|
+
targetId: sourceEntry.id,
|
|
248
|
+
replacement: { content: shrunk.content as ContextEditableContent },
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
if (edits.length === 0) return;
|
|
252
|
+
ctx.ui.notify(
|
|
253
|
+
`Compaction: trimmed ${edits.length} oversized message(s), ~${Math.round(saved / 4).toLocaleString()} tok (head+tail kept; originals stay in session file)`,
|
|
254
|
+
"info",
|
|
255
|
+
);
|
|
256
|
+
return { entries: [...event.entries, ...edits] };
|
|
257
|
+
});
|
|
258
|
+
|
|
184
259
|
pi.on("agent_settled", (_event, ctx) => {
|
|
185
260
|
if (compacting || triggerDisabled) return;
|
|
186
261
|
const { triggerPercent, minimumTokens } = config(compactionSection);
|
|
@@ -259,7 +334,16 @@ export default function registerCompaction(pi: ExtensionAPI): void {
|
|
|
259
334
|
},
|
|
260
335
|
onError: (err) => {
|
|
261
336
|
compacting = false;
|
|
262
|
-
|
|
337
|
+
// Pi already reported the failure (compaction_end). "Nothing to compact" /
|
|
338
|
+
// "Already compacted" will fail identically next turn, so latch off instead
|
|
339
|
+
// of re-firing every agent_settled. Oversized messages are trimmed in
|
|
340
|
+
// agent_before_settle, so this is now only a backstop.
|
|
341
|
+
if (!/nothing to compact|already compacted/i.test(err.message)) return;
|
|
342
|
+
triggerDisabled = true;
|
|
343
|
+
ctx.ui.notify(
|
|
344
|
+
`Compaction: ${tokens.toLocaleString()} tok but nothing older to summarize. Auto-trigger disabled for this session; start a new session if context keeps growing.`,
|
|
345
|
+
"warning",
|
|
346
|
+
);
|
|
263
347
|
},
|
|
264
348
|
});
|
|
265
349
|
});
|