pi-mega-compact 0.15.4 → 0.16.0
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/dist/extensions/mega-events/context-handler.js +70 -3
- package/extensions/dashboard-client/dist/assets/OverviewTab-iKiEcOLL.js.map +1 -1
- package/extensions/dashboard-client/src/components/SessionContextGauges.tsx +6 -1
- package/extensions/dashboard-server/resolve-snapshot.ts +1 -1
- package/extensions/mega-events/context-handler.ts +81 -9
- package/package.json +1 -1
|
@@ -164,7 +164,12 @@ export function SessionContextGauges({
|
|
|
164
164
|
// Also skip rows where percent is 0 but tokens are present — these are
|
|
165
165
|
// stale heartbeats whose token_samples join returned a 0 percent
|
|
166
166
|
// (a transient state during session startup). A 0% gauge is noise.
|
|
167
|
-
if (
|
|
167
|
+
if (
|
|
168
|
+
s.percent != null &&
|
|
169
|
+
s.percent === 0 &&
|
|
170
|
+
s.tokens != null &&
|
|
171
|
+
s.tokens > 0
|
|
172
|
+
)
|
|
168
173
|
return false;
|
|
169
174
|
const repoKey = s.repoRoot ?? `(pid:${s.pid})`;
|
|
170
175
|
if (seenRepo.has(repoKey)) return false;
|
|
@@ -55,7 +55,7 @@ function mostRecentOtherRepoStateDir(launchStateDir: string): string | null {
|
|
|
55
55
|
const { DatabaseSync } =
|
|
56
56
|
require("node:sqlite") as typeof import("node:sqlite");
|
|
57
57
|
db = new DatabaseSync(indexPath, { readOnly: true });
|
|
58
|
-
|
|
58
|
+
const rows = db
|
|
59
59
|
.prepare(
|
|
60
60
|
`SELECT state_dir, last_seen FROM repo_registry
|
|
61
61
|
WHERE state_dir IS NOT NULL AND last_seen IS NOT NULL
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
openStore,
|
|
20
20
|
} from "../../src/store/sqlite.js";
|
|
21
21
|
import { autoCompactCheck } from "../../src/compact.js";
|
|
22
|
-
import { estimateSessionTokens } from "../../src/tokens.js";
|
|
22
|
+
import { estimateSessionTokens, estimateBlockTokens, estimateMessageTokens } from "../../src/tokens.js";
|
|
23
23
|
import type { MegaRuntime } from "../mega-runtime.js";
|
|
24
24
|
import { runCompact, piCompactWouldNoop } from "../mega-pipeline.js";
|
|
25
25
|
import {
|
|
@@ -324,13 +324,23 @@ export function registerContextHandler(
|
|
|
324
324
|
compactedFrom: ran.result.compactedFrom,
|
|
325
325
|
summary: ran.result.summary,
|
|
326
326
|
anchorUserMessages,
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
327
|
+
// CRITICAL-OVER ESCAPE HATCH: when context is at/over ~90% of the
|
|
328
|
+
// window, relief takes priority over the anchor floor. Without this,
|
|
329
|
+
// computeLiveTrimCut bails to null (can't satisfy the floor) and the
|
|
330
|
+
// model is fed a raw overflow that errors every turn — the
|
|
331
|
+
// "Already compacted" + overflow death-spiral (2026-08-01 incident).
|
|
332
|
+
// A thin anchor is recoverable; an overflowed session is not.
|
|
333
|
+
//
|
|
334
|
+
// CRITICAL: pct is null for OpenAI-compatible providers that don't
|
|
335
|
+
// report usage.percent (e.g. neuralwatt). Without the token-pressure
|
|
336
|
+
// fallback the hatch never armed → cut=null → raw overflow → 400
|
|
337
|
+
// "conversation too long even after compaction" (2026-08-03 incident
|
|
338
|
+
// on glm-5.2-short, 200K window). Now also fires on pressure >= 0.9
|
|
339
|
+
// (token-basis) so the hatch arms regardless of whether the provider
|
|
340
|
+
// reports pct.
|
|
341
|
+
criticalOver:
|
|
342
|
+
(pct ?? 0) >= 90 ||
|
|
343
|
+
pressure >= 0.9,
|
|
334
344
|
});
|
|
335
345
|
if (cut === null) {
|
|
336
346
|
runtime.diagCtxCutNull++;
|
|
@@ -357,7 +367,69 @@ export function registerContextHandler(
|
|
|
357
367
|
// on every replay within the same compaction epoch.
|
|
358
368
|
timestamp: runtime.rt.lastCompactAt ?? Date.now(),
|
|
359
369
|
} as unknown as AgentMessage;
|
|
360
|
-
const
|
|
370
|
+
const recentRaw = messages.slice(cut); // guardrails-allow PREVENT-PI-002: `cut` is the pre-sanitized `compactedFrom` produced by src/boundary.ts computeDropRange, so the preserved run begins on a toolPair-safe index.
|
|
371
|
+
|
|
372
|
+
// FIX 2 (2026-08-03 incident): TOKEN-BUDGET CAP on the live-trim view.
|
|
373
|
+
// Compaction fires at tier% of the window (140K for a 200K window),
|
|
374
|
+
// but a SINGLE turn can inject a huge tool output (file read, bash) that
|
|
375
|
+
// jumps context from 139K → 199K+ before the next gate fires. When that
|
|
376
|
+
// happens [summary + preserved tail] can STILL exceed the model window,
|
|
377
|
+
// and the provider rejects with 400 "conversation too long even after
|
|
378
|
+
// compaction". The anchor floor (PREVENT-PI-001) keeps ≥N user messages
|
|
379
|
+
// but has NO token cap, so a 2-message tail of two 80K bash outputs sails
|
|
380
|
+
// right past the window.
|
|
381
|
+
//
|
|
382
|
+
// Cap: when the model context window is known, reserve room for the
|
|
383
|
+
// summary + the model's max output tokens + a 10% safety margin, then
|
|
384
|
+
// drop oldest preserved messages from the front of `recentRaw` until the
|
|
385
|
+
// tail fits. Never drops below the FINAL message (always keep the latest
|
|
386
|
+
// turn so the agent can respond). This is a last-resort HARD cap — it
|
|
387
|
+
// only fires when the preserved tail alone is oversized, which is rare.
|
|
388
|
+
const ctxWindow = runtime.lastCtxWindow;
|
|
389
|
+
// Reserve room for output tokens. Use the model's reported max output
|
|
390
|
+
// when known; fall back to 10% of the window (scales with any model —
|
|
391
|
+
// 20K for a 200K window, 100K for a 1M window) so we never let the
|
|
392
|
+
// preserved tail eat the model's output budget when maxTokens is unknown.
|
|
393
|
+
const maxOutput =
|
|
394
|
+
runtime.currentModel?.maxTokens && runtime.currentModel.maxTokens > 0
|
|
395
|
+
? runtime.currentModel.maxTokens
|
|
396
|
+
: Math.ceil(ctxWindow * 0.1);
|
|
397
|
+
let recent = recentRaw;
|
|
398
|
+
if (ctxWindow > 0 && recentRaw.length > 1) {
|
|
399
|
+
const summaryTokens = estimateBlockTokens(summaryMsg.text);
|
|
400
|
+
// Reserve: summary + max output + 10% safety margin.
|
|
401
|
+
const safetyMargin = Math.ceil(ctxWindow * 0.1);
|
|
402
|
+
const budget =
|
|
403
|
+
ctxWindow - maxOutput - safetyMargin - summaryTokens;
|
|
404
|
+
if (budget > 0) {
|
|
405
|
+
// Walk recent from the front, dropping oldest first until the
|
|
406
|
+
// remaining tail fits. Use the AgentMessage→engine-text estimate via
|
|
407
|
+
// messageContentText (already imported) + estimateMessageTokens.
|
|
408
|
+
let tailTokens = 0;
|
|
409
|
+
for (let i = recentRaw.length - 1; i >= 0; i--) {
|
|
410
|
+
const m = recentRaw[i];
|
|
411
|
+
tailTokens += estimateMessageTokens({
|
|
412
|
+
text: messageContentText(m),
|
|
413
|
+
});
|
|
414
|
+
if (tailTokens > budget) {
|
|
415
|
+
// Keep from i+1 onward; but never fewer than the final message.
|
|
416
|
+
const startIdx = Math.min(i + 1, recentRaw.length - 1);
|
|
417
|
+
if (startIdx > 0) {
|
|
418
|
+
recent = recentRaw.slice(startIdx);
|
|
419
|
+
runtime.logger.warn("live-trim-tail-cap", {
|
|
420
|
+
sessionId: runtime.rt.sessionId,
|
|
421
|
+
dropped: startIdx,
|
|
422
|
+
tailTokens,
|
|
423
|
+
budget,
|
|
424
|
+
ctxWindow,
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
break;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
361
433
|
// v0.8.6: cache the trim view so subsequent gated calls in this epoch
|
|
362
434
|
// replay it verbatim (stabilizing the KV-cache prefix) instead of
|
|
363
435
|
// regenerating a fresh summary + sentinel every fire.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-mega-compact",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Layered, local, vector-backed context compressor for pi — supersede/collapse/cluster compaction with deduped inline recall.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BSD-3-Clause",
|