pi-mega-compact 0.11.8 → 0.11.9
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/gather-debug-keys.js +18 -0
- package/dist/extensions/dashboard-server/gather-debug.js +129 -0
- package/dist/extensions/dashboard-server/routes-maintenance.js +6 -0
- package/dist/extensions/mega-config.js +1 -0
- package/dist/extensions/mega-events/agent-handlers.js +74 -5
- package/dist/extensions/mega-events/context-handler.js +67 -3
- package/dist/extensions/mega-runtime/snapshot.js +1 -1
- package/dist/extensions/mega-trim.js +10 -0
- package/extensions/dashboard-client/dist/assets/{AchievementTiles-BESF1HT1.js → AchievementTiles-DFitrb4Y.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AchievementTiles-BESF1HT1.js.map → AchievementTiles-DFitrb4Y.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{AchievementsTab-8RzNnz8u.js → AchievementsTab-PJrmq8pa.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{AchievementsTab-8RzNnz8u.js.map → AchievementsTab-PJrmq8pa.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/CacheTab-C1WE7dEx.js +2 -0
- package/extensions/dashboard-client/dist/assets/CacheTab-C1WE7dEx.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{ConfigTab-D85fB9HP.js → ConfigTab-Dx-RpO8C.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ConfigTab-D85fB9HP.js.map → ConfigTab-Dx-RpO8C.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{EventsTab-TCLR81G5.js → EventsTab-C63BEWEs.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{EventsTab-TCLR81G5.js.map → EventsTab-C63BEWEs.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{GameTab-BqneNRYH.js → GameTab-DHpJE6km.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{GameTab-BqneNRYH.js.map → GameTab-DHpJE6km.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/MaintenanceTab-BSv0GfBb.js +2 -0
- package/extensions/dashboard-client/dist/assets/MaintenanceTab-BSv0GfBb.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-CpjMQwRr.js → MemoryMapTab-Cg1fgEx_.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MemoryMapTab-CpjMQwRr.js.map → MemoryMapTab-Cg1fgEx_.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{MetricsTab-LxNtNzLQ.js → MetricsTab-DCahxNOQ.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{MetricsTab-LxNtNzLQ.js.map → MetricsTab-DCahxNOQ.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{OverviewTab-BXKUZbT6.js → OverviewTab-RYvO1SZL.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{OverviewTab-BXKUZbT6.js.map → OverviewTab-RYvO1SZL.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{ReposTab-DTooKeAi.js → ReposTab-BDGr_UXb.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{ReposTab-DTooKeAi.js.map → ReposTab-BDGr_UXb.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SessionsTab-Devulgm9.js → SessionsTab-ByrTnHaB.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SessionsTab-Devulgm9.js.map → SessionsTab-ByrTnHaB.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{SetupTab-By9eqUoE.js → SetupTab-CELafKis.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{SetupTab-By9eqUoE.js.map → SetupTab-CELafKis.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-z4M8TDLm.js → TimeSavedCard-BLWtmfr_.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TimeSavedCard-z4M8TDLm.js.map → TimeSavedCard-BLWtmfr_.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TopicsTab-bCGZSfLN.js → TopicsTab-8Fsh_Igx.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TopicsTab-bCGZSfLN.js.map → TopicsTab-8Fsh_Igx.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{TurnsTab-m_XzE0H4.js → TurnsTab-D7mgXDe2.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{TurnsTab-m_XzE0H4.js.map → TurnsTab-D7mgXDe2.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{index-PRkilPXw.js → index-h3jgEs6G.js} +10 -10
- package/extensions/dashboard-client/dist/assets/{index-PRkilPXw.js.map → index-h3jgEs6G.js.map} +1 -1
- package/extensions/dashboard-client/dist/assets/{useSSE-CGzgc2IK.js → useSSE-C5lmipBF.js} +2 -2
- package/extensions/dashboard-client/dist/assets/{useSSE-CGzgc2IK.js.map → useSSE-C5lmipBF.js.map} +1 -1
- package/extensions/dashboard-client/dist/index.html +1 -1
- package/extensions/dashboard-client/src/api/client.ts +5 -0
- package/extensions/dashboard-client/src/tabs/CacheTab.tsx +29 -2
- package/extensions/dashboard-client/src/tabs/MaintenanceTab.tsx +101 -0
- package/extensions/dashboard-server/api-contracts/index.ts +1 -0
- package/extensions/dashboard-server/api-contracts/maintenance.ts +13 -0
- package/extensions/dashboard-server/gather-debug-keys.ts +18 -0
- package/extensions/dashboard-server/gather-debug.ts +136 -0
- package/extensions/dashboard-server/routes-maintenance.ts +7 -0
- package/extensions/mega-config.ts +4 -0
- package/extensions/mega-events/agent-handlers.ts +88 -5
- package/extensions/mega-events/context-handler.ts +72 -3
- package/extensions/mega-runtime/snapshot.ts +1 -1
- package/extensions/mega-trim.ts +15 -0
- package/package.json +1 -1
- package/extensions/dashboard-client/dist/assets/CacheTab-CUIiUgh3.js +0 -2
- package/extensions/dashboard-client/dist/assets/CacheTab-CUIiUgh3.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/MaintenanceTab-LblMg3NB.js +0 -2
- package/extensions/dashboard-client/dist/assets/MaintenanceTab-LblMg3NB.js.map +0 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{r as e}from"./index-
|
|
2
|
-
//# sourceMappingURL=useSSE-
|
|
1
|
+
import{r as e}from"./index-h3jgEs6G.js";function R(i={}){const{maxEvents:c=500,maxReconnects:l=5}=i,[f,v]=e.useState([]),[E,n]=e.useState("connecting"),[S,p]=e.useState(0),[m,d]=e.useState(null),o=e.useRef(0),s=e.useRef(null),r=e.useCallback(()=>{s.current&&s.current.close(),n("connecting");const t=new EventSource("/api/events");s.current=t,t.onopen=()=>{n("connected"),o.current=0},t.onmessage=g=>{try{const x=JSON.parse(g.data);v(u=>{const a=[...u,x];return a.length>c?a.slice(-c):a}),p(u=>u+1),d(Date.now())}catch{}},t.onerror=()=>{t.close(),o.current<l?(o.current+=1,n("connecting"),setTimeout(r,1e3)):n("disconnected")}},[c,l]);return e.useEffect(()=>(r(),()=>{s.current?.close()}),[r]),{events:f,status:E,eventCount:S,lastEventAt:m}}export{R as u};
|
|
2
|
+
//# sourceMappingURL=useSSE-C5lmipBF.js.map
|
package/extensions/dashboard-client/dist/assets/{useSSE-CGzgc2IK.js.map → useSSE-C5lmipBF.js.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSSE-
|
|
1
|
+
{"version":3,"file":"useSSE-C5lmipBF.js","sources":["../../src/hooks/useSSE.ts"],"sourcesContent":["/**\n * dashboard-client/src/hooks/useSSE.ts — Server-Sent Events hook.\n *\n * Connects to /api/events, provides real-time event stream.\n * SPRINT-B1: basic connection. SPRINT-D1: reconnect with backoff.\n */\n\nimport { useState, useEffect, useRef, useCallback } from 'react';\nimport type { SseEvent } from '../../../dashboard-server/api-contracts';\n\nexport type SseStatus = 'connecting' | 'connected' | 'disconnected' | 'error';\n\nexport interface UseSSEResult {\n events: SseEvent[];\n status: SseStatus;\n eventCount: number;\n lastEventAt: number | null;\n}\n\nexport interface UseSSEOptions {\n /** Max events to keep in memory (ring buffer). */\n maxEvents?: number;\n /** Max reconnect attempts before giving up. */\n maxReconnects?: number;\n}\n\n// SPRINT-D1-REMAINING: exponential backoff (1s → 2s → 4s → max 30s).\n// SPRINT-D1-REMAINING: after 5 failures, set status='disconnected'.\n\nexport function useSSE(options: UseSSEOptions = {}): UseSSEResult {\n const { maxEvents = 500, maxReconnects = 5 } = options;\n const [events, setEvents] = useState<SseEvent[]>([]);\n const [status, setStatus] = useState<SseStatus>('connecting');\n const [eventCount, setEventCount] = useState(0);\n const [lastEventAt, setLastEventAt] = useState<number | null>(null);\n const reconnectCount = useRef(0);\n const sourceRef = useRef<EventSource | null>(null);\n\n const connect = useCallback(() => {\n if (sourceRef.current) {\n sourceRef.current.close();\n }\n setStatus('connecting');\n const source = new EventSource('/api/events');\n sourceRef.current = source;\n\n source.onopen = () => {\n setStatus('connected');\n reconnectCount.current = 0;\n };\n\n source.onmessage = (event) => {\n try {\n const parsed = JSON.parse(event.data) as SseEvent;\n setEvents(prev => {\n const next = [...prev, parsed];\n return next.length > maxEvents ? next.slice(-maxEvents) : next;\n });\n setEventCount(c => c + 1);\n setLastEventAt(Date.now());\n } catch {\n // non-fatal: skip malformed events\n }\n };\n\n source.onerror = () => {\n source.close();\n if (reconnectCount.current < maxReconnects) {\n reconnectCount.current += 1;\n setStatus('connecting');\n // SPRINT-D1: use exponential backoff instead of fixed delay\n setTimeout(connect, 1000);\n } else {\n setStatus('disconnected');\n }\n };\n }, [maxEvents, maxReconnects]);\n\n useEffect(() => {\n connect();\n return () => {\n sourceRef.current?.close();\n };\n }, [connect]);\n\n return { events, status, eventCount, lastEventAt };\n}\n"],"names":["useSSE","options","maxEvents","maxReconnects","events","setEvents","useState","status","setStatus","eventCount","setEventCount","lastEventAt","setLastEventAt","reconnectCount","useRef","sourceRef","connect","useCallback","source","event","parsed","prev","next","c","useEffect"],"mappings":"wCA6BO,SAASA,EAAOC,EAAyB,GAAkB,CAChE,KAAM,CAAE,UAAAC,EAAY,IAAK,cAAAC,EAAgB,GAAMF,EACzC,CAACG,EAAQC,CAAS,EAAIC,EAAAA,SAAqB,CAAA,CAAE,EAC7C,CAACC,EAAQC,CAAS,EAAIF,EAAAA,SAAoB,YAAY,EACtD,CAACG,EAAYC,CAAa,EAAIJ,EAAAA,SAAS,CAAC,EACxC,CAACK,EAAaC,CAAc,EAAIN,EAAAA,SAAwB,IAAI,EAC5DO,EAAiBC,EAAAA,OAAO,CAAC,EACzBC,EAAYD,EAAAA,OAA2B,IAAI,EAE3CE,EAAUC,EAAAA,YAAY,IAAM,CAC5BF,EAAU,SACZA,EAAU,QAAQ,MAAA,EAEpBP,EAAU,YAAY,EACtB,MAAMU,EAAS,IAAI,YAAY,aAAa,EAC5CH,EAAU,QAAUG,EAEpBA,EAAO,OAAS,IAAM,CACpBV,EAAU,WAAW,EACrBK,EAAe,QAAU,CAC3B,EAEAK,EAAO,UAAaC,GAAU,CAC5B,GAAI,CACF,MAAMC,EAAS,KAAK,MAAMD,EAAM,IAAI,EACpCd,EAAUgB,GAAQ,CAChB,MAAMC,EAAO,CAAC,GAAGD,EAAMD,CAAM,EAC7B,OAAOE,EAAK,OAASpB,EAAYoB,EAAK,MAAM,CAACpB,CAAS,EAAIoB,CAC5D,CAAC,EACDZ,EAAca,GAAKA,EAAI,CAAC,EACxBX,EAAe,KAAK,KAAK,CAC3B,MAAQ,CAER,CACF,EAEAM,EAAO,QAAU,IAAM,CACrBA,EAAO,MAAA,EACHL,EAAe,QAAUV,GAC3BU,EAAe,SAAW,EAC1BL,EAAU,YAAY,EAEtB,WAAWQ,EAAS,GAAI,GAExBR,EAAU,cAAc,CAE5B,CACF,EAAG,CAACN,EAAWC,CAAa,CAAC,EAE7BqB,OAAAA,EAAAA,UAAU,KACRR,EAAA,EACO,IAAM,CACXD,EAAU,SAAS,MAAA,CACrB,GACC,CAACC,CAAO,CAAC,EAEL,CAAE,OAAAZ,EAAQ,OAAAG,EAAQ,WAAAE,EAAY,YAAAE,CAAA,CACvC"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>mega-compact dashboard</title>
|
|
7
|
-
<script type="module" crossorigin src="./assets/index-
|
|
7
|
+
<script type="module" crossorigin src="./assets/index-h3jgEs6G.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="./assets/index-CUNn22iV.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
@@ -39,6 +39,7 @@ import type {
|
|
|
39
39
|
SchemaHealthResponse,
|
|
40
40
|
MaintenanceAction,
|
|
41
41
|
MaintenanceActionResult,
|
|
42
|
+
DebugBundleResponse,
|
|
42
43
|
ProviderCacheResponse,
|
|
43
44
|
CacheStripesResponse,
|
|
44
45
|
SetupStatusResponse,
|
|
@@ -284,6 +285,10 @@ export function postMaintenanceAction(
|
|
|
284
285
|
);
|
|
285
286
|
}
|
|
286
287
|
|
|
288
|
+
export function fetchDebugBundle(): Promise<DebugBundleResponse> {
|
|
289
|
+
return postJson<DebugBundleResponse>("/api/maintenance/gather-debug", {});
|
|
290
|
+
}
|
|
291
|
+
|
|
287
292
|
// ─── Setup wizard (P0b) ────────────────────────────────────────────────────
|
|
288
293
|
|
|
289
294
|
/** GET /api/setup-status — current embedder configuration. */
|
|
@@ -11,8 +11,7 @@
|
|
|
11
11
|
* 5s polling on provider cache and activity snapshot; 15s on stripes + trend.
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import
|
|
15
|
-
import { useCallback } from "react";
|
|
14
|
+
import { useState, useCallback } from "react";
|
|
16
15
|
import type {
|
|
17
16
|
SnapshotResponse,
|
|
18
17
|
ProviderCacheResponse,
|
|
@@ -33,6 +32,7 @@ import { StripeDistributionCard } from "../components/StripeDistributionCard";
|
|
|
33
32
|
import { CacheHitRateTrendCard } from "../components/CacheHitRateTrendCard";
|
|
34
33
|
|
|
35
34
|
export default function CacheTab(): React.ReactElement {
|
|
35
|
+
const [infoExpanded, setInfoExpanded] = useState(false);
|
|
36
36
|
/* --- Provider prompt cache (5s poll) --- */
|
|
37
37
|
const {
|
|
38
38
|
data: providerCache,
|
|
@@ -75,6 +75,33 @@ export default function CacheTab(): React.ReactElement {
|
|
|
75
75
|
|
|
76
76
|
return (
|
|
77
77
|
<div className="cache-tab">
|
|
78
|
+
<div className="card-section">
|
|
79
|
+
<button
|
|
80
|
+
type="button"
|
|
81
|
+
className="info-toggle"
|
|
82
|
+
onClick={() => setInfoExpanded(!infoExpanded)}
|
|
83
|
+
>
|
|
84
|
+
{infoExpanded ? "Hide Guidance" : "Show Cache-Friendly Prompt Ordering Guidance"}
|
|
85
|
+
</button>
|
|
86
|
+
{infoExpanded && (
|
|
87
|
+
<div className="info-panel">
|
|
88
|
+
<h3>Cache-Friendly Prompt Ordering</h3>
|
|
89
|
+
<ul>
|
|
90
|
+
<li>Keep system prompts and stable context at the top of your conversation — the provider caches the leading prefix.</li>
|
|
91
|
+
<li>Tool results and volatile content are automatically moved to the tail by message separation (<code>MEGACOMPACT_MESSAGE_SEPARATION</code>) so they don't invalidate the cache prefix.</li>
|
|
92
|
+
<li>Cache striping (<code>MEGACOMPACT_CACHE_STRIPING</code>) further orders stable context by a stability score so the most durable chunks lead.</li>
|
|
93
|
+
<li>Avoid inserting new instructions mid-conversation — prepend them instead.</li>
|
|
94
|
+
</ul>
|
|
95
|
+
{snapshot && (
|
|
96
|
+
<div className="cache-status-indicator">
|
|
97
|
+
<strong>Status:</strong>{" "}
|
|
98
|
+
{snapshot.config.messageSeparation ? "Separation Enabled" : "Separation Disabled (Check env)"} |{" "}
|
|
99
|
+
{snapshot.config.cacheStriping ? "Striping Enabled" : "Striping Disabled (Check env)"}
|
|
100
|
+
</div>
|
|
101
|
+
)}
|
|
102
|
+
</div>
|
|
103
|
+
)}
|
|
104
|
+
</div>
|
|
78
105
|
{/* ==============================================================
|
|
79
106
|
Section 1 -- Provider Prompt Cache
|
|
80
107
|
============================================================== */}
|
|
@@ -16,12 +16,14 @@ import type {
|
|
|
16
16
|
SchemaHealthResponse,
|
|
17
17
|
MaintenanceAction,
|
|
18
18
|
MaintenanceActionResult,
|
|
19
|
+
DebugBundleResponse,
|
|
19
20
|
} from "@contracts";
|
|
20
21
|
import { useApi } from "../hooks/useApi";
|
|
21
22
|
import {
|
|
22
23
|
fetchDbStats,
|
|
23
24
|
fetchSchemaHealth,
|
|
24
25
|
postMaintenanceAction,
|
|
26
|
+
fetchDebugBundle,
|
|
25
27
|
} from "../api/client";
|
|
26
28
|
|
|
27
29
|
// ---------------------------------------------------------------------------
|
|
@@ -419,6 +421,104 @@ function ActionsCard(): React.ReactElement {
|
|
|
419
421
|
);
|
|
420
422
|
}
|
|
421
423
|
|
|
424
|
+
// ---------------------------------------------------------------------------
|
|
425
|
+
// Debug bundle card — gather diagnostic info for bug reports
|
|
426
|
+
// ---------------------------------------------------------------------------
|
|
427
|
+
|
|
428
|
+
function DebugBundleCard(): React.ReactElement {
|
|
429
|
+
const [bundle, setBundle] = useState<DebugBundleResponse | null>(null);
|
|
430
|
+
const [loading, setLoading] = useState(false);
|
|
431
|
+
const [error, setError] = useState<string | null>(null);
|
|
432
|
+
const [showJson, setShowJson] = useState(false);
|
|
433
|
+
|
|
434
|
+
const gather = useCallback(async () => {
|
|
435
|
+
setLoading(true);
|
|
436
|
+
setError(null);
|
|
437
|
+
try {
|
|
438
|
+
const res = await fetchDebugBundle();
|
|
439
|
+
setBundle(res);
|
|
440
|
+
setShowJson(true);
|
|
441
|
+
} catch (err) {
|
|
442
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
443
|
+
} finally {
|
|
444
|
+
setLoading(false);
|
|
445
|
+
}
|
|
446
|
+
}, []);
|
|
447
|
+
|
|
448
|
+
const copyToClipboard = useCallback(() => {
|
|
449
|
+
if (!bundle) return;
|
|
450
|
+
const text = JSON.stringify(bundle, null, 2);
|
|
451
|
+
void navigator.clipboard.writeText(text).catch(() => {});
|
|
452
|
+
}, [bundle]);
|
|
453
|
+
|
|
454
|
+
const downloadJson = useCallback(() => {
|
|
455
|
+
if (!bundle) return;
|
|
456
|
+
const text = JSON.stringify(bundle, null, 2);
|
|
457
|
+
const blob = new Blob([text], { type: "application/json" });
|
|
458
|
+
const url = URL.createObjectURL(blob);
|
|
459
|
+
const a = document.createElement("a");
|
|
460
|
+
a.href = url;
|
|
461
|
+
a.download = `mega-compact-debug-bundle-${bundle.builtAt}.json`;
|
|
462
|
+
a.click();
|
|
463
|
+
URL.revokeObjectURL(url);
|
|
464
|
+
}, [bundle]);
|
|
465
|
+
|
|
466
|
+
const criticalCount = bundle?.criticalEvents.length ?? 0;
|
|
467
|
+
|
|
468
|
+
return (
|
|
469
|
+
<div className="card">
|
|
470
|
+
<div className="card-header">
|
|
471
|
+
Debug Bundle
|
|
472
|
+
{criticalCount > 0 && (
|
|
473
|
+
<span className="text-warn" style={{ marginLeft: "1em" }}>
|
|
474
|
+
{criticalCount} critical event{criticalCount !== 1 ? "s" : ""} found
|
|
475
|
+
</span>
|
|
476
|
+
)}
|
|
477
|
+
</div>
|
|
478
|
+
<div className="card-body">
|
|
479
|
+
<p className="text-muted" style={{ marginBottom: "0.75em" }}>
|
|
480
|
+
Gather a diagnostic bundle (recent events, config flags, schema health,
|
|
481
|
+
store stats) to attach to a bug report. Critical/compaction events are
|
|
482
|
+
highlighted.
|
|
483
|
+
</p>
|
|
484
|
+
<button onClick={gather} disabled={loading} style={{ marginRight: "0.5em" }}>
|
|
485
|
+
{loading ? "Gathering…" : "Gather Debug Logs"}
|
|
486
|
+
</button>
|
|
487
|
+
{error && <div className="text-error">{error}</div>}
|
|
488
|
+
{bundle && showJson && (
|
|
489
|
+
<>
|
|
490
|
+
<div style={{ marginTop: "0.75em", marginBottom: "0.5em" }}>
|
|
491
|
+
<button onClick={copyToClipboard} style={{ marginRight: "0.5em" }}>
|
|
492
|
+
Copy to clipboard
|
|
493
|
+
</button>
|
|
494
|
+
<button onClick={downloadJson}>Download JSON</button>
|
|
495
|
+
<button
|
|
496
|
+
onClick={() => setShowJson((v) => !v)}
|
|
497
|
+
style={{ marginLeft: "0.5em" }}
|
|
498
|
+
>
|
|
499
|
+
{showJson ? "Hide" : "Show"}
|
|
500
|
+
</button>
|
|
501
|
+
</div>
|
|
502
|
+
{criticalCount > 0 && (
|
|
503
|
+
<div className="stat-section">
|
|
504
|
+
<div className="stat-label text-warn">
|
|
505
|
+
Critical events ({criticalCount})
|
|
506
|
+
</div>
|
|
507
|
+
<pre className="text-mono" style={{ maxHeight: "12em", overflow: "auto" }}>
|
|
508
|
+
{JSON.stringify(bundle.criticalEvents, null, 2)}
|
|
509
|
+
</pre>
|
|
510
|
+
</div>
|
|
511
|
+
)}
|
|
512
|
+
<pre className="text-mono" style={{ maxHeight: "24em", overflow: "auto" }}>
|
|
513
|
+
{JSON.stringify(bundle, null, 2)}
|
|
514
|
+
</pre>
|
|
515
|
+
</>
|
|
516
|
+
)}
|
|
517
|
+
</div>
|
|
518
|
+
</div>
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
|
|
422
522
|
// ---------------------------------------------------------------------------
|
|
423
523
|
// Main tab component
|
|
424
524
|
// ---------------------------------------------------------------------------
|
|
@@ -453,6 +553,7 @@ export default function MaintenanceTab(): React.ReactElement {
|
|
|
453
553
|
<DbStatsCard data={dbStats} loading={statsLoading} error={statsError} />
|
|
454
554
|
</div>
|
|
455
555
|
<ActionsCard />
|
|
556
|
+
<DebugBundleCard />
|
|
456
557
|
</div>
|
|
457
558
|
);
|
|
458
559
|
}
|
|
@@ -82,3 +82,16 @@ export type MaintenanceAction =
|
|
|
82
82
|
| { action: "reconcile-dedup" }
|
|
83
83
|
| { action: "prune"; daysOld: number }
|
|
84
84
|
| { action: "integrity-check" };
|
|
85
|
+
|
|
86
|
+
/** Debug bundle for gathering diagnostic info. */
|
|
87
|
+
export interface DebugBundleResponse {
|
|
88
|
+
builtAt: number;
|
|
89
|
+
version: string;
|
|
90
|
+
stateDir: string;
|
|
91
|
+
config: Record<string, string>;
|
|
92
|
+
schemaHealth: SchemaHealthResponse | { error: string };
|
|
93
|
+
storeStats: DbStatsResponse | { error: string };
|
|
94
|
+
dashboardSnapshot: unknown | { error: string };
|
|
95
|
+
recentEvents: unknown[];
|
|
96
|
+
criticalEvents: unknown[];
|
|
97
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const MEGACOMPACT_ENV_KEYS = [
|
|
2
|
+
"MEGACOMPACT_AUTO",
|
|
3
|
+
"MEGACOMPACT_THRESHOLD_TOKENS",
|
|
4
|
+
"MEGACOMPACT_TIER",
|
|
5
|
+
"MEGACOMPACT_DBMIRROR",
|
|
6
|
+
"MEGACOMPACT_MESSAGE_SEPARATION",
|
|
7
|
+
"MEGACOMPACT_CACHE_STRIPING",
|
|
8
|
+
"MEGACOMPACT_LEGACY_DURABLE_TRIM",
|
|
9
|
+
"MEGACOMPACT_RACE_GUARD_STRICT",
|
|
10
|
+
"MEGACOMPACT_RECALL_TAIL_INJECT",
|
|
11
|
+
"MEGACOMPACT_PGLITE_DISABLED",
|
|
12
|
+
"MEGACOMPACT_WIKI_REBUILD_EVERY",
|
|
13
|
+
"MEGACOMPACT_WIKI_SEED_FROM_TURNS",
|
|
14
|
+
"MEGACOMPACT_MEMORY_GRAPH_SEED_TURNS",
|
|
15
|
+
"MEGACOMPACT_MEMORY_GRAPH_SEED_TIER",
|
|
16
|
+
"MEGACOMPACT_MEMORY_GRAPH_SEED_BATCH",
|
|
17
|
+
"MEGACOMPACT_MEMORY_GRAPH_SEED_MAX",
|
|
18
|
+
];
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* gather-debug.ts — assemble a diagnostic bundle for bug reports.
|
|
3
|
+
*
|
|
4
|
+
* POST /api/maintenance/gather-debug → DebugBundleResponse. Reads local
|
|
5
|
+
* SQLite + FS only (PREVENT-PI-004: zero network). Each section is non-fatal:
|
|
6
|
+
* a read failure records `{ error }` for that section and continues, so the
|
|
7
|
+
* bundle always returns 200 with whatever could be gathered.
|
|
8
|
+
*/
|
|
9
|
+
import { readFileSync, existsSync } from "node:fs";
|
|
10
|
+
import { createRequire } from "node:module";
|
|
11
|
+
import type { ServerResponse } from "node:http";
|
|
12
|
+
import type { RouteContext } from "./routes-core.js";
|
|
13
|
+
import type { DebugBundleResponse } from "./api-contracts/maintenance.js";
|
|
14
|
+
import { defaultMetricsPath, defaultEventsPath } from "../../src/monitoring.js";
|
|
15
|
+
import { ownVersion } from "../mega-runtime/helpers.js";
|
|
16
|
+
import { MEGACOMPACT_ENV_KEYS } from "./gather-debug-keys.js";
|
|
17
|
+
|
|
18
|
+
/** Event-name patterns that indicate a critical/compaction class of error. */
|
|
19
|
+
const CRITICAL_PATTERN =
|
|
20
|
+
/critical|overflow|already[.\s-]?compacted|compaction[.\s-]?failed|stream[.\s-]?death|poisoned|prefix_stability/i;
|
|
21
|
+
|
|
22
|
+
const require = createRequire(import.meta.url);
|
|
23
|
+
|
|
24
|
+
/** Lazy store opener (mirrors routes-maintenance.ts's lazyStoreUtils). */
|
|
25
|
+
function openStoreDb(ctx: RouteContext) {
|
|
26
|
+
const mod = require("../../src/store/sqlite/utils.js") as typeof import("../../src/store/sqlite/utils.js");
|
|
27
|
+
return mod.openStore(ctx.stateDir);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Lazy maintenance loaders (mirrors routes-maintenance.ts's lazyMaintenance). */
|
|
31
|
+
function loadMaintenance() {
|
|
32
|
+
return require("../../src/store/sqlite/maintenance.js") as typeof import("../../src/store/sqlite/maintenance.js");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Send the debug bundle as JSON. Always 200 (partial on read failure). */
|
|
36
|
+
export function sendGatherDebug(res: ServerResponse, ctx: RouteContext): void {
|
|
37
|
+
const result: DebugBundleResponse = {
|
|
38
|
+
builtAt: Date.now(),
|
|
39
|
+
version: ownVersion(),
|
|
40
|
+
stateDir: ctx.stateDir,
|
|
41
|
+
config: {},
|
|
42
|
+
schemaHealth: { error: "not fetched" },
|
|
43
|
+
storeStats: { error: "not fetched" },
|
|
44
|
+
dashboardSnapshot: { error: "not fetched" },
|
|
45
|
+
recentEvents: [],
|
|
46
|
+
criticalEvents: [],
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// 1. Config flags (no secrets — MEGACOMPACT_* only)
|
|
50
|
+
for (const key of MEGACOMPACT_ENV_KEYS) {
|
|
51
|
+
result.config[key] = process.env[key] ?? "<default>";
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 2. Schema health
|
|
55
|
+
try {
|
|
56
|
+
const maint = loadMaintenance();
|
|
57
|
+
const integrity = maint.integrityCheck(ctx.stateDir);
|
|
58
|
+
const db = openStoreDb(ctx);
|
|
59
|
+
const metaRow = db
|
|
60
|
+
.prepare("SELECT value FROM meta WHERE key = ?")
|
|
61
|
+
.get("schema_version") as { value: string } | undefined;
|
|
62
|
+
const schemaVersion = metaRow ? Number(metaRow.value) : 0;
|
|
63
|
+
const fkRows = db.prepare("PRAGMA foreign_key_check").all() as Array<Record<string, unknown>>;
|
|
64
|
+
result.schemaHealth = {
|
|
65
|
+
type: "schema-health" as const,
|
|
66
|
+
schemaVersion,
|
|
67
|
+
integrity,
|
|
68
|
+
fkCheck: fkRows.map((r) => JSON.stringify(r)),
|
|
69
|
+
columns: [],
|
|
70
|
+
healthy: true,
|
|
71
|
+
};
|
|
72
|
+
} catch (e) {
|
|
73
|
+
result.schemaHealth = { error: e instanceof Error ? e.message : String(e) };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// 3. Store stats
|
|
77
|
+
try {
|
|
78
|
+
const maint = loadMaintenance();
|
|
79
|
+
const raw = maint.getDbStats(ctx.stateDir);
|
|
80
|
+
result.storeStats = {
|
|
81
|
+
type: "db-stats" as const,
|
|
82
|
+
tables: Object.entries(raw.tableCounts).map(([table, rowCount]) => ({ table, rowCount })),
|
|
83
|
+
storage: {
|
|
84
|
+
files: { dbBytes: raw.dbBytes, walBytes: raw.walBytes, shmBytes: raw.shmBytes },
|
|
85
|
+
pageSize: raw.pageSize,
|
|
86
|
+
pageCount: raw.pageCount,
|
|
87
|
+
freelistPages: raw.freelistPages,
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
} catch (e) {
|
|
91
|
+
result.storeStats = { error: e instanceof Error ? e.message : String(e) };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 4. Dashboard snapshot
|
|
95
|
+
try {
|
|
96
|
+
const path = defaultMetricsPath(ctx.stateDir);
|
|
97
|
+
if (existsSync(path)) {
|
|
98
|
+
const raw = readFileSync(path, "utf-8");
|
|
99
|
+
result.dashboardSnapshot = JSON.parse(raw) as unknown;
|
|
100
|
+
} else {
|
|
101
|
+
result.dashboardSnapshot = { error: "no snapshot" };
|
|
102
|
+
}
|
|
103
|
+
} catch (e) {
|
|
104
|
+
result.dashboardSnapshot = { error: e instanceof Error ? e.message : String(e) };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// 5. Recent + critical events (tail ~200 lines of events.log)
|
|
108
|
+
try {
|
|
109
|
+
const path = defaultEventsPath(ctx.stateDir);
|
|
110
|
+
if (existsSync(path)) {
|
|
111
|
+
const lines = readFileSync(path, "utf-8").split("\n").filter(Boolean);
|
|
112
|
+
for (const line of lines.slice(-200)) {
|
|
113
|
+
try {
|
|
114
|
+
const ev = JSON.parse(line) as unknown;
|
|
115
|
+
result.recentEvents.push(ev);
|
|
116
|
+
if (CRITICAL_PATTERN.test(line)) result.criticalEvents.push(ev);
|
|
117
|
+
} catch {
|
|
118
|
+
continue; // skip unparseable line (PREVENT-001)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
} catch (e) {
|
|
123
|
+
result.recentEvents = [{ error: e instanceof Error ? e.message : String(e) }];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Send (local helper to avoid importing sendJson from a non-exporting module)
|
|
127
|
+
try {
|
|
128
|
+
const body = JSON.stringify(result);
|
|
129
|
+
res.writeHead(200, { "Content-Type": "application/json" }); // guardrails-allow PREVENT-PI-004: loopback dashboard response (local)
|
|
130
|
+
res.end(body);
|
|
131
|
+
} catch {
|
|
132
|
+
// last resort — never leave the response hanging
|
|
133
|
+
res.writeHead(500, { "Content-Type": "application/json" }); // guardrails-allow PREVENT-PI-004: loopback dashboard response (local)
|
|
134
|
+
res.end('{"error":"failed to serialize debug bundle"}');
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import { createRequire } from "node:module";
|
|
15
15
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
16
|
+
import { sendGatherDebug } from "./gather-debug.js";
|
|
16
17
|
|
|
17
18
|
import type { RouteContext } from "./routes-core.js";
|
|
18
19
|
|
|
@@ -77,6 +78,12 @@ export function handleMaintenance(
|
|
|
77
78
|
return true;
|
|
78
79
|
}
|
|
79
80
|
|
|
81
|
+
// ── POST /api/maintenance/gather-debug ──────────────────────────
|
|
82
|
+
if (method === "POST" && url === "/api/maintenance/gather-debug") {
|
|
83
|
+
sendGatherDebug(res, ctx);
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
|
|
80
87
|
// ── GET /api/maintenance ──────────────────────────────────────────
|
|
81
88
|
if (method === "GET" && url === "/api/maintenance") {
|
|
82
89
|
sendDbStats(res, ctx);
|
|
@@ -172,6 +172,9 @@ export interface MegaConfig {
|
|
|
172
172
|
* from volatile tool results so the prompt-cache prefix stays stable.
|
|
173
173
|
* Default OFF (flag-OFF = byte-identical pre-sprint). */
|
|
174
174
|
messageSeparation: boolean;
|
|
175
|
+
/** P3: Cache-aware striping (PLAN_V2 Phase 3). Inserts stability-ordered
|
|
176
|
+
* cache stripes between summaries and thread. Default OFF. */
|
|
177
|
+
cacheStriping: boolean;
|
|
175
178
|
debug: boolean;
|
|
176
179
|
/** Master reconciliation: TUI shutdown widget (MEGACOMPACT_TUI_WIDGET=0 to disable). */
|
|
177
180
|
tuiWidget: boolean;
|
|
@@ -357,6 +360,7 @@ export function loadConfig(): MegaConfig {
|
|
|
357
360
|
windowDedupe: envBool("MEGACOMPACT_WINDOW_DEDUPE", true),
|
|
358
361
|
recallTailInject: envBool("MEGACOMPACT_RECALL_TAIL_INJECT", true),
|
|
359
362
|
messageSeparation: envBool("MEGACOMPACT_MESSAGE_SEPARATION", false),
|
|
363
|
+
cacheStriping: envBool("MEGACOMPACT_CACHE_STRIPING", false),
|
|
360
364
|
tuiWidget: envBool("MEGACOMPACT_TUI_WIDGET", true),
|
|
361
365
|
ragQueryReformulation: envBool("MEGACOMPACT_QUERY_REFORMULATION", false),
|
|
362
366
|
ragTieredRouter: envBool("MEGACOMPACT_TIERED_ROUTER", false),
|
|
@@ -26,6 +26,15 @@ import {
|
|
|
26
26
|
import { safeSendUserMessage } from "./send-safe.js";
|
|
27
27
|
import { maybeSendProviderOutageAdvisory } from "./outage-advisor.js";
|
|
28
28
|
import { vectorStats } from "../../src/vectorStore.js";
|
|
29
|
+
import { defaultEmbedder } from "../../src/embedder.js";
|
|
30
|
+
import {
|
|
31
|
+
storeTopicEmbedding,
|
|
32
|
+
loadTopicEmbedding,
|
|
33
|
+
detectTopicShift,
|
|
34
|
+
} from "./separated-prompt.js";
|
|
35
|
+
import {
|
|
36
|
+
refreshStripeAssignments as writeStripeAssignments,
|
|
37
|
+
} from "../../src/cache-stripe-impl.js";
|
|
29
38
|
|
|
30
39
|
/** Register agent/turn tracking event handlers. */
|
|
31
40
|
export function registerAgentHandlers(
|
|
@@ -167,9 +176,20 @@ export function registerAgentHandlers(
|
|
|
167
176
|
// context-handler.ts:258-287 so both call sites stay in sync.
|
|
168
177
|
const cooldownMs = config.raceGuardStrict ? 30_000 : 10_000;
|
|
169
178
|
const sinceCompact = now - (runtime.rt.lastNativeCompactAt ?? 0);
|
|
179
|
+
// CRITICAL-OVER ESCAPE HATCH: when context is critically over the
|
|
180
|
+
// window (>= 90%), force the durable ctx.compact() even if
|
|
181
|
+
// piCompactWouldNoop says it would no-op ("Already compacted").
|
|
182
|
+
// Without this, the on-disk transcript never truncates and the
|
|
183
|
+
// session stays overflowed on every resume — the "Already
|
|
184
|
+
// compacted" + overflow death-spiral (2026-08-01 incident). The
|
|
185
|
+
// cooldown still applies (we don't spam compact), but the no-op
|
|
186
|
+
// gate is bypassed when critical. pi's ctx.compact() may throw
|
|
187
|
+
// "Already compacted" in the truly-no-op case — wrapped in
|
|
188
|
+
// try/catch so the throw is non-fatal.
|
|
189
|
+
const criticalOver = (runtime.lastCtxPercent ?? 0) >= 90;
|
|
170
190
|
if (sinceCompact < cooldownMs) {
|
|
171
191
|
runtime.diagAgentEndDurableSkipRecent++;
|
|
172
|
-
} else if (!piCompactWouldNoop(ctx)) {
|
|
192
|
+
} else if (criticalOver || !piCompactWouldNoop(ctx)) {
|
|
173
193
|
runtime.debounceUntil = now + 2000;
|
|
174
194
|
runtime.diagAgentEndDurable++;
|
|
175
195
|
runtime.logger.info("agent-end-durable-trigger", {
|
|
@@ -186,6 +206,7 @@ export function registerAgentHandlers(
|
|
|
186
206
|
// would re-check before it lands.
|
|
187
207
|
const stamp = runtime.rt.lastNativeCompactAt;
|
|
188
208
|
const liveSid = runtime.rt.sessionId;
|
|
209
|
+
const liveCritical = criticalOver;
|
|
189
210
|
setTimeout(() => {
|
|
190
211
|
try {
|
|
191
212
|
if (runtime.rt.sessionId !== liveSid) return; // session reset
|
|
@@ -198,16 +219,22 @@ export function registerAgentHandlers(
|
|
|
198
219
|
since2 < cooldownMs
|
|
199
220
|
)
|
|
200
221
|
return;
|
|
201
|
-
|
|
222
|
+
// CRITICAL-OVER ESCAPE HATCH: bypass the no-op gate when
|
|
223
|
+
// critical (captured at handler time) so the durable trim fires.
|
|
224
|
+
if (!liveCritical && piCompactWouldNoop(ctx)) return;
|
|
202
225
|
ctx.compact({
|
|
203
226
|
customInstructions: undefined,
|
|
204
|
-
}); // guardrails-allow PREVENT-PI-004: local ctx.compact() — no network; deferred + re-validated.
|
|
227
|
+
}); // guardrails-allow PREVENT-PI-004: local ctx.compact() — no network; deferred + re-validated. May throw "Already compacted" when the escape hatch forces it — caught below.
|
|
205
228
|
} catch {
|
|
206
|
-
/* non-fatal */
|
|
229
|
+
/* non-fatal: "Already compacted" throws are expected when the critical-over hatch forces a no-op compact */
|
|
207
230
|
}
|
|
208
231
|
}, 500);
|
|
209
232
|
} else {
|
|
210
|
-
|
|
233
|
+
try {
|
|
234
|
+
ctx.compact({ customInstructions: undefined }); // guardrails-allow PREVENT-PI-004: local ctx.compact() — no network; agent settled so no in-flight abort. May throw "Already compacted" when the critical-over escape hatch forces it — caught.
|
|
235
|
+
} catch {
|
|
236
|
+
/* non-fatal */
|
|
237
|
+
}
|
|
211
238
|
}
|
|
212
239
|
didDurableTrim = true;
|
|
213
240
|
}
|
|
@@ -777,5 +804,61 @@ export function registerAgentHandlers(
|
|
|
777
804
|
} catch {
|
|
778
805
|
/* non-fatal: a classifier/retry failure never breaks the agent loop */
|
|
779
806
|
}
|
|
807
|
+
|
|
808
|
+
// P3.5: topic-shift stripe refresh. When MEGACOMPACT_CACHE_STRIPING is ON,
|
|
809
|
+
// embed the current turn, detect topic shift vs. the previous turn, and
|
|
810
|
+
// refresh cache stripe assignments when shifted (or first turn). Best-effort:
|
|
811
|
+
// failures log + continue, never break the agent loop.
|
|
812
|
+
if (config.cacheStriping) {
|
|
813
|
+
try {
|
|
814
|
+
const stateDir = runtime.currentStateDir;
|
|
815
|
+
const sessionId = runtime.rt.sessionId;
|
|
816
|
+
const currentTurn = event.turnIndex;
|
|
817
|
+
const embedder = defaultEmbedder();
|
|
818
|
+
|
|
819
|
+
// (a) Extract text from the assistant's response for topic embedding.
|
|
820
|
+
let textToEmbed = "";
|
|
821
|
+
const msg = event.message;
|
|
822
|
+
if (msg.role === "assistant" && Array.isArray(msg.content)) {
|
|
823
|
+
for (const part of msg.content) {
|
|
824
|
+
if ("text" in part && typeof part.text === "string") {
|
|
825
|
+
textToEmbed += part.text + " ";
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
textToEmbed = textToEmbed.trim() || `turn-${currentTurn}`;
|
|
830
|
+
const embedding = new Float32Array(embedder.embed(textToEmbed));
|
|
831
|
+
storeTopicEmbedding(stateDir, sessionId, currentTurn, embedding);
|
|
832
|
+
|
|
833
|
+
// (b) Load previous turn's embedding and detect topic shift.
|
|
834
|
+
const prevEmb = currentTurn > 0
|
|
835
|
+
? loadTopicEmbedding(stateDir, sessionId, currentTurn - 1)
|
|
836
|
+
: null;
|
|
837
|
+
const shifted = currentTurn === 0 || detectTopicShift(embedding, prevEmb);
|
|
838
|
+
|
|
839
|
+
// (c) If shifted (or first turn), refresh stripe assignments (WRITE path).
|
|
840
|
+
let chunkCount = 0;
|
|
841
|
+
if (shifted) {
|
|
842
|
+
chunkCount = writeStripeAssignments(
|
|
843
|
+
stateDir,
|
|
844
|
+
undefined,
|
|
845
|
+
embedder,
|
|
846
|
+
(detail) => runtime.logger.info("stripe_refresh_detail", {
|
|
847
|
+
detail,
|
|
848
|
+
turnIndex: currentTurn,
|
|
849
|
+
}),
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// (d) Log the refresh outcome.
|
|
854
|
+
runtime.logger.info("stripe_refresh", {
|
|
855
|
+
turnIndex: currentTurn,
|
|
856
|
+
shifted,
|
|
857
|
+
chunkCount,
|
|
858
|
+
});
|
|
859
|
+
} catch {
|
|
860
|
+
/* non-fatal: stripe refresh never breaks the agent loop */
|
|
861
|
+
}
|
|
862
|
+
}
|
|
780
863
|
});
|
|
781
864
|
}
|