pi-mega-compact 0.15.3 → 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/dashboard-server/resolve-snapshot.js +38 -28
- package/dist/extensions/mega-events/context-handler.js +70 -3
- package/extensions/dashboard-client/dist/assets/{AreaChart-CMri0vM1.js → AreaChart-CrmTcE7w.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AreaChart-CMri0vM1.js.map → AreaChart-CrmTcE7w.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{BarChart-BbkxxSkM.js → BarChart-Cf8DTaXS.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{BarChart-BbkxxSkM.js.map → BarChart-Cf8DTaXS.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{CacheTab-CiHS6tod.js → CacheTab-Ce0MES6p.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{CacheTab-CiHS6tod.js.map → CacheTab-Ce0MES6p.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-NnD7EpRm.js → EventsTab-DbaUuA58.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-NnD7EpRm.js.map → EventsTab-DbaUuA58.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{HealthTab-Dlt7WTh7.js → HealthTab-BmEXkcfL.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{HealthTab-Dlt7WTh7.js.map → HealthTab-BmEXkcfL.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-DgKBZmdD.js → MaintenanceTab-n2JQrMy7.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MaintenanceTab-DgKBZmdD.js.map → MaintenanceTab-n2JQrMy7.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-SwTOzuZW.js → MemoryMapTab-Dvu7vkOm.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-SwTOzuZW.js.map → MemoryMapTab-Dvu7vkOm.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MetricsTab-CWEgBTx0.js → MetricsTab-Q0g32AmP.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-CWEgBTx0.js.map → MetricsTab-Q0g32AmP.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{OverviewTab-B0YDSSMH.js → OverviewTab-iKiEcOLL.js} +4 -4
- package/extensions/dashboard-client/dist/assets/OverviewTab-iKiEcOLL.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{ReposTab-CMKZ_a4B.js → ReposTab-DWpnv77y.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-CMKZ_a4B.js.map → ReposTab-DWpnv77y.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SessionsTab-D2cCDKB3.js → SessionsTab-G0YxR1vG.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-D2cCDKB3.js.map → SessionsTab-G0YxR1vG.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SetupTab-HUAG7xDf.js → SetupTab-C-y6H-F-.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SetupTab-HUAG7xDf.js.map → SetupTab-C-y6H-F-.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-NFCsyJhB.js → TimeSavedCard-1_LCFiJp.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-NFCsyJhB.js.map → TimeSavedCard-1_LCFiJp.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TurnsTab-BY7_AZaG.js → TurnsTab-pqOkjE4L.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TurnsTab-BY7_AZaG.js.map → TurnsTab-pqOkjE4L.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{WikiTab-MbmjqKVQ.js → WikiTab-99sKE4nB.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{WikiTab-MbmjqKVQ.js.map → WikiTab-99sKE4nB.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{button-CoVmU9Vr.js → button-YcV2VQEP.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{button-CoVmU9Vr.js.map → button-YcV2VQEP.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{card-Cp9Hpdoo.js → card-Bwxzo50p.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{card-Cp9Hpdoo.js.map → card-Bwxzo50p.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-CMM8OUO5.js → generateCategoricalChart-C6hlfMYG.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-CMM8OUO5.js.map → generateCategoricalChart-C6hlfMYG.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{index-CRHmWnRi.js → index-DRa2d4TF.js} +3 -3
- package/extensions/dashboard-client/dist/assets/{index-CRHmWnRi.js.map → index-DRa2d4TF.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{switch-jrYrJJYy.js → switch-rU-lor0v.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{switch-jrYrJJYy.js.map → switch-rU-lor0v.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{toggle-DT1AN5wS.js → toggle-h-9B4w9X.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{toggle-DT1AN5wS.js.map → toggle-h-9B4w9X.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-rJH_Rx6K.js → useSSE-BxJORWx8.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-rJH_Rx6K.js.map → useSSE-BxJORWx8.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +1 -1
- package/extensions/dashboard-client/src/components/SessionContextGauges.tsx +21 -9
- package/extensions/dashboard-client/src/tabs/OverviewTab.tsx +10 -13
- package/extensions/dashboard-server/resolve-snapshot.ts +38 -28
- package/extensions/mega-events/context-handler.ts +81 -9
- package/package.json +1 -1
- package/extensions/dashboard-client/dist/assets/OverviewTab-B0YDSSMH.js.map +0 -1
|
@@ -2,21 +2,23 @@
|
|
|
2
2
|
* dashboard-server/resolve-snapshot.ts — dynamic per-repo snapshot resolver.
|
|
3
3
|
*
|
|
4
4
|
* The dashboard server is launched with a single static stateDir, but the
|
|
5
|
-
* active pi session may be running in a different repo. This module
|
|
6
|
-
*
|
|
7
|
-
* so the Overview always shows live data from the repo pi is actually running
|
|
8
|
-
* in.
|
|
5
|
+
* active pi session may be running in a different repo. This module resolves
|
|
6
|
+
* which repo's dashboard.json to serve as the "this session" snapshot.
|
|
9
7
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* 2.
|
|
8
|
+
* Resolution order (the launch dir is the OWNer of this dashboard):
|
|
9
|
+
* 1. The launch stateDir's OWN dashboard.json — the pi process that launched
|
|
10
|
+
* this dashboard server is the session you are coding in, so its snapshot
|
|
11
|
+
* is "this session" by definition. This must win over any other repo
|
|
12
|
+
* regardless of last_seen freshness; otherwise a more-recently-active
|
|
13
|
+
* OTHER repo steals the "this session" slot and your actual session gets
|
|
14
|
+
* demoted to an "other" gauge, producing the exact duplicate-gauge bug
|
|
15
|
+
* (this session + pi-<launchrepo> both showing the same active session
|
|
16
|
+
* split across two boxes).
|
|
17
|
+
* 2. Most recently seen OTHER repo (by last_seen in repo_registry) whose
|
|
18
|
+
* dashboard.json exists and has a non-null updatedAt — fallback for when
|
|
19
|
+
* the launch dir has no live snapshot yet (e.g. dashboard launched before
|
|
20
|
+
* any compaction wrote dashboard.json).
|
|
21
|
+
* 3. The static launch stateDir's dashboard.json (original behavior).
|
|
20
22
|
*/
|
|
21
23
|
|
|
22
24
|
import { existsSync } from "node:fs";
|
|
@@ -40,17 +42,12 @@ interface RepoRegistryRow {
|
|
|
40
42
|
}
|
|
41
43
|
|
|
42
44
|
/**
|
|
43
|
-
* Find the most recently active repo's stateDir
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
* The launch stateDir is NOT excluded: if it is itself the most-recently-active
|
|
48
|
-
* repo (the common case — `/dashboard` was run in the repo you are still
|
|
49
|
-
* coding in), its live dashboard.json must be served. Excluding it would cause
|
|
50
|
-
* a stale *other*-repo snapshot to win, producing a phantom 0% "this session"
|
|
51
|
-
* gauge + a duplicate live-session gauge.
|
|
45
|
+
* Find the most recently active OTHER repo's stateDir (excluding the launch
|
|
46
|
+
* dir, which is handled by the caller) by scanning the repo registry for the
|
|
47
|
+
* repo with the highest last_seen whose dashboard.json exists and has a
|
|
48
|
+
* non-null updatedAt. Returns null if no suitable repo is found.
|
|
52
49
|
*/
|
|
53
|
-
function
|
|
50
|
+
function mostRecentOtherRepoStateDir(launchStateDir: string): string | null {
|
|
54
51
|
const indexPath = join(getIndexDir(), "index.sqlite");
|
|
55
52
|
if (!existsSync(indexPath)) return null;
|
|
56
53
|
let db;
|
|
@@ -62,9 +59,10 @@ function mostRecentRepoStateDir(_staticStateDir: string): string | null {
|
|
|
62
59
|
.prepare(
|
|
63
60
|
`SELECT state_dir, last_seen FROM repo_registry
|
|
64
61
|
WHERE state_dir IS NOT NULL AND last_seen IS NOT NULL
|
|
62
|
+
AND state_dir != @launch
|
|
65
63
|
ORDER BY last_seen DESC`,
|
|
66
64
|
)
|
|
67
|
-
.all() as unknown as RepoRegistryRow[];
|
|
65
|
+
.all({ launch: launchStateDir }) as unknown as RepoRegistryRow[];
|
|
68
66
|
for (const r of rows) {
|
|
69
67
|
const dashPath = join(r.state_dir, "dashboard.json");
|
|
70
68
|
if (!existsSync(dashPath)) continue;
|
|
@@ -93,8 +91,20 @@ export function resolveSnapshot(
|
|
|
93
91
|
staticSnapshotPath: string,
|
|
94
92
|
staticStateDir: string,
|
|
95
93
|
): ResolvedSnapshot {
|
|
96
|
-
//
|
|
97
|
-
|
|
94
|
+
// 1. The launch dir's OWN snapshot is "this session" — the pi process that
|
|
95
|
+
// launched this dashboard is the session you are coding in. It wins over
|
|
96
|
+
// any other repo regardless of last_seen, so a more-recently-active OTHER
|
|
97
|
+
// repo can't steal the "this session" slot and demote your actual session
|
|
98
|
+
// to a duplicate "other" gauge.
|
|
99
|
+
if (existsSync(staticSnapshotPath)) {
|
|
100
|
+
const launchSnap = readSnapshot(staticSnapshotPath);
|
|
101
|
+
if (launchSnap.updatedAt) {
|
|
102
|
+
return { snapshot: launchSnap, stateDir: staticStateDir };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// 2. Fall back to the most recently active OTHER repo (launch dir had no
|
|
106
|
+
// live snapshot yet).
|
|
107
|
+
const activeDir = mostRecentOtherRepoStateDir(staticStateDir);
|
|
98
108
|
if (activeDir) {
|
|
99
109
|
const activePath = join(activeDir, "dashboard.json");
|
|
100
110
|
const snap = readSnapshot(activePath);
|
|
@@ -102,7 +112,7 @@ export function resolveSnapshot(
|
|
|
102
112
|
return { snapshot: snap, stateDir: activeDir };
|
|
103
113
|
}
|
|
104
114
|
}
|
|
105
|
-
//
|
|
115
|
+
// 3. Last resort: the static launch path (even if stale).
|
|
106
116
|
return {
|
|
107
117
|
snapshot: readSnapshot(staticSnapshotPath),
|
|
108
118
|
stateDir: staticStateDir,
|
|
@@ -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",
|