pi-mega-compact 0.8.12 → 0.8.15
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/README.md +44 -21
- package/dist/extensions/mega-cache-replay.test.js +4 -2
- package/dist/extensions/mega-compact-s38.test.js +317 -0
- package/dist/extensions/mega-compact.js +14 -0
- package/dist/extensions/mega-compact.test.js +37 -8
- package/dist/extensions/mega-config.js +5 -0
- package/dist/extensions/mega-events/agent-handlers.js +184 -4
- package/dist/extensions/mega-events/context-handler.js +35 -2
- package/dist/extensions/mega-events/error-classifier.js +118 -0
- package/dist/extensions/mega-events.js +1 -0
- package/dist/extensions/mega-runtime/state.js +16 -0
- package/dist/extensions/mega-teamrun.test.js +14 -1
- package/extensions/dashboard-client/dist/assets/AchievementTiles-BH26e95w.js +2 -0
- package/extensions/dashboard-client/dist/assets/AchievementTiles-BH26e95w.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/AchievementsTab-B94wvRV_.js +2 -0
- package/extensions/dashboard-client/dist/assets/AchievementsTab-B94wvRV_.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/CacheTab-DPmw3NHR.js +2 -0
- package/extensions/dashboard-client/dist/assets/CacheTab-DPmw3NHR.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/ConfigTab-C3g3smRL.js +2 -0
- package/extensions/dashboard-client/dist/assets/ConfigTab-C3g3smRL.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/EventsTab-B5PicfFw.js +2 -0
- package/extensions/dashboard-client/dist/assets/EventsTab-B5PicfFw.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/GameTab-CsVwp78a.js +2 -0
- package/extensions/dashboard-client/dist/assets/GameTab-CsVwp78a.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/MetricsTab-Cf72UewX.js +2 -0
- package/extensions/dashboard-client/dist/assets/MetricsTab-Cf72UewX.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/OverviewTab-Dh6aG3gy.js +2 -0
- package/extensions/dashboard-client/dist/assets/OverviewTab-Dh6aG3gy.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/ReposTab-HXf2dorl.js +2 -0
- package/extensions/dashboard-client/dist/assets/ReposTab-HXf2dorl.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/TimeSavedCard-kUnoospE.js +2 -0
- package/extensions/dashboard-client/dist/assets/TimeSavedCard-kUnoospE.js.map +1 -0
- package/extensions/dashboard-client/dist/assets/index-Cfxniu2R.css +1 -0
- package/extensions/dashboard-client/dist/assets/{index-XAIN8km6.js → index-DMntLu-8.js} +11 -10
- package/extensions/dashboard-client/dist/assets/index-DMntLu-8.js.map +1 -0
- package/extensions/dashboard-client/dist/index.html +2 -2
- package/extensions/dashboard-client/src/App.tsx +18 -4
- package/extensions/dashboard-client/src/components/AchievementTiles.tsx +105 -0
- package/extensions/dashboard-client/src/components/ActiveReposTable.tsx +116 -0
- package/extensions/dashboard-client/src/components/CacheHitsCard.tsx +73 -0
- package/extensions/dashboard-client/src/components/CacheStatusPerModel.tsx +78 -35
- package/extensions/dashboard-client/src/components/ConfigSummaryCard.tsx +101 -0
- package/extensions/dashboard-client/src/components/ContextGauge.tsx +11 -15
- package/extensions/dashboard-client/src/components/DataSafetyCard.tsx +42 -33
- package/extensions/dashboard-client/src/components/EventCategoryFilter.tsx +46 -9
- package/extensions/dashboard-client/src/components/LegendCard.tsx +49 -0
- package/extensions/dashboard-client/src/components/ModelBadge.tsx +42 -11
- package/extensions/dashboard-client/src/components/PerfCards.tsx +91 -0
- package/extensions/dashboard-client/src/components/RepoAllSessionsCard.tsx +102 -0
- package/extensions/dashboard-client/src/components/RepoDetailModal.tsx +9 -2
- package/extensions/dashboard-client/src/components/RepoTable.tsx +30 -22
- package/extensions/dashboard-client/src/components/SavingsByModelTable.tsx +226 -0
- package/extensions/dashboard-client/src/components/SessionInfo.tsx +25 -50
- package/extensions/dashboard-client/src/components/SummaryTiles.tsx +20 -11
- package/extensions/dashboard-client/src/components/TimeSavedCard.tsx +61 -0
- package/extensions/dashboard-client/src/components/TriggerStatus.tsx +31 -19
- package/extensions/dashboard-client/src/components/VectorStoreCard.tsx +134 -0
- package/extensions/dashboard-client/src/main.tsx +5 -0
- package/extensions/dashboard-client/src/styles/base.css +77 -9
- package/extensions/dashboard-client/src/styles/config.css +149 -0
- package/extensions/dashboard-client/src/styles/game-achievements.css +265 -0
- package/extensions/dashboard-client/src/styles/metrics-extra.css +69 -0
- package/extensions/dashboard-client/src/styles/overview-extra.css +170 -0
- package/extensions/dashboard-client/src/styles/repos-extra.css +99 -0
- package/extensions/dashboard-client/src/styles/repos-metrics.css +14 -23
- package/extensions/dashboard-client/src/tabs/AchievementsTab.tsx +55 -0
- package/extensions/dashboard-client/src/tabs/CacheTab.tsx +50 -0
- package/extensions/dashboard-client/src/tabs/ConfigTab.tsx +240 -5
- package/extensions/dashboard-client/src/tabs/EventsTab.tsx +55 -3
- package/extensions/dashboard-client/src/tabs/GameTab.tsx +287 -0
- package/extensions/dashboard-client/src/tabs/MetricsTab.tsx +7 -1
- package/extensions/dashboard-client/src/tabs/OverviewTab.tsx +103 -17
- package/extensions/dashboard-client/src/tabs/ReposTab.tsx +57 -22
- package/extensions/dashboard-client/src/utils/format.ts +85 -0
- package/extensions/dashboard-client/src/utils/types.ts +27 -0
- package/extensions/mega-cache-replay.test.ts +3 -3
- package/extensions/mega-compact-s38.test.ts +330 -0
- package/extensions/mega-compact.test.ts +41 -13
- package/extensions/mega-compact.ts +14 -0
- package/extensions/mega-config.ts +24 -0
- package/extensions/mega-dashboard.ts +7 -0
- package/extensions/mega-events/agent-handlers.ts +179 -5
- package/extensions/mega-events/context-handler.ts +31 -2
- package/extensions/mega-events/error-classifier.ts +109 -0
- package/extensions/mega-events.ts +1 -0
- package/extensions/mega-runtime/helpers.ts +5 -0
- package/extensions/mega-runtime/state.ts +17 -1
- package/extensions/mega-teamrun.test.ts +13 -1
- package/package.json +4 -1
- package/dist/extensions/dashboard-server/helpers.js +0 -37
- package/dist/extensions/dashboard-server/html/all-repos-tab.js +0 -26
- package/dist/extensions/dashboard-server/html/body-open.js +0 -23
- package/dist/extensions/dashboard-server/html/current-repo-tab.js +0 -130
- package/dist/extensions/dashboard-server/html/head-open.js +0 -16
- package/dist/extensions/dashboard-server/html/high-score-tab.js +0 -25
- package/dist/extensions/dashboard-server/html/repo-detail-modal.js +0 -26
- package/dist/extensions/dashboard-server/html/script.js +0 -259
- package/dist/extensions/dashboard-server/html/styles.js +0 -103
- package/dist/extensions/dashboard-server/html/summary-tab.js +0 -19
- package/dist/extensions/dashboard-server/html-template.js +0 -41
- package/dist/src/store/sqlite/connection.js +0 -35
- package/dist/src/store/sqlite/index-store.js +0 -167
- package/dist/src/store/sqlite/memory.js +0 -54
- package/dist/src/store/sqlite/minhash-lsh.js +0 -47
- package/dist/src/store/sqlite/sessions.js +0 -39
- package/dist/src/store/sqlite/transaction.js +0 -19
- package/dist/src/vectorStore/add.js +0 -260
- package/dist/src/vectorStore/dedup.js +0 -52
- package/dist/src/vectorStore/index.js +0 -10
- package/dist/src/vectorStore/queries.js +0 -83
- package/dist/src/vectorStore/search.js +0 -95
- package/dist/src/vectorStore/session.js +0 -19
- package/dist/src/vectorStore/store.js +0 -105
- package/dist/src/vectorStore/types.js +0 -6
- package/dist/src/vectorStore/utils.js +0 -23
- package/extensions/dashboard-client/dist/assets/ConfigTab-RrjXWfvr.js +0 -2
- package/extensions/dashboard-client/dist/assets/ConfigTab-RrjXWfvr.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/EventsTab-BH_FxJ08.js +0 -2
- package/extensions/dashboard-client/dist/assets/EventsTab-BH_FxJ08.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/MetricsTab-C-PoFV4p.js +0 -2
- package/extensions/dashboard-client/dist/assets/MetricsTab-C-PoFV4p.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/OverviewTab-Db-qBqFN.js +0 -2
- package/extensions/dashboard-client/dist/assets/OverviewTab-Db-qBqFN.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/ReposTab-DhCjJvuj.js +0 -2
- package/extensions/dashboard-client/dist/assets/ReposTab-DhCjJvuj.js.map +0 -1
- package/extensions/dashboard-client/dist/assets/index-Buer5yxB.css +0 -1
- package/extensions/dashboard-client/dist/assets/index-XAIN8km6.js.map +0 -1
|
@@ -4,6 +4,7 @@ import { recordScore } from "../../src/store/sqlite.js";
|
|
|
4
4
|
import { evaluateAndUnlockAchievements } from "../../src/store/sqlite/game-achievements.js";
|
|
5
5
|
import { isMegaCache } from "../../src/game/scoring.js";
|
|
6
6
|
import { resolveRepoRoot } from "../mega-config.js";
|
|
7
|
+
import { classifyError } from "./error-classifier.js";
|
|
7
8
|
/** Register agent/turn tracking event handlers. */
|
|
8
9
|
export function registerAgentHandlers(pi, runtime, config) {
|
|
9
10
|
// ---- Agent tracking for real-time widget + status-line updates ---------
|
|
@@ -37,10 +38,31 @@ export function registerAgentHandlers(pi, runtime, config) {
|
|
|
37
38
|
const idle = ctx.isIdle?.() ?? true;
|
|
38
39
|
const queued = ctx.hasPendingMessages?.() ?? false;
|
|
39
40
|
const now = Date.now();
|
|
41
|
+
// S38.5: read LIVE pressure from ctx.getContextUsage() instead of the
|
|
42
|
+
// stale runtime.lastCtxTokens (only updated by the `context` event).
|
|
43
|
+
// agent_end may fire without a preceding context event this turn (e.g.
|
|
44
|
+
// a sub-agent settling), so the cached value can be null/stale and the
|
|
45
|
+
// durable-trim branch would be unreachable. ctx.getContextUsage() is the
|
|
46
|
+
// authoritative live reading (mirrors context-handler.ts:86). Fall back
|
|
47
|
+
// to the cached value only if the ctx omits it.
|
|
48
|
+
const liveUsage = ctx.getContextUsage?.();
|
|
49
|
+
const liveTokens = typeof liveUsage?.tokens === "number"
|
|
50
|
+
? liveUsage.tokens
|
|
51
|
+
: runtime.lastCtxTokens ?? 0;
|
|
52
|
+
// Keep the cache fresh for snapshot()/diag regardless of which source we use.
|
|
53
|
+
if (typeof liveUsage?.tokens === "number") {
|
|
54
|
+
runtime.lastCtxTokens = liveUsage.tokens;
|
|
55
|
+
}
|
|
56
|
+
if (typeof liveUsage?.percent === "number") {
|
|
57
|
+
runtime.lastCtxPercent = liveUsage.percent;
|
|
58
|
+
}
|
|
59
|
+
if (typeof liveUsage?.contextWindow === "number") {
|
|
60
|
+
runtime.lastCtxWindow = liveUsage.contextWindow;
|
|
61
|
+
}
|
|
40
62
|
// DIAG (team-run relief): surface whether the agent is idle + over
|
|
41
63
|
// threshold at agent_end so we can see if a mid-run durable-trim trigger
|
|
42
64
|
// *should* have fired but didn't.
|
|
43
|
-
const overThreshold =
|
|
65
|
+
const overThreshold = liveTokens >= runtime.effectiveThreshold;
|
|
44
66
|
runtime.diagAgentEndIdle++;
|
|
45
67
|
runtime.logger.info("agent-end-idle", {
|
|
46
68
|
sessionId: runtime.rt.sessionId,
|
|
@@ -87,11 +109,20 @@ export function registerAgentHandlers(pi, runtime, config) {
|
|
|
87
109
|
// synchronous `piCompactWouldNoop` branch check misses a native
|
|
88
110
|
// compaction that hasn't appended its entry yet — calling
|
|
89
111
|
// ctx.compact() then races with pi and throws "Already compacted"
|
|
90
|
-
// to the user. The `
|
|
112
|
+
// to the user. The `lastNativeCompactAt` cooldown (updated by the
|
|
91
113
|
// session_compact listener for EVERY compaction, native or
|
|
92
114
|
// extension-supplied) closes that race window.
|
|
115
|
+
//
|
|
116
|
+
// S38.5: strict race guard widens the cooldown 10s -> 30s AND defers
|
|
117
|
+
// ctx.compact() via setTimeout(500) with a re-check, so pi's
|
|
118
|
+
// about-to-run native _checkCompaction can append its `compaction`
|
|
119
|
+
// branch entry first (closes the first-race-in-burst window). Gated
|
|
120
|
+
// by MEGACOMPACT_RACE_GUARD_STRICT (default true); false reverts to
|
|
121
|
+
// the v0.7.4 synchronous 10s guard. Mirrors the legacy path in
|
|
122
|
+
// context-handler.ts:258-287 so both call sites stay in sync.
|
|
123
|
+
const cooldownMs = config.raceGuardStrict ? 30_000 : 10_000;
|
|
93
124
|
const sinceCompact = now - (runtime.rt.lastNativeCompactAt ?? 0);
|
|
94
|
-
if (sinceCompact <
|
|
125
|
+
if (sinceCompact < cooldownMs) {
|
|
95
126
|
runtime.diagAgentEndDurableSkipRecent++;
|
|
96
127
|
}
|
|
97
128
|
else if (!piCompactWouldNoop(ctx)) {
|
|
@@ -103,7 +134,35 @@ export function registerAgentHandlers(pi, runtime, config) {
|
|
|
103
134
|
thresholdTokens: config.thresholdTokens,
|
|
104
135
|
queued,
|
|
105
136
|
});
|
|
106
|
-
|
|
137
|
+
if (config.raceGuardStrict) {
|
|
138
|
+
// Strict: defer ctx.compact() with a re-check so pi's
|
|
139
|
+
// about-to-run native _checkCompaction can append its
|
|
140
|
+
// `compaction` branch entry first. setTimeout(500) — pi's
|
|
141
|
+
// compaction-summary append is async I/O, so queueMicrotask
|
|
142
|
+
// would re-check before it lands.
|
|
143
|
+
const stamp = runtime.rt.lastNativeCompactAt;
|
|
144
|
+
const liveSid = runtime.rt.sessionId;
|
|
145
|
+
setTimeout(() => {
|
|
146
|
+
try {
|
|
147
|
+
if (runtime.rt.sessionId !== liveSid)
|
|
148
|
+
return; // session reset
|
|
149
|
+
const since2 = now - (runtime.rt.lastNativeCompactAt ?? 0);
|
|
150
|
+
if (runtime.rt.lastNativeCompactAt !== stamp && since2 < cooldownMs)
|
|
151
|
+
return;
|
|
152
|
+
if (piCompactWouldNoop(ctx))
|
|
153
|
+
return;
|
|
154
|
+
ctx.compact({
|
|
155
|
+
customInstructions: undefined,
|
|
156
|
+
}); // guardrails-allow PREVENT-PI-004: local ctx.compact() — no network; deferred + re-validated.
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
/* non-fatal */
|
|
160
|
+
}
|
|
161
|
+
}, 500);
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
ctx.compact({ customInstructions: undefined }); // guardrails-allow PREVENT-PI-004: local ctx.compact() — no network; agent settled so no in-flight abort. Race-guarded by lastNativeCompactAt cooldown above (ctx.compact returns void → throw is surfaced by pi as compaction_end; the cooldown prevents the call entirely).
|
|
165
|
+
}
|
|
107
166
|
didDurableTrim = true;
|
|
108
167
|
}
|
|
109
168
|
}
|
|
@@ -141,6 +200,7 @@ export function registerAgentHandlers(pi, runtime, config) {
|
|
|
141
200
|
pi.on("turn_start", async (event, ctx) => {
|
|
142
201
|
runtime.currentTurn = event.turnIndex;
|
|
143
202
|
runtime.rt.lengthStopPending = false; // S28: re-arm defensively each user turn
|
|
203
|
+
runtime.rt.errorRetryCount = 0; // S38: reset error-retry counter each user turn
|
|
144
204
|
runtime.dashboard.event("turn_start", { turnIndex: event.turnIndex });
|
|
145
205
|
runtime.snapshot(ctx);
|
|
146
206
|
});
|
|
@@ -218,5 +278,125 @@ export function registerAgentHandlers(pi, runtime, config) {
|
|
|
218
278
|
runtime.rt.lengthStopPending = true;
|
|
219
279
|
runtime.dashboard.event("length_stop", { turnIndex: event.turnIndex });
|
|
220
280
|
}
|
|
281
|
+
// S38: broader error-retry safety net. S28 only catches stopReason==='length';
|
|
282
|
+
// this catches ALL other error types (provider failure, network timeout, 5xx,
|
|
283
|
+
// 429, auth, compaction-noop) that surface at turn_end. Non-fatal: wrapped in
|
|
284
|
+
// try/catch so a classifier/retry failure never breaks the agent loop.
|
|
285
|
+
// PREVENT-PI-003: retry nudge fires via pi.sendUserMessage (user-role).
|
|
286
|
+
try {
|
|
287
|
+
// (1) S28 owns length — skip the classifier entirely for it.
|
|
288
|
+
const sr = event.message?.stopReason;
|
|
289
|
+
if (sr === 'length') {
|
|
290
|
+
// S28 handles; nothing for S38 to do here.
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
const category = classifyError(event.message);
|
|
294
|
+
if (category === null) {
|
|
295
|
+
// (3) success / normal flow / unknown-but-non-retryable — reset.
|
|
296
|
+
runtime.rt.errorRetryCount = 0;
|
|
297
|
+
runtime.rt.consecutiveErrors = 0; // S38.6: circuit-breaker reset on success
|
|
298
|
+
}
|
|
299
|
+
else if (category === 'compaction-noop') {
|
|
300
|
+
// (4) pi race / manual compact catch — NOT retryable. The compaction
|
|
301
|
+
// already succeeded via pi's native path; retrying would race again
|
|
302
|
+
// (FAIL-2026071701). Log a diagnostic, reset the counter, and surface
|
|
303
|
+
// the original error WITHOUT firing a retry nudge.
|
|
304
|
+
runtime.rt.errorRetryCount = 0;
|
|
305
|
+
runtime.rt.consecutiveErrors = 0; // S38.6: circuit-breaker reset
|
|
306
|
+
runtime.dashboard.event('compaction_noop_diagnostic', {
|
|
307
|
+
turnIndex: event.turnIndex,
|
|
308
|
+
sessionId: runtime.rt.sessionId,
|
|
309
|
+
});
|
|
310
|
+
runtime.logger.info('compaction-noop-diagnostic', {
|
|
311
|
+
sessionId: runtime.rt.sessionId,
|
|
312
|
+
turnIndex: event.turnIndex,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
else {
|
|
316
|
+
// (5) transient or permanent — retry with exponential backoff.
|
|
317
|
+
// S38.7: hard-stop switch — bypass ALL retry logic when set.
|
|
318
|
+
if (config.errorRetryHardStop) {
|
|
319
|
+
runtime.rt.errorRetryCount = 0;
|
|
320
|
+
runtime.dashboard.event('error_retry_disabled', {
|
|
321
|
+
category,
|
|
322
|
+
turnIndex: event.turnIndex,
|
|
323
|
+
reason: 'hard-stop',
|
|
324
|
+
});
|
|
325
|
+
return; // early exit — no retry
|
|
326
|
+
}
|
|
327
|
+
// S38.6: circuit-breaker — stop retrying after too many consecutive errors.
|
|
328
|
+
runtime.rt.consecutiveErrors++;
|
|
329
|
+
if (runtime.rt.consecutiveErrors > config.maxConsecutiveErrors) {
|
|
330
|
+
runtime.dashboard.event('error_retry_circuit_open', {
|
|
331
|
+
consecutive: runtime.rt.consecutiveErrors,
|
|
332
|
+
max: config.maxConsecutiveErrors,
|
|
333
|
+
turnIndex: event.turnIndex,
|
|
334
|
+
});
|
|
335
|
+
runtime.logger.warn('error-retry-circuit-open', {
|
|
336
|
+
sessionId: runtime.rt.sessionId,
|
|
337
|
+
consecutive: runtime.rt.consecutiveErrors,
|
|
338
|
+
max: config.maxConsecutiveErrors,
|
|
339
|
+
});
|
|
340
|
+
return; // early exit — circuit breaker tripped
|
|
341
|
+
}
|
|
342
|
+
const max = category === 'transient'
|
|
343
|
+
? config.autoRetryTransientMax
|
|
344
|
+
: config.autoRetryPermanentMax;
|
|
345
|
+
// max === 0 disables the category entirely (revert to S28-only).
|
|
346
|
+
if (max <= 0) {
|
|
347
|
+
runtime.rt.errorRetryCount = 0;
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
runtime.rt.errorRetryCount++;
|
|
351
|
+
if (runtime.rt.errorRetryCount > max) {
|
|
352
|
+
// Exhausted — surface the error, reset for the next burst.
|
|
353
|
+
runtime.dashboard.event('error_retry_exhausted', {
|
|
354
|
+
category,
|
|
355
|
+
count: runtime.rt.errorRetryCount,
|
|
356
|
+
max,
|
|
357
|
+
turnIndex: event.turnIndex,
|
|
358
|
+
});
|
|
359
|
+
runtime.logger.info('error-retry-exhausted', {
|
|
360
|
+
sessionId: runtime.rt.sessionId,
|
|
361
|
+
category,
|
|
362
|
+
count: runtime.rt.errorRetryCount,
|
|
363
|
+
max,
|
|
364
|
+
});
|
|
365
|
+
runtime.rt.errorRetryCount = 0;
|
|
366
|
+
}
|
|
367
|
+
else {
|
|
368
|
+
// S38: fire the retry nudge. Each error turn fires its own
|
|
369
|
+
// nudge up to `max` — we intentionally do NOT debounce on the
|
|
370
|
+
// backoff window (errorRetryUntil) here, because that would
|
|
371
|
+
// suppress turns 2..max on a fast-erroring provider (the exact
|
|
372
|
+
// scenario this feature targets) and the user could see zero
|
|
373
|
+
// retries followed by error_retry_exhausted. The per-turn cap
|
|
374
|
+
// (errorRetryCount <= max) plus the session circuit breaker
|
|
375
|
+
// (consecutiveErrors > maxConsecutiveErrors) already bound the
|
|
376
|
+
// loop, so a tight turn_end storm cannot busy-loop unbounded.
|
|
377
|
+
// errorRetryUntil + errorRetryBackoffMs() are retained on the
|
|
378
|
+
// runtime for future/optional pacing but are not gating.
|
|
379
|
+
runtime.dashboard.event('error_retry', {
|
|
380
|
+
category,
|
|
381
|
+
count: runtime.rt.errorRetryCount,
|
|
382
|
+
max,
|
|
383
|
+
turnIndex: event.turnIndex,
|
|
384
|
+
});
|
|
385
|
+
runtime.logger.info('error-retry', {
|
|
386
|
+
sessionId: runtime.rt.sessionId,
|
|
387
|
+
category,
|
|
388
|
+
count: runtime.rt.errorRetryCount,
|
|
389
|
+
max,
|
|
390
|
+
});
|
|
391
|
+
// PREVENT-PI-003: user-role sendUserMessage only.
|
|
392
|
+
pi.sendUserMessage('[mega-compact] the last turn ended with an error; please retry.');
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
catch {
|
|
399
|
+
/* non-fatal: a classifier/retry failure never breaks the agent loop */
|
|
400
|
+
}
|
|
221
401
|
});
|
|
222
402
|
}
|
|
@@ -208,10 +208,43 @@ export function registerContextHandler(pi, runtime, config) {
|
|
|
208
208
|
// NATIVE compaction just fired (avoids racing pi and surfacing a spurious
|
|
209
209
|
// "Already compacted" / "Nothing to compact" toast). Uses lastNativeCompactAt
|
|
210
210
|
// (NOT lastCompactAt, which runCompact also stamps for our own checkpoint).
|
|
211
|
+
// S38.5: strict race guard widens the cooldown 10s -> 30s (gated by
|
|
212
|
+
// MEGACOMPACT_RACE_GUARD_STRICT; false reverts to v0.7.4 10s).
|
|
213
|
+
const cooldownMs = config.raceGuardStrict ? 30_000 : 10_000;
|
|
211
214
|
const sinceCompact = Date.now() - (runtime.rt.lastNativeCompactAt ?? 0);
|
|
212
|
-
if (sinceCompact <
|
|
215
|
+
if (sinceCompact < cooldownMs || piCompactWouldNoop(ctx))
|
|
213
216
|
return;
|
|
214
|
-
|
|
217
|
+
// S38.5: defer ctx.compact() with a re-check so pi's about-to-run native
|
|
218
|
+
// _checkCompaction can append its `compaction` branch entry first (closes
|
|
219
|
+
// the first-race-in-burst window). setTimeout(500) — pi's compaction-summary
|
|
220
|
+
// append is async I/O, so queueMicrotask would re-check before it lands.
|
|
221
|
+
// Non-strict (v0.7.4) keeps the synchronous call.
|
|
222
|
+
if (config.raceGuardStrict) {
|
|
223
|
+
const stamp = runtime.rt.lastNativeCompactAt;
|
|
224
|
+
const liveSid = runtime.rt.sessionId;
|
|
225
|
+
setTimeout(() => {
|
|
226
|
+
try {
|
|
227
|
+
if (runtime.rt.sessionId !== liveSid)
|
|
228
|
+
return; // session reset
|
|
229
|
+
const since2 = Date.now() - (runtime.rt.lastNativeCompactAt ?? 0);
|
|
230
|
+
if (runtime.rt.lastNativeCompactAt !== stamp && since2 < cooldownMs)
|
|
231
|
+
return;
|
|
232
|
+
if (piCompactWouldNoop(ctx))
|
|
233
|
+
return;
|
|
234
|
+
ctx.compact({
|
|
235
|
+
customInstructions: undefined,
|
|
236
|
+
}); // guardrails-allow PREVENT-PI-004: local ctx.compact() — no network; deferred + re-validated.
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
/* non-fatal */
|
|
240
|
+
}
|
|
241
|
+
}, 500);
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
ctx.compact({
|
|
245
|
+
customInstructions: undefined,
|
|
246
|
+
}); // race-guarded by lastNativeCompactAt cooldown (ctx.compact returns void → not catchable; the cooldown prevents the call)
|
|
247
|
+
}
|
|
215
248
|
return;
|
|
216
249
|
}
|
|
217
250
|
// S16 LIVE trim: collapse the compacted region to a summary + recent anchor.
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* error-classifier.ts — S38.2 error classification for retry logic.
|
|
3
|
+
*
|
|
4
|
+
* Classifies turn-end error/stop signals into retry categories.
|
|
5
|
+
* Ordering matters: compaction-noop is matched BEFORE generic transient
|
|
6
|
+
* so a pi race / manual compact catch is never misclassified.
|
|
7
|
+
*/
|
|
8
|
+
/** S38.2: classify a turn-end error/stop signal into a retry category.
|
|
9
|
+
*
|
|
10
|
+
* `length` is returned as null — S28 owns the max-output-token length stopReason
|
|
11
|
+
* exclusively (its agent_end nudge path is separate and must not be doubled).
|
|
12
|
+
*
|
|
13
|
+
* @param message the event.message (a pi AgentMessage) or an error string
|
|
14
|
+
* @returns 'transient' | 'permanent' | 'compaction-noop' | null (success/unknown)
|
|
15
|
+
*/
|
|
16
|
+
export function classifyError(message) {
|
|
17
|
+
// Resolve a searchable text blob from a pi AgentMessage or raw string.
|
|
18
|
+
let text = '';
|
|
19
|
+
if (typeof message === 'string') {
|
|
20
|
+
text = message;
|
|
21
|
+
}
|
|
22
|
+
else if (message && typeof message === 'object') {
|
|
23
|
+
const m = message;
|
|
24
|
+
const sr = typeof m.stopReason === 'string' ? m.stopReason : '';
|
|
25
|
+
// S28 guard: length stopReason is handled exclusively by the S28 path.
|
|
26
|
+
if (sr === 'length')
|
|
27
|
+
return null;
|
|
28
|
+
// Success / normal tool flow — not an error, nothing to retry.
|
|
29
|
+
if (sr === 'stop' || sr === 'toolUse' || sr === 'tool_use')
|
|
30
|
+
return null;
|
|
31
|
+
const parts = [];
|
|
32
|
+
if (sr)
|
|
33
|
+
parts.push(sr);
|
|
34
|
+
const c = m.content;
|
|
35
|
+
if (typeof c === 'string')
|
|
36
|
+
parts.push(c);
|
|
37
|
+
else if (Array.isArray(c)) {
|
|
38
|
+
for (const b of c) {
|
|
39
|
+
if (b && typeof b === 'object' && 'text' in b) {
|
|
40
|
+
parts.push(String(b.text ?? ''));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (m.error) {
|
|
45
|
+
// Extract message from error objects for pattern matching.
|
|
46
|
+
const err = m.error;
|
|
47
|
+
if (typeof err === 'string') {
|
|
48
|
+
parts.push(err);
|
|
49
|
+
}
|
|
50
|
+
else if (err && typeof err === 'object') {
|
|
51
|
+
const errObj = err;
|
|
52
|
+
if (typeof errObj.message === 'string') {
|
|
53
|
+
parts.push(errObj.message);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
parts.push(JSON.stringify(err));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// S38: detect mid-response errors where the stream died without a
|
|
61
|
+
// proper stopReason (empty/undefined) — this catches provider failures
|
|
62
|
+
// that cut off the response mid-stream, INCLUDING the case where the
|
|
63
|
+
// provider emitted partial content before dying (a truncated response
|
|
64
|
+
// with no stop reason is a mid-stream death, not a success).
|
|
65
|
+
// A genuine success ALWAYS carries stop/tool_use/toolUse (which
|
|
66
|
+
// short-circuit to null at the top), so any message reaching here with a
|
|
67
|
+
// falsy stopReason is a stream failure → retryable.
|
|
68
|
+
if (!sr) {
|
|
69
|
+
return 'transient';
|
|
70
|
+
}
|
|
71
|
+
text = parts.join(' ');
|
|
72
|
+
}
|
|
73
|
+
if (!text)
|
|
74
|
+
return null;
|
|
75
|
+
const s = text.toLowerCase();
|
|
76
|
+
// --- compaction-noop (ORDER FIRST: pi race / manual compact catch) ---
|
|
77
|
+
// FAIL-2026071701: these are NOT retryable — the compaction already
|
|
78
|
+
// succeeded via pi's native path; retrying would race again.
|
|
79
|
+
if (/already compacted/.test(s))
|
|
80
|
+
return 'compaction-noop';
|
|
81
|
+
if (/compaction failed/.test(s))
|
|
82
|
+
return 'compaction-noop';
|
|
83
|
+
if (/nothing to compact/.test(s))
|
|
84
|
+
return 'compaction-noop';
|
|
85
|
+
if (/auto[\s-]?compaction failed/.test(s))
|
|
86
|
+
return 'compaction-noop';
|
|
87
|
+
// --- transient (retryable) ---
|
|
88
|
+
if (s.includes('error') && !/\b(permanent|invalid request|malformed|bad request|auth|unauthorized|invalid (api )?key|permission)\b/.test(s)) {
|
|
89
|
+
return 'transient'; // generic pi stopReason 'error' / 'aborted'
|
|
90
|
+
}
|
|
91
|
+
if (s.includes('aborted'))
|
|
92
|
+
return 'transient';
|
|
93
|
+
if (/max(imum)? output token/.test(s))
|
|
94
|
+
return 'transient';
|
|
95
|
+
if (/rate[\s.-]?limit|429|too many requests/.test(s))
|
|
96
|
+
return 'transient';
|
|
97
|
+
if (/5\d\d|internal server|bad gateway|service unavailable/.test(s))
|
|
98
|
+
return 'transient';
|
|
99
|
+
if (/network|timeout|connection (lost|refused|reset)|stream (interrupted|closed|ended|failed)|disconnected/.test(s))
|
|
100
|
+
return 'transient';
|
|
101
|
+
// --- permanent (NOT retryable beyond 1) ---
|
|
102
|
+
if (/auth|unauthorized|invalid (api )?key|permission/.test(s))
|
|
103
|
+
return 'permanent';
|
|
104
|
+
if (/invalid request|malformed|bad request/.test(s))
|
|
105
|
+
return 'permanent';
|
|
106
|
+
// Unknown — do not retry (avoid busy-looping on an unclassified signal).
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
/** S38.2: exponential backoff for error-retry nudges (5s,10s,20s,30s,30s cap).
|
|
110
|
+
* count is 1-based (the retry about to fire). */
|
|
111
|
+
export function errorRetryBackoffMs(count) {
|
|
112
|
+
switch (count) {
|
|
113
|
+
case 1: return 5_000;
|
|
114
|
+
case 2: return 10_000;
|
|
115
|
+
case 3: return 20_000;
|
|
116
|
+
default: return 30_000; // cap from the 4th retry onward
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -49,6 +49,9 @@ export class MegaRuntime {
|
|
|
49
49
|
recallInjections: 0,
|
|
50
50
|
cacheHitTokens: 0,
|
|
51
51
|
lengthStopPending: false,
|
|
52
|
+
errorRetryCount: 0,
|
|
53
|
+
errorRetryUntil: 0,
|
|
54
|
+
consecutiveErrors: 0,
|
|
52
55
|
};
|
|
53
56
|
// v0.8.6 cache-stability: the cached live-trim view for the current
|
|
54
57
|
// compaction epoch. Set after a fresh runCompact + computeLiveTrimCut, and
|
|
@@ -486,6 +489,16 @@ export class MegaRuntime {
|
|
|
486
489
|
cacheHit: { sessionSec: sec(this.rt.cacheHitTokens), totalSec: sec(cacheHitsTotalTokens) },
|
|
487
490
|
},
|
|
488
491
|
model,
|
|
492
|
+
// S38.8: error-retry state for the dashboard "retries" tile. The field is
|
|
493
|
+
// declared on DashboardSnapshot (mega-dashboard.ts) and surfaced here so the
|
|
494
|
+
// dashboard can render live retry/circuit-breaker status alongside the event
|
|
495
|
+
// stream (which already carries per-retry events).
|
|
496
|
+
retries: {
|
|
497
|
+
errorRetryCount: this.rt.errorRetryCount,
|
|
498
|
+
consecutiveErrors: this.rt.consecutiveErrors,
|
|
499
|
+
maxConsecutiveErrors: this.config.maxConsecutiveErrors,
|
|
500
|
+
errorRetryHardStop: this.config.errorRetryHardStop,
|
|
501
|
+
},
|
|
489
502
|
diag: {
|
|
490
503
|
ctxFastGate: this.diagCtxFastGate,
|
|
491
504
|
liveTrimFires: this.diagLiveTrimFires,
|
|
@@ -742,6 +755,9 @@ export class MegaRuntime {
|
|
|
742
755
|
recallInjections: 0,
|
|
743
756
|
cacheHitTokens: 0,
|
|
744
757
|
lengthStopPending: false,
|
|
758
|
+
errorRetryCount: 0,
|
|
759
|
+
errorRetryUntil: 0,
|
|
760
|
+
consecutiveErrors: 0,
|
|
745
761
|
};
|
|
746
762
|
this.trimCache = null; // v0.8.6: never replay a stale trim into a new session
|
|
747
763
|
this.statusKey = undefined;
|
|
@@ -38,6 +38,12 @@ function harness() {
|
|
|
38
38
|
process.env.MEGACOMPACT_DEBUG = "true";
|
|
39
39
|
process.env.MEGACOMPACT_THRESHOLD_TOKENS = "50";
|
|
40
40
|
process.env.MEGACOMPACT_FAST_GATE_PCT = "1";
|
|
41
|
+
// Strict race-guard mode double-counts diagAgentEndDurable (sync++ at branch
|
|
42
|
+
// entry + deferred++ in the setTimeout(500) callback = 6, not 3), lands
|
|
43
|
+
// compactCalls after the synchronous assertions, and leaks timers that hang
|
|
44
|
+
// `node --test`. The strict deferred path is covered by the two S38.5 tests
|
|
45
|
+
// in mega-compact.test.ts. Use the synchronous v0.7.4 path here.
|
|
46
|
+
process.env.MEGACOMPACT_RACE_GUARD_STRICT = "false";
|
|
41
47
|
process.env.MEGACOMPACT_ANCHOR_USER_MESSAGES = "1";
|
|
42
48
|
process.env.MEGACOMPACT_DURABLE_TRIM_FLOOR = "0"; // piCompactWouldNoop must not skip
|
|
43
49
|
process.env.MEGACOMPACT_MEMORY_AUTO_REVIEW = "false";
|
|
@@ -138,6 +144,13 @@ test("control: session_before_compact supplies a durable compaction (parent sett
|
|
|
138
144
|
assert.equal(res.compaction.firstKeptEntryId, "e2", "reuses pi's boundary (PREVENT-PI-002)");
|
|
139
145
|
});
|
|
140
146
|
test("cleanup", async () => {
|
|
141
|
-
|
|
147
|
+
// Race closeVectorIndex with a timeout to prevent 40-min hangs.
|
|
148
|
+
try {
|
|
149
|
+
await Promise.race([
|
|
150
|
+
closeVectorIndex(),
|
|
151
|
+
new Promise((r) => setTimeout(r, 3000)),
|
|
152
|
+
]);
|
|
153
|
+
}
|
|
154
|
+
catch { /* ignore */ }
|
|
142
155
|
rmSync(baseTmp, { recursive: true, force: true });
|
|
143
156
|
});
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{r as o,j as n}from"./index-DMntLu-8.js";function x(s){try{return new Date(s*1e3).toLocaleString()}catch{return"—"}}function f({achievements:s}){const i=o.useRef(0),[r,a]=o.useState(null),l=s.reduce((e,t)=>Math.max(e,t.unlocked_at??0),0),c=i.current;o.useEffect(()=>{l>i.current&&(i.current=l)},[l]),o.useEffect(()=>{if(c>0&&l>c){const e=s.filter(t=>t.unlocked_at!=null&&(t.unlocked_at??0)>c);if(e.length>0){const t=e.map(d=>`${d.icon??""} ${d.title}`.trim()).join(", ")+" unlocked!";a(t);const h=setTimeout(()=>a(null),4e3);return()=>clearTimeout(h)}}},[s,l,c]);const u=s.filter(e=>!(e.hidden===1&&e.unlocked_at==null));return n.jsxs("div",{className:"ach-section",children:[r!=null&&n.jsx("div",{className:"ach-toast show",children:r}),n.jsxs("div",{className:"ach-tiles",children:[u.length===0&&n.jsx("span",{className:"repo-none",children:"no achievements yet"}),u.map(e=>{if(e.unlocked_at!=null){const t=c>0&&(e.unlocked_at??0)>c;return n.jsxs("div",{className:`ach-tile unlocked${t?" just-unlocked":""}`,children:[e.icon??""," ",e.title,n.jsxs("span",{className:"ach-detail",children:["unlocked ",x(e.unlocked_at)]})]},e.id)}return n.jsxs("div",{className:"ach-tile locked",children:["??? ",e.title]},e.id)})]})]})}export{f as A};
|
|
2
|
+
//# sourceMappingURL=AchievementTiles-BH26e95w.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AchievementTiles-BH26e95w.js","sources":["../../src/components/AchievementTiles.tsx"],"sourcesContent":["/**\n * dashboard-client/src/components/AchievementTiles.tsx — shared achievement grid.\n *\n * Renders a grid of achievement tiles from fetchAchievements() data.\n * Tile states: unlocked (icon+title+date), visible-locked (??? teaser),\n * hidden+locked (not rendered). Just-unlocked tiles get a pulse animation.\n * Toast notification fires for newly-unlocked achievements.\n *\n * Used by both GameTab (sub-section) and AchievementsTab (standalone).\n */\n\nimport type React from \"react\";\nimport { useRef, useState, useEffect } from \"react\";\nimport type { AchievementRow } from \"@contracts\";\n\nexport interface AchievementTilesProps {\n\t/** Achievement rows from /api/achievements. */\n\tachievements: AchievementRow[];\n}\n\n/** Format unlock timestamp (seconds → locale string). */\nfunction fmtUnlockDate(ts: number): string {\n\ttry {\n\t\treturn new Date(ts * 1000).toLocaleString();\n\t} catch {\n\t\treturn \"\\u2014\";\n\t}\n}\n\nexport function AchievementTiles({\n\tachievements,\n}: AchievementTilesProps): React.ReactElement {\n\tconst lastMaxTsRef = useRef(0);\n\tconst [toastText, setToastText] = useState<string | null>(null);\n\n\tconst maxTs = achievements.reduce(\n\t\t(mx, a) => Math.max(mx, a.unlocked_at ?? 0),\n\t\t0,\n\t);\n\tconst prevMaxTs = lastMaxTsRef.current;\n\n\t/* Update ref after render so next poll sees previous baseline. */\n\tuseEffect(() => {\n\t\tif (maxTs > lastMaxTsRef.current) {\n\t\t\tlastMaxTsRef.current = maxTs;\n\t\t}\n\t}, [maxTs]);\n\n\t/* Toast for newly-unlocked achievements. */\n\tuseEffect(() => {\n\t\tif (prevMaxTs > 0 && maxTs > prevMaxTs) {\n\t\t\tconst newly = achievements.filter(\n\t\t\t\t(a) => a.unlocked_at != null && (a.unlocked_at ?? 0) > prevMaxTs,\n\t\t\t);\n\t\t\tif (newly.length > 0) {\n\t\t\t\tconst text =\n\t\t\t\t\tnewly\n\t\t\t\t\t\t.map((a) => `${a.icon ?? \"\"} ${a.title}`.trim())\n\t\t\t\t\t\t.join(\", \") + \" unlocked!\";\n\t\t\t\tsetToastText(text);\n\t\t\t\tconst t = setTimeout(() => setToastText(null), 4000);\n\t\t\t\treturn (): void => clearTimeout(t);\n\t\t\t}\n\t\t}\n\t}, [achievements, maxTs, prevMaxTs]);\n\n\t/* Visible tiles: skip hidden+locked (hidden === 1 && unlocked_at == null). */\n\tconst tiles = achievements.filter(\n\t\t(a) => !(a.hidden === 1 && a.unlocked_at == null),\n\t);\n\n\treturn (\n\t\t<div className=\"ach-section\">\n\t\t\t{toastText != null && (\n\t\t\t\t<div className=\"ach-toast show\">{toastText}</div>\n\t\t\t)}\n\t\t\t<div className=\"ach-tiles\">\n\t\t\t\t{tiles.length === 0 && (\n\t\t\t\t\t<span className=\"repo-none\">no achievements yet</span>\n\t\t\t\t)}\n\t\t\t\t{tiles.map((a) => {\n\t\t\t\t\tif (a.unlocked_at != null) {\n\t\t\t\t\t\tconst isNew = prevMaxTs > 0 && (a.unlocked_at ?? 0) > prevMaxTs;\n\t\t\t\t\t\treturn (\n\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\tkey={a.id}\n\t\t\t\t\t\t\t\tclassName={`ach-tile unlocked${isNew ? \" just-unlocked\" : \"\"}`}\n\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t{a.icon ?? \"\"} {a.title}\n\t\t\t\t\t\t\t\t<span className=\"ach-detail\">\n\t\t\t\t\t\t\t\t\tunlocked {fmtUnlockDate(a.unlocked_at)}\n\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\treturn (\n\t\t\t\t\t\t<div key={a.id} className=\"ach-tile locked\">\n\t\t\t\t\t\t\t??? {a.title}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t);\n\t\t\t\t})}\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"],"names":["fmtUnlockDate","ts","AchievementTiles","achievements","lastMaxTsRef","useRef","toastText","setToastText","useState","maxTs","mx","a","prevMaxTs","useEffect","newly","text","t","tiles","jsxs","jsx","isNew"],"mappings":"+CAqBA,SAASA,EAAcC,EAAoB,CAC1C,GAAI,CACH,OAAO,IAAI,KAAKA,EAAK,GAAI,EAAE,eAAA,CAC5B,MAAQ,CACP,MAAO,GACR,CACD,CAEO,SAASC,EAAiB,CAChC,aAAAC,CACD,EAA8C,CAC7C,MAAMC,EAAeC,EAAAA,OAAO,CAAC,EACvB,CAACC,EAAWC,CAAY,EAAIC,EAAAA,SAAwB,IAAI,EAExDC,EAAQN,EAAa,OAC1B,CAACO,EAAIC,IAAM,KAAK,IAAID,EAAIC,EAAE,aAAe,CAAC,EAC1C,CAAA,EAEKC,EAAYR,EAAa,QAG/BS,EAAAA,UAAU,IAAM,CACXJ,EAAQL,EAAa,UACxBA,EAAa,QAAUK,EAEzB,EAAG,CAACA,CAAK,CAAC,EAGVI,EAAAA,UAAU,IAAM,CACf,GAAID,EAAY,GAAKH,EAAQG,EAAW,CACvC,MAAME,EAAQX,EAAa,OACzBQ,GAAMA,EAAE,aAAe,OAASA,EAAE,aAAe,GAAKC,CAAA,EAExD,GAAIE,EAAM,OAAS,EAAG,CACrB,MAAMC,EACLD,EACE,IAAKH,GAAM,GAAGA,EAAE,MAAQ,EAAE,IAAIA,EAAE,KAAK,GAAG,KAAA,CAAM,EAC9C,KAAK,IAAI,EAAI,aAChBJ,EAAaQ,CAAI,EACjB,MAAMC,EAAI,WAAW,IAAMT,EAAa,IAAI,EAAG,GAAI,EACnD,MAAO,IAAY,aAAaS,CAAC,CAClC,CACD,CACD,EAAG,CAACb,EAAcM,EAAOG,CAAS,CAAC,EAGnC,MAAMK,EAAQd,EAAa,OACzBQ,GAAM,EAAEA,EAAE,SAAW,GAAKA,EAAE,aAAe,KAAA,EAG7C,OACCO,EAAAA,KAAC,MAAA,CAAI,UAAU,cACb,SAAA,CAAAZ,GAAa,MACba,EAAAA,IAAC,MAAA,CAAI,UAAU,iBAAkB,SAAAb,EAAU,EAE5CY,EAAAA,KAAC,MAAA,CAAI,UAAU,YACb,SAAA,CAAAD,EAAM,SAAW,GACjBE,MAAC,OAAA,CAAK,UAAU,YAAY,SAAA,sBAAmB,EAE/CF,EAAM,IAAKN,GAAM,CACjB,GAAIA,EAAE,aAAe,KAAM,CAC1B,MAAMS,EAAQR,EAAY,IAAMD,EAAE,aAAe,GAAKC,EACtD,OACCM,EAAAA,KAAC,MAAA,CAEA,UAAW,oBAAoBE,EAAQ,iBAAmB,EAAE,GAE3D,SAAA,CAAAT,EAAE,MAAQ,GAAG,IAAEA,EAAE,MAClBO,EAAAA,KAAC,OAAA,CAAK,UAAU,aAAa,SAAA,CAAA,YAClBlB,EAAcW,EAAE,WAAW,CAAA,CAAA,CACtC,CAAA,CAAA,EANKA,EAAE,EAAA,CASV,CACA,OACCO,EAAAA,KAAC,MAAA,CAAe,UAAU,kBAAkB,SAAA,CAAA,OACtCP,EAAE,KAAA,CAAA,EADEA,EAAE,EAEZ,CAEF,CAAC,CAAA,CAAA,CACF,CAAA,EACD,CAEF"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{r as t,g as m,j as s}from"./index-DMntLu-8.js";import{A as v}from"./AchievementTiles-BH26e95w.js";function x(){const[r,o]=t.useState([]),[l,u]=t.useState(!0),[a,n]=t.useState(null),c=t.useCallback(async()=>{try{const e=await m();o(e),n(null)}catch(e){n(e instanceof Error?e:new Error(String(e)))}finally{u(!1)}},[]);return t.useEffect(()=>{let e=!0;const i=async()=>{e&&await c()};i();const h=setInterval(()=>void i(),15e3);return()=>{e=!1,clearInterval(h)}},[c]),l&&r.length===0?s.jsx("div",{className:"tab-stub",children:"Loading achievements…"}):a&&r.length===0?s.jsxs("div",{className:"tab-stub",children:["Error: ",a.message]}):s.jsxs("div",{className:"achievements-tab",children:[s.jsx("h2",{children:"Achievements"}),s.jsx(v,{achievements:r})]})}export{x as default};
|
|
2
|
+
//# sourceMappingURL=AchievementsTab-B94wvRV_.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AchievementsTab-B94wvRV_.js","sources":["../../src/tabs/AchievementsTab.tsx"],"sourcesContent":["/**\n * dashboard-client/src/tabs/AchievementsTab.tsx — Achievements tab (NEW).\n *\n * Renders the shared AchievementTiles component standalone, plus a toast area.\n * Polls /api/achievements every 15s.\n */\n\nimport type React from \"react\";\nimport { useState, useEffect, useCallback } from \"react\";\nimport type { AchievementRow } from \"@contracts\";\nimport { fetchAchievements } from \"../api/client\";\nimport { AchievementTiles } from \"../components/AchievementTiles\";\n\nexport default function AchievementsTab(): React.ReactElement {\n\tconst [achievements, setAchievements] = useState<AchievementRow[]>([]);\n\tconst [loading, setLoading] = useState(true);\n\tconst [error, setError] = useState<Error | null>(null);\n\n\tconst doFetch = useCallback(async (): Promise<void> => {\n\t\ttry {\n\t\t\tconst rows = await fetchAchievements();\n\t\t\tsetAchievements(rows);\n\t\t\tsetError(null);\n\t\t} catch (e) {\n\t\t\tsetError(e instanceof Error ? e : new Error(String(e)));\n\t\t} finally {\n\t\t\tsetLoading(false);\n\t\t}\n\t}, []);\n\n\tuseEffect(() => {\n\t\tlet active = true;\n\t\tconst run = async (): Promise<void> => {\n\t\t\tif (active) await doFetch();\n\t\t};\n\t\tvoid run();\n\t\tconst timer = setInterval(() => void run(), 15000);\n\t\treturn () => {\n\t\t\tactive = false;\n\t\t\tclearInterval(timer);\n\t\t};\n\t}, [doFetch]);\n\n\tif (loading && achievements.length === 0)\n\t\treturn <div className=\"tab-stub\">Loading achievements…</div>;\n\tif (error && achievements.length === 0)\n\t\treturn <div className=\"tab-stub\">Error: {error.message}</div>;\n\n\treturn (\n\t\t<div className=\"achievements-tab\">\n\t\t\t<h2>Achievements</h2>\n\t\t\t<AchievementTiles achievements={achievements} />\n\t\t</div>\n\t);\n}\n"],"names":["AchievementsTab","achievements","setAchievements","useState","loading","setLoading","error","setError","doFetch","useCallback","rows","fetchAchievements","useEffect","active","run","timer","jsx","jsxs","AchievementTiles"],"mappings":"yGAaA,SAAwBA,GAAsC,CAC7D,KAAM,CAACC,EAAcC,CAAe,EAAIC,EAAAA,SAA2B,CAAA,CAAE,EAC/D,CAACC,EAASC,CAAU,EAAIF,EAAAA,SAAS,EAAI,EACrC,CAACG,EAAOC,CAAQ,EAAIJ,EAAAA,SAAuB,IAAI,EAE/CK,EAAUC,EAAAA,YAAY,SAA2B,CACtD,GAAI,CACH,MAAMC,EAAO,MAAMC,EAAA,EACnBT,EAAgBQ,CAAI,EACpBH,EAAS,IAAI,CACd,OAAS,EAAG,CACXA,EAAS,aAAa,MAAQ,EAAI,IAAI,MAAM,OAAO,CAAC,CAAC,CAAC,CACvD,QAAA,CACCF,EAAW,EAAK,CACjB,CACD,EAAG,CAAA,CAAE,EAeL,OAbAO,EAAAA,UAAU,IAAM,CACf,IAAIC,EAAS,GACb,MAAMC,EAAM,SAA2B,CAClCD,SAAcL,EAAA,CACnB,EACKM,EAAA,EACL,MAAMC,EAAQ,YAAY,IAAM,KAAKD,EAAA,EAAO,IAAK,EACjD,MAAO,IAAM,CACZD,EAAS,GACT,cAAcE,CAAK,CACpB,CACD,EAAG,CAACP,CAAO,CAAC,EAERJ,GAAWH,EAAa,SAAW,EAC/Be,EAAAA,IAAC,MAAA,CAAI,UAAU,WAAW,SAAA,wBAAqB,EACnDV,GAASL,EAAa,SAAW,EAC7BgB,EAAAA,KAAC,MAAA,CAAI,UAAU,WAAW,SAAA,CAAA,UAAQX,EAAM,OAAA,EAAQ,EAGvDW,EAAAA,KAAC,MAAA,CAAI,UAAU,mBACd,SAAA,CAAAD,EAAAA,IAAC,MAAG,SAAA,cAAA,CAAY,EAChBA,MAACE,GAAiB,aAAAjB,CAAA,CAA4B,CAAA,EAC/C,CAEF"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{u as n,r,b as d,j as s}from"./index-DMntLu-8.js";import{C as l,T as h}from"./TimeSavedCard-kUnoospE.js";function p(){const{data:a,loading:i,error:c}=n(r.useCallback(()=>d(),[]),{pollInterval:5e3});if(i&&!a)return s.jsx("div",{className:"tab-stub",children:"Loading snapshot…"});if(c&&!a)return s.jsxs("div",{className:"tab-stub",children:["Error: ",c.message]});if(!a)return s.jsx("div",{className:"tab-stub",children:"No snapshot data."});const{cacheHits:e,compacts:o,timeSaved:t}=a;return s.jsx("div",{className:"cache-tab",children:s.jsxs("div",{className:"card-grid overview-card-grid",children:[s.jsx(l,{cacheHitsSession:e.session,cacheHitsTotal:e.total,tokensSavedSession:e.sessionTokensSaved,tokensSavedTotal:e.totalTokensSaved,compactionsSession:o.session,compactionsTotal:o.total}),s.jsx(h,{compactSessionSec:t.compact.sessionSec,compactTotalSec:t.compact.totalSec,cacheHitSessionSec:t.cacheHit.sessionSec,cacheHitTotalSec:t.cacheHit.totalSec})]})})}export{p as default};
|
|
2
|
+
//# sourceMappingURL=CacheTab-DPmw3NHR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CacheTab-DPmw3NHR.js","sources":["../../src/tabs/CacheTab.tsx"],"sourcesContent":["/**\n * dashboard-client/src/tabs/CacheTab.tsx — Cache tab (NEW).\n *\n * Renders CacheHitsCard + TimeSavedCard from /api/snapshot\n * via the useApi hook with 5s polling.\n */\n\nimport type React from \"react\";\nimport { useCallback } from \"react\";\nimport type { SnapshotResponse } from \"@contracts\";\nimport { useApi } from \"../hooks/useApi\";\nimport { fetchSnapshot } from \"../api/client\";\nimport { CacheHitsCard } from \"../components/CacheHitsCard\";\nimport { TimeSavedCard } from \"../components/TimeSavedCard\";\n\nexport default function CacheTab(): React.ReactElement {\n\tconst { data: snapshot, loading, error } = useApi<SnapshotResponse>(\n\t\tuseCallback(() => fetchSnapshot(), []),\n\t\t{ pollInterval: 5000 },\n\t);\n\n\tif (loading && !snapshot)\n\t\treturn <div className=\"tab-stub\">Loading snapshot…</div>;\n\tif (error && !snapshot)\n\t\treturn <div className=\"tab-stub\">Error: {error.message}</div>;\n\tif (!snapshot) return <div className=\"tab-stub\">No snapshot data.</div>;\n\n\tconst { cacheHits, compacts, timeSaved } = snapshot;\n\n\treturn (\n\t\t<div className=\"cache-tab\">\n\t\t\t<div className=\"card-grid overview-card-grid\">\n\t\t\t\t<CacheHitsCard\n\t\t\t\t\tcacheHitsSession={cacheHits.session}\n\t\t\t\t\tcacheHitsTotal={cacheHits.total}\n\t\t\t\t\ttokensSavedSession={cacheHits.sessionTokensSaved}\n\t\t\t\t\ttokensSavedTotal={cacheHits.totalTokensSaved}\n\t\t\t\t\tcompactionsSession={compacts.session}\n\t\t\t\t\tcompactionsTotal={compacts.total}\n\t\t\t\t/>\n\t\t\t\t<TimeSavedCard\n\t\t\t\t\tcompactSessionSec={timeSaved.compact.sessionSec}\n\t\t\t\t\tcompactTotalSec={timeSaved.compact.totalSec}\n\t\t\t\t\tcacheHitSessionSec={timeSaved.cacheHit.sessionSec}\n\t\t\t\t\tcacheHitTotalSec={timeSaved.cacheHit.totalSec}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t</div>\n\t);\n}\n"],"names":["CacheTab","snapshot","loading","error","useApi","useCallback","fetchSnapshot","jsx","jsxs","cacheHits","compacts","timeSaved","CacheHitsCard","TimeSavedCard"],"mappings":"+GAeA,SAAwBA,GAA+B,CACtD,KAAM,CAAE,KAAMC,EAAU,QAAAC,EAAS,MAAAC,GAAUC,EAC1CC,cAAY,IAAMC,EAAA,EAAiB,EAAE,EACrC,CAAE,aAAc,GAAA,CAAK,EAGtB,GAAIJ,GAAW,CAACD,EACf,OAAOM,EAAAA,IAAC,MAAA,CAAI,UAAU,WAAW,SAAA,oBAAiB,EACnD,GAAIJ,GAAS,CAACF,EACb,OAAOO,EAAAA,KAAC,MAAA,CAAI,UAAU,WAAW,SAAA,CAAA,UAAQL,EAAM,OAAA,EAAQ,EACxD,GAAI,CAACF,EAAU,aAAQ,MAAA,CAAI,UAAU,WAAW,SAAA,oBAAiB,EAEjE,KAAM,CAAE,UAAAQ,EAAW,SAAAC,EAAU,UAAAC,CAAA,EAAcV,EAE3C,aACE,MAAA,CAAI,UAAU,YACd,SAAAO,EAAAA,KAAC,MAAA,CAAI,UAAU,+BACd,SAAA,CAAAD,EAAAA,IAACK,EAAA,CACA,iBAAkBH,EAAU,QAC5B,eAAgBA,EAAU,MAC1B,mBAAoBA,EAAU,mBAC9B,iBAAkBA,EAAU,iBAC5B,mBAAoBC,EAAS,QAC7B,iBAAkBA,EAAS,KAAA,CAAA,EAE5BH,EAAAA,IAACM,EAAA,CACA,kBAAmBF,EAAU,QAAQ,WACrC,gBAAiBA,EAAU,QAAQ,SACnC,mBAAoBA,EAAU,SAAS,WACvC,iBAAkBA,EAAU,SAAS,QAAA,CAAA,CACtC,CAAA,CACD,CAAA,CACD,CAEF"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{r as i,p as j,j as e,b as N,c as w}from"./index-DMntLu-8.js";const u=[{id:"transparent",label:"Transparent (accents only)",css:{bg:null,fg:"#c9d1d9",accent:"#3fb950",mega:"#f0883e"},ansi:{bg:null,fg:"39",accent:"32",mega:"33"}},{id:"retro",label:"Retro 8-bit green",css:{bg:"#003300",fg:"#33ff33",accent:"#00ff41",mega:"#39ff14"},ansi:{bg:"40",fg:"92",accent:"92",mega:"1;92"}},{id:"orange-bold",label:"Orange bold",css:{bg:"#1a0f00",fg:"#ff8c00",accent:"#ff6b00",mega:"#ff4500"},ansi:{bg:"48;5;52",fg:"38;5;208",accent:"38;5;208",mega:"1;38;5;202"}},{id:"cyan-neon",label:"Cyan neon",css:{bg:"#001a1a",fg:"#00ffff",accent:"#22d3ee",mega:"#7df9ff"},ansi:{bg:"48;5;23",fg:"38;5;51",accent:"38;5;51",mega:"1;38;5;51"}},{id:"amber-mono",label:"Amber phosphor",css:{bg:"#1a1200",fg:"#ffb000",accent:"#ff8c00",mega:"#ff5500"},ansi:{bg:"48;5;58",fg:"38;5;214",accent:"38;5;214",mega:"1;38;5;208"}},{id:"grayscale",label:"Grayscale minimal",css:{bg:"#161616",fg:"#b0b0b0",accent:"#8b949e",mega:"#e6edf3"},ansi:{bg:"48;5;235",fg:"38;5;249",accent:"38;5;249",mega:"1;38;5;255"}}];u.map(a=>a.id);const c={gameMode:"Turn game mode on/off (themes the widget + dashboard)",theme:"Visual theme (applies instantly)",tuiMode:"TUI widget display density",tier:"Live pressure band — climbs low→mega as context fills the window.",preset:"The env-resolved base compaction preset (low/medium/high/ultra/mega) that set the token threshold.",pressure:"Live pressure = currentTokens / threshold — % of the model context window (threshold fires at the tier's % of window).",threshold:"Compaction threshold = tierPct × model context window — mega-compact trims BELOW pi's native ~80% auto-compact for any model size.",fastGate:"Fast-gate arming floor — the live trim arms once context passes this % of the window."};function y(a,d,l){let o=a.toLocaleString();return d!=null&&l>0&&(o+=` (${Math.round(d*100)}% of ${l.toLocaleString()})`),o}function T(a){return`${Math.round((a||0)*100)}%`}function M(){const[a,d]=i.useState(null),[l,o]=i.useState(null),[m,g]=i.useState(!1);i.useEffect(()=>{let s=!0;const t=async()=>{try{const r=await N();s&&d(r)}catch{}};t();const f=setInterval(()=>void t(),5e3);return()=>{s=!1,clearInterval(f)}},[]),i.useEffect(()=>{let s=!0;const t=async()=>{try{const r=await w();s&&(o(r),r.theme&&(document.documentElement.dataset.theme=r.theme))}catch{}};t();const f=setInterval(()=>void t(),5e3);return()=>{s=!1,clearInterval(f)}},[]);const h=i.useCallback(async s=>{g(!0);try{const t=await j(s);o(t),t.theme&&(document.documentElement.dataset.theme=t.theme)}catch{}g(!1)},[]),p=l?.game_mode_on??!1,b=l?.theme??"transparent",x=l?.tui_display_mode??"full",n=a?.config,v=a?.context.contextWindow??0;return e.jsxs("div",{className:"config-tab",children:[e.jsxs("section",{className:"config-settings",children:[e.jsx("h3",{children:"Settings"}),e.jsx("div",{className:"settings-row",children:e.jsxs("label",{title:c.gameMode,children:[e.jsx("input",{type:"checkbox",checked:p,onChange:s=>h({game_mode_on:s.currentTarget.checked}),disabled:m}),"Game Mode"]})}),e.jsx("div",{className:"settings-row",children:e.jsxs("label",{title:c.theme,children:["Theme",e.jsx("select",{value:b,onChange:s=>h({theme:s.currentTarget.value}),disabled:m,children:u.map(s=>e.jsx("option",{value:s.id,children:s.label},s.id))})]})}),e.jsx("div",{className:"settings-row",children:e.jsxs("label",{title:c.tuiMode,children:["TUI Mode",e.jsxs("select",{value:x,onChange:s=>h({tui_display_mode:s.currentTarget.value}),disabled:m,children:[e.jsx("option",{value:"full",children:"Full"}),e.jsx("option",{value:"minimal",children:"Minimal"})]})]})})]}),e.jsxs("section",{className:"config-display",children:[e.jsx("h3",{children:"Configuration"}),e.jsxs("div",{className:"config-grid",children:[e.jsxs("div",{className:"config-item",children:[e.jsx("span",{className:"config-label",title:c.tier,children:"Tier"}),e.jsx("span",{className:"config-value",children:a?`${a.tier} (live)`:"—"})]}),e.jsxs("div",{className:"config-item",children:[e.jsx("span",{className:"config-label",title:c.preset,children:"Preset"}),e.jsx("span",{className:"config-value",children:a?.presetTier??"—"})]}),e.jsxs("div",{className:"config-item",children:[e.jsx("span",{className:"config-label",title:c.pressure,children:"Pressure"}),e.jsx("span",{className:"config-value",children:a?T(a.pressure):"—"})]}),e.jsxs("div",{className:"config-item",children:[e.jsx("span",{className:"config-label",title:c.threshold,children:"Threshold"}),e.jsx("span",{className:"config-value",children:n?y(n.thresholdTokens,n.tierPct,v):"—"})]}),e.jsxs("div",{className:"config-item",children:[e.jsx("span",{className:"config-label",title:c.fastGate,children:"Fast Gate"}),e.jsx("span",{className:"config-value",children:n?`${n.fastGatePct}%`:"—"})]}),e.jsxs("div",{className:"config-item",children:[e.jsx("span",{className:"config-label",children:"Auto"}),e.jsx("span",{className:"config-value",children:n?n.auto?"enabled":"disabled":"—"})]}),e.jsxs("div",{className:"config-item",children:[e.jsx("span",{className:"config-label",children:"Anchor"}),e.jsx("span",{className:"config-value",children:n?.anchorUserMessages??"—"})]})]})]})]})}export{M as default};
|
|
2
|
+
//# sourceMappingURL=ConfigTab-C3g3smRL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConfigTab-C3g3smRL.js","sources":["../../../../src/config/themes.ts","../../src/tabs/ConfigTab.tsx"],"sourcesContent":["/**\n * themes.ts — SINGLE SOURCE OF TRUTH for game-mode visual-effect themes (S30).\n *\n * Each theme defines BOTH an HTML/CSS palette (dashboard) AND an ANSI accent\n * mapping (TUI widget), since the TUI is ANSI and the dashboard is HTML.\n *\n * Theme scope (QA1 correction): themes restyle the mega-compact statusbar\n * widget + the whole dashboard HTML page. We do NOT theme pi's chrome.\n *\n * `transparent` (default) semantics: no background fill, colored accents only —\n * so game-mode stays visible against any terminal background.\n *\n * PREVENT-PI-004: pure config, no network. Pi-agnostic: no pi runtime types.\n */\n\n/** HTML/CSS palette for the dashboard (hex strings, or `null` for \"no bg\"). */\nexport interface ThemeCss {\n /** Page/cell background hex, or `null` for transparent (no bg fill). */\n bg: string | null;\n /** Default foreground (text) hex. */\n fg: string;\n /** Accent hex (bars, highlights, level numbers). */\n accent: string;\n /** MEGA CACHE highlight hex (fires at cache >= 100%). */\n mega: string;\n}\n\n/** ANSI accent mapping for the TUI widget (SGR parameter strings, no `\\x1b[`).\n * `bg` is `null` for transparent themes (no background fill). */\nexport interface ThemeAnsi {\n /** Background SGR params (e.g. \"48;5;23\"), or `null` for no bg fill. */\n bg: string | null;\n /** Foreground SGR params (e.g. \"39\" for default, \"92\" for bright green). */\n fg: string;\n /** Accent SGR params. */\n accent: string;\n /** MEGA CACHE SGR params (fires at cache >= 100%). */\n mega: string;\n}\n\n/** A complete theme = CSS palette + ANSI mapping + display metadata. */\nexport interface Theme {\n id: string;\n label: string;\n css: ThemeCss;\n ansi: ThemeAnsi;\n}\n\nexport const DEFAULT_THEME = \"transparent\";\n\nexport const THEMES: readonly Theme[] = [\n {\n id: \"transparent\",\n label: \"Transparent (accents only)\",\n css: { bg: null, fg: \"#c9d1d9\", accent: \"#3fb950\", mega: \"#f0883e\" },\n ansi: { bg: null, fg: \"39\", accent: \"32\", mega: \"33\" },\n },\n {\n id: \"retro\",\n label: \"Retro 8-bit green\",\n css: { bg: \"#003300\", fg: \"#33ff33\", accent: \"#00ff41\", mega: \"#39ff14\" },\n ansi: { bg: \"40\", fg: \"92\", accent: \"92\", mega: \"1;92\" },\n },\n {\n id: \"orange-bold\",\n label: \"Orange bold\",\n css: { bg: \"#1a0f00\", fg: \"#ff8c00\", accent: \"#ff6b00\", mega: \"#ff4500\" },\n ansi: { bg: \"48;5;52\", fg: \"38;5;208\", accent: \"38;5;208\", mega: \"1;38;5;202\" },\n },\n {\n id: \"cyan-neon\",\n label: \"Cyan neon\",\n css: { bg: \"#001a1a\", fg: \"#00ffff\", accent: \"#22d3ee\", mega: \"#7df9ff\" },\n ansi: { bg: \"48;5;23\", fg: \"38;5;51\", accent: \"38;5;51\", mega: \"1;38;5;51\" },\n },\n {\n id: \"amber-mono\",\n label: \"Amber phosphor\",\n css: { bg: \"#1a1200\", fg: \"#ffb000\", accent: \"#ff8c00\", mega: \"#ff5500\" },\n ansi: { bg: \"48;5;58\", fg: \"38;5;214\", accent: \"38;5;214\", mega: \"1;38;5;208\" },\n },\n {\n id: \"grayscale\",\n label: \"Grayscale minimal\",\n css: { bg: \"#161616\", fg: \"#b0b0b0\", accent: \"#8b949e\", mega: \"#e6edf3\" },\n ansi: { bg: \"48;5;235\", fg: \"38;5;249\", accent: \"38;5;249\", mega: \"1;38;5;255\" },\n },\n] as const;\n\n/** All valid theme ids, in definition order (used for `theme next` cycling). */\nexport const THEME_IDS: readonly string[] = THEMES.map((t) => t.id);\n\n/** Look up a theme by id; returns `undefined` for an unknown id (caller\n * validates + falls back to DEFAULT_THEME). */\nexport function getTheme(id: string): Theme | undefined {\n return THEMES.find((t) => t.id === id);\n}\n\n/** True if `id` is a known theme id. */\nexport function isValidTheme(id: string): boolean {\n return THEME_IDS.includes(id);\n}\n\n/** Cycle to the next theme id (wraps at the end). Falls back to DEFAULT_THEME\n * if `current` is unknown (defensive — the stored id should always be valid). */\nexport function nextTheme(current: string): string {\n const i = THEME_IDS.indexOf(current);\n if (i < 0) return DEFAULT_THEME;\n return THEME_IDS[(i + 1) % THEME_IDS.length]!;\n}\n\n/** CSS variable declarations for a theme's CSS palette. `bg` is `null` for the\n * transparent theme → `--bg: transparent` (no page fill, accents only). Pure,\n * no pi types. */\nexport function themeCssVars(theme: Theme): string {\n const bg = theme.css.bg ?? \"transparent\";\n return `--bg: ${bg}; --fg: ${theme.css.fg}; --accent: ${theme.css.accent}; --mega: ${theme.css.mega};`;\n}\n\n/** A `:root[data-theme=\"<id>\"]` override block carrying the theme's CSS vars,\n * for client-side instant theme switching. Pure, no pi types. */\nexport function themeDataBlock(theme: Theme): string {\n return `:root[data-theme=\"${theme.id}\"]{ ${themeCssVars(theme)} }`;\n}\n","/**\n * dashboard-client/src/tabs/ConfigTab.tsx — Config tab (C3).\n *\n * Full configuration editor:\n * (1) Game mode toggle (checkbox)\n * (2) Theme selector (dropdown) from src/config/themes.ts THEMES array\n * (3) TUI mode selector (Full/Minimal dropdown)\n * (4) Read-only config display section from snapshot (Tier, Preset, Pressure,\n * Threshold, Fast Gate, Auto, Anchor) with verbatim tooltips from html.ts.\n *\n * Uses fetchGameState() to load current state, putGameState() to save changes.\n * Polls gameState and snapshot every 5s. Applies theme by setting\n * document.documentElement.dataset.theme.\n */\n\nimport type React from \"react\";\nimport { useState, useEffect, useCallback } from \"react\";\nimport type { SnapshotResponse, GameStatePatch } from \"@contracts\";\nimport { fetchSnapshot, fetchGameState, putGameState } from \"../api/client\";\nimport { THEMES } from \"../../../../src/config/themes\";\n\n/** Actual flat game state returned by the server (the server returns a flat\n * { game_mode_on, theme, tui_display_mode } shape, NOT the contract's nested\n * { config: { enabled, theme, displayMode }, activeRitual } shape). */\ninterface FlatGameState {\n\tgame_mode_on: boolean;\n\ttheme: string;\n\ttui_display_mode: \"full\" | \"minimal\";\n}\ntype FlatGameStatePatch = Partial<FlatGameState>;\n\n/** Extended snapshot config — the runtime includes tierPct (not in contract). */\ninterface ConfigExt extends SnapshotResponse[\"config\"] {\n\ttierPct?: number | null;\n}\n\n/* Tooltips copied verbatim from extensions/dashboard-server/html.ts */\nconst TOOLTIPS = {\n\tgameMode: \"Turn game mode on/off (themes the widget + dashboard)\",\n\ttheme: \"Visual theme (applies instantly)\",\n\ttuiMode: \"TUI widget display density\",\n\ttier: \"Live pressure band — climbs low\\u2192mega as context fills the window.\",\n\tpreset:\n\t\t\"The env-resolved base compaction preset (low/medium/high/ultra/mega) that set the token threshold.\",\n\tpressure:\n\t\t\"Live pressure = currentTokens / threshold \\u2014 % of the model context window (threshold fires at the tier's % of window).\",\n\tthreshold:\n\t\t\"Compaction threshold = tierPct \\u00d7 model context window \\u2014 mega-compact trims BELOW pi's native ~80% auto-compact for any model size.\",\n\tfastGate:\n\t\t\"Fast-gate arming floor \\u2014 the live trim arms once context passes this % of the window.\",\n} as const;\n\nfunction formatThreshold(\n\ttokens: number,\n\ttierPct: number | null | undefined,\n\tcw: number,\n): string {\n\tlet txt = tokens.toLocaleString();\n\tif (tierPct != null && cw > 0) {\n\t\ttxt += ` (${Math.round(tierPct * 100)}% of ${cw.toLocaleString()})`;\n\t}\n\treturn txt;\n}\n\nfunction formatPressure(pressure: number): string {\n\t/* Runtime sends 0\\u20131 fraction despite contract saying 0\\u2013100. */\n\treturn `${Math.round((pressure || 0) * 100)}%`;\n}\n\nexport default function ConfigTab(): React.ReactElement {\n\tconst [snapshot, setSnapshot] = useState<SnapshotResponse | null>(null);\n\tconst [gameState, setGameState] = useState<FlatGameState | null>(null);\n\tconst [saving, setSaving] = useState(false);\n\n\t/* Poll snapshot every 5s for config display section. */\n\tuseEffect(() => {\n\t\tlet active = true;\n\t\tconst doFetch = async (): Promise<void> => {\n\t\t\ttry {\n\t\t\t\tconst s = await fetchSnapshot();\n\t\t\t\tif (active) setSnapshot(s);\n\t\t\t} catch {\n\t\t\t\t/* non-fatal */\n\t\t\t}\n\t\t};\n\t\tvoid doFetch();\n\t\tconst timer = setInterval(() => void doFetch(), 5000);\n\t\treturn () => {\n\t\t\tactive = false;\n\t\t\tclearInterval(timer);\n\t\t};\n\t}, []);\n\n\t/* Poll game state every 5s. */\n\tuseEffect(() => {\n\t\tlet active = true;\n\t\tconst doFetch = async (): Promise<void> => {\n\t\t\ttry {\n\t\t\t\tconst gs = (await fetchGameState()) as unknown as FlatGameState;\n\t\t\t\tif (active) {\n\t\t\t\t\tsetGameState(gs);\n\t\t\t\t\tif (gs.theme)\n\t\t\t\t\t\tdocument.documentElement.dataset.theme = gs.theme;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t/* non-fatal */\n\t\t\t}\n\t\t};\n\t\tvoid doFetch();\n\t\tconst timer = setInterval(() => void doFetch(), 5000);\n\t\treturn () => {\n\t\t\tactive = false;\n\t\t\tclearInterval(timer);\n\t\t};\n\t}, []);\n\n\t/* PUT a patch to the server immediately on change. */\n\tconst handlePatch = useCallback(async (patch: FlatGameStatePatch) => {\n\t\tsetSaving(true);\n\t\ttry {\n\t\t\tconst result = (await putGameState(\n\t\t\t\tpatch as unknown as GameStatePatch,\n\t\t\t)) as unknown as FlatGameState;\n\t\t\tsetGameState(result);\n\t\t\tif (result.theme)\n\t\t\t\tdocument.documentElement.dataset.theme = result.theme;\n\t\t} catch {\n\t\t\t/* non-fatal */\n\t\t}\n\t\tsetSaving(false);\n\t}, []);\n\n\tconst gmOn = gameState?.game_mode_on ?? false;\n\tconst themeVal = gameState?.theme ?? \"transparent\";\n\tconst tuiVal = gameState?.tui_display_mode ?? \"full\";\n\n\tconst cfg = snapshot?.config as ConfigExt | undefined;\n\tconst cw = snapshot?.context.contextWindow ?? 0;\n\n\treturn (\n\t\t<div className=\"config-tab\">\n\t\t\t<section className=\"config-settings\">\n\t\t\t\t<h3>Settings</h3>\n\t\t\t\t<div className=\"settings-row\">\n\t\t\t\t\t<label title={TOOLTIPS.gameMode}>\n\t\t\t\t\t\t<input\n\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\tchecked={gmOn}\n\t\t\t\t\t\t\tonChange={(e) =>\n\t\t\t\t\t\t\t\thandlePatch({ game_mode_on: e.currentTarget.checked })\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tdisabled={saving}\n\t\t\t\t\t\t/>\n\t\t\t\t\t\tGame Mode\n\t\t\t\t\t</label>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"settings-row\">\n\t\t\t\t\t<label title={TOOLTIPS.theme}>\n\t\t\t\t\t\tTheme\n\t\t\t\t\t\t<select\n\t\t\t\t\t\t\tvalue={themeVal}\n\t\t\t\t\t\t\tonChange={(e) => handlePatch({ theme: e.currentTarget.value })}\n\t\t\t\t\t\t\tdisabled={saving}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t{THEMES.map((t) => (\n\t\t\t\t\t\t\t\t<option key={t.id} value={t.id}>\n\t\t\t\t\t\t\t\t\t{t.label}\n\t\t\t\t\t\t\t\t</option>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</select>\n\t\t\t\t\t</label>\n\t\t\t\t</div>\n\t\t\t\t<div className=\"settings-row\">\n\t\t\t\t\t<label title={TOOLTIPS.tuiMode}>\n\t\t\t\t\t\tTUI Mode\n\t\t\t\t\t\t<select\n\t\t\t\t\t\t\tvalue={tuiVal}\n\t\t\t\t\t\t\tonChange={(e) =>\n\t\t\t\t\t\t\t\thandlePatch({\n\t\t\t\t\t\t\t\t\ttui_display_mode: e.currentTarget.value as \"full\" | \"minimal\",\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tdisabled={saving}\n\t\t\t\t\t\t>\n\t\t\t\t\t\t\t<option value=\"full\">Full</option>\n\t\t\t\t\t\t\t<option value=\"minimal\">Minimal</option>\n\t\t\t\t\t\t</select>\n\t\t\t\t\t</label>\n\t\t\t\t</div>\n\t\t\t</section>\n\n\t\t\t<section className=\"config-display\">\n\t\t\t\t<h3>Configuration</h3>\n\t\t\t\t<div className=\"config-grid\">\n\t\t\t\t\t<div className=\"config-item\">\n\t\t\t\t\t\t<span className=\"config-label\" title={TOOLTIPS.tier}>\n\t\t\t\t\t\t\tTier\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span className=\"config-value\">\n\t\t\t\t\t\t\t{snapshot ? `${snapshot.tier} (live)` : \"\\u2014\"}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"config-item\">\n\t\t\t\t\t\t<span className=\"config-label\" title={TOOLTIPS.preset}>\n\t\t\t\t\t\t\tPreset\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span className=\"config-value\">\n\t\t\t\t\t\t\t{snapshot?.presetTier ?? \"\\u2014\"}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"config-item\">\n\t\t\t\t\t\t<span className=\"config-label\" title={TOOLTIPS.pressure}>\n\t\t\t\t\t\t\tPressure\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span className=\"config-value\">\n\t\t\t\t\t\t\t{snapshot ? formatPressure(snapshot.pressure) : \"\\u2014\"}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"config-item\">\n\t\t\t\t\t\t<span className=\"config-label\" title={TOOLTIPS.threshold}>\n\t\t\t\t\t\t\tThreshold\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span className=\"config-value\">\n\t\t\t\t\t\t\t{cfg ? formatThreshold(cfg.thresholdTokens, cfg.tierPct, cw) : \"\\u2014\"}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"config-item\">\n\t\t\t\t\t\t<span className=\"config-label\" title={TOOLTIPS.fastGate}>\n\t\t\t\t\t\t\tFast Gate\n\t\t\t\t\t\t</span>\n\t\t\t\t\t\t<span className=\"config-value\">\n\t\t\t\t\t\t\t{cfg ? `${cfg.fastGatePct}%` : \"\\u2014\"}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"config-item\">\n\t\t\t\t\t\t<span className=\"config-label\">Auto</span>\n\t\t\t\t\t\t<span className=\"config-value\">\n\t\t\t\t\t\t\t{cfg ? (cfg.auto ? \"enabled\" : \"disabled\") : \"\\u2014\"}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div className=\"config-item\">\n\t\t\t\t\t\t<span className=\"config-label\">Anchor</span>\n\t\t\t\t\t\t<span className=\"config-value\">\n\t\t\t\t\t\t\t{cfg?.anchorUserMessages ?? \"\\u2014\"}\n\t\t\t\t\t\t</span>\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t</section>\n\t\t</div>\n\t);\n}\n"],"names":["THEMES","t","TOOLTIPS","formatThreshold","tokens","tierPct","cw","txt","formatPressure","pressure","ConfigTab","snapshot","setSnapshot","useState","gameState","setGameState","saving","setSaving","useEffect","active","doFetch","s","fetchSnapshot","timer","gs","fetchGameState","handlePatch","useCallback","patch","result","putGameState","gmOn","themeVal","tuiVal","cfg","jsxs","jsx","e"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{r,j as s}from"./index-DMntLu-8.js";function E(e={}){const{maxEvents:t=500,maxReconnects:c=5}=e,[o,l]=r.useState([]),[i,n]=r.useState("connecting"),[a,u]=r.useState(0),[f,$]=r.useState(null),_=r.useRef(0),m=r.useRef(null),g=r.useCallback(()=>{m.current&&m.current.close(),n("connecting");const d=new EventSource("/api/events");m.current=d,d.onopen=()=>{n("connected"),_.current=0},d.onmessage=x=>{try{const j=JSON.parse(x.data);l(p=>{const h=[...p,j];return h.length>t?h.slice(-t):h}),u(p=>p+1),$(Date.now())}catch{}},d.onerror=()=>{d.close(),_.current<c?(_.current+=1,n("connecting"),setTimeout(g,1e3)):n("disconnected")}},[t,c]);return r.useEffect(()=>(g(),()=>{m.current?.close()}),[g]),{events:o,status:i,eventCount:a,lastEventAt:f}}const S={compact_start:"ev-compact",compact_end:"ev-compact",compact_trigger:"ev-trigger",compact_skip:"ev-trigger",tier_changed:"ev-tier",model_changed:"ev-tier",pressure_lifted:"ev-tier",checkpoint_persisted:"ev-checkpoint",recall_inject:"ev-recall",anchors_updated:"ev-recall",config_updated:"ev-config",config_preset:"ev-config",crew_presence_changed:"ev-crew",crew_turn_changed:"ev-crew",crew_bandit_chosen:"ev-crew",game_ritual_start:"ev-game",game_ritual_stage:"ev-game",game_ritual_end:"ev-game",game_mode_changed:"ev-game",game_render:"ev-game"},y=["all","compact_start","compact_end","recall_inject","checkpoint_persisted"];function k(e){switch(e.type){case"compact_start":return`trigger=${e.trigger} session=${e.sessionId.slice(0,8)}`;case"compact_end":return`freed=${e.tokensFreed} ok=${e.success} cp=${e.checkpointId.slice(0,8)}`;case"compact_trigger":return`pressure=${e.pressure}% threshold=${e.threshold}% armed=${e.armed}`;case"compact_skip":return`reason=${e.reason}`;case"tier_changed":return`${e.from} → ${e.to} ctx=${e.contextPct}%`;case"model_changed":return`${e.providerName}/${e.model}`;case"pressure_lifted":return`${e.beforePct}% → ${e.afterPct}%`;case"checkpoint_persisted":return`cp=${e.checkpointId.slice(0,8)} sessionTok=${e.sessionTokens}`;case"recall_inject":return`q="${e.query.slice(0,40)}" chunks=${e.chunks} tok=${e.tokens}`;case"anchors_updated":return`count=${e.count} pinned=${e.pinned}`;case"config_updated":return`key=${e.key}`;case"config_preset":return`preset=${e.preset}`;case"crew_presence_changed":return`agents=${e.activeAgents} turn=${e.currentTurn}`;case"crew_turn_changed":return`turn=${e.turnIndex} agent=${e.agentName}`;case"crew_bandit_chosen":return`agent=${e.chosenAgent} score=${e.score} regret=${e.regret}`;case"game_ritual_start":case"game_ritual_stage":case"game_ritual_end":case"game_mode_changed":case"game_render":return`stage=${e.type}`}}function N(e){try{return new Date(e).toLocaleTimeString()}catch{return e}}function v({events:e}){const[t,c]=r.useState("all"),[o,l]=r.useState(null),i=r.useMemo(()=>(t==="all"?e:e.filter(a=>a.type===t)).slice(-200).reverse(),[e,t]);return s.jsxs("div",{className:"event-stream",children:[s.jsxs("div",{className:"event-filters",role:"toolbar",children:[y.map(n=>s.jsx("button",{type:"button",className:`filter-chip ${t===n?"active":""}`,onClick:()=>c(n),children:n==="all"?"all":n.replace(/_/g," ")},n)),s.jsxs("span",{className:"event-count",children:[e.length," buffered"]})]}),s.jsxs("ul",{className:"event-list",children:[i.length===0&&s.jsx("li",{className:"event-empty",children:"No events yet."}),i.map((n,a)=>{const u=S[n.type]??"ev-default";return s.jsxs("li",{className:`event-row ${u}`,onClick:()=>l(o===a?null:a),children:[s.jsx("span",{className:"ev-time",children:N(n.ts)}),s.jsx("span",{className:"ev-type",children:n.type}),s.jsx("span",{className:"ev-summary",children:k(n)}),o===a&&s.jsx("pre",{className:"ev-detail",children:JSON.stringify(n,null,2)})]},`${n.ts}-${a}`)})]})]})}const w=["all","compact","recall","config","crew","game"];function C({active:e,onFilter:t}){return s.jsx("div",{className:"event-category-filter",role:"toolbar","aria-label":"Event category filter",children:w.map(c=>s.jsx("button",{type:"button",className:`category-chip${e===c?" active":""}`,onClick:()=>t(c),"aria-pressed":e===c,children:c},c))})}const b=new Set(["compact_start","compact_end","compact_trigger","compact_skip"]),T=new Set(["config_updated","config_preset","tier_changed","model_changed","pressure_lifted","anchors_updated","checkpoint_persisted"]),P=new Set(["crew_presence_changed","crew_turn_changed","crew_bandit_chosen"]),R=new Set(["game_ritual_start","game_ritual_stage","game_ritual_end","game_mode_changed","game_render"]);function A(e,t){if(t==="all")return!0;const c=e.type;return t==="compact"?b.has(c):t==="recall"?c==="recall_inject":t==="config"?T.has(c):t==="crew"?P.has(c):R.has(c)}function O(){const{events:e,status:t,eventCount:c}=E(),[o,l]=r.useState("all"),i=r.useMemo(()=>e.filter(u=>A(u,o)),[e,o]),n=`sse-status sse-${t}`,a=t==="connected"?"connected":t==="connecting"?"connecting…":t==="error"?"error":"disconnected";return s.jsxs("div",{className:"events-tab",children:[s.jsxs("div",{className:"events-header",children:[s.jsxs("span",{className:n,role:"status",children:[s.jsx("span",{className:"sse-dot","aria-hidden":"true"}),a]}),s.jsxs("span",{className:"event-count",children:[c," events received"]})]}),s.jsx(C,{active:o,onFilter:l}),s.jsx(v,{events:i})]})}export{O as default};
|
|
2
|
+
//# sourceMappingURL=EventsTab-B5PicfFw.js.map
|