pi-mega-compact 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/dist/extensions/dashboard-server/api-contracts/endpoints/registry.js +6 -0
  2. package/dist/extensions/dashboard-server/routes-cache.js +17 -5
  3. package/dist/extensions/dashboard-server/routes-game.js +60 -0
  4. package/dist/extensions/dashboard-server/routes.js +1 -1
  5. package/dist/extensions/dashboard-server/server.js +3 -1
  6. package/dist/extensions/mega-runtime/capture-model.js +15 -2
  7. package/dist/extensions/mega-runtime/runtime-snapshot.js +26 -0
  8. package/dist/extensions/mega-runtime/runtime.js +1 -0
  9. package/extensions/dashboard-client/dist/assets/AreaChart-rJdaOUlj.js +2 -0
  10. package/extensions/dashboard-client/dist/assets/AreaChart-rJdaOUlj.js.map +1 -0
  11. package/extensions/dashboard-client/dist/assets/BarChart-DOfc8me9.js +2 -0
  12. package/extensions/dashboard-client/dist/assets/BarChart-DOfc8me9.js.map +1 -0
  13. package/extensions/dashboard-client/dist/assets/{CacheTab-CDyZqMO-.js → CacheTab-EhqkEglo.js} +2 -2
  14. package/extensions/dashboard-client/dist/assets/{CacheTab-CDyZqMO-.js.map → CacheTab-EhqkEglo.js.map} +1 -1
  15. package/extensions/dashboard-client/dist/assets/{EventsTab-ByPYMJVG.js → EventsTab-BUACwWXf.js} +2 -2
  16. package/extensions/dashboard-client/dist/assets/{EventsTab-ByPYMJVG.js.map → EventsTab-BUACwWXf.js.map} +1 -1
  17. package/extensions/dashboard-client/dist/assets/{HealthTab-CeHQR0py.js → HealthTab-CDEYVRBb.js} +2 -2
  18. package/extensions/dashboard-client/dist/assets/{HealthTab-CeHQR0py.js.map → HealthTab-CDEYVRBb.js.map} +1 -1
  19. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-ceTD_c1A.js → MaintenanceTab-DtanIeCQ.js} +2 -2
  20. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-ceTD_c1A.js.map → MaintenanceTab-DtanIeCQ.js.map} +1 -1
  21. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-CRUdVR7u.js → MemoryMapTab-BGEhL7OG.js} +2 -2
  22. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-CRUdVR7u.js.map → MemoryMapTab-BGEhL7OG.js.map} +1 -1
  23. package/extensions/dashboard-client/dist/assets/MetricsTab-Cktozn4s.js +2 -0
  24. package/extensions/dashboard-client/dist/assets/MetricsTab-Cktozn4s.js.map +1 -0
  25. package/extensions/dashboard-client/dist/assets/OverviewTab-08v6Ayuf.js +16 -0
  26. package/extensions/dashboard-client/dist/assets/OverviewTab-08v6Ayuf.js.map +1 -0
  27. package/extensions/dashboard-client/dist/assets/{ReposTab-DUrP7Efa.js → ReposTab-Bg72Ozdc.js} +2 -2
  28. package/extensions/dashboard-client/dist/assets/{ReposTab-DUrP7Efa.js.map → ReposTab-Bg72Ozdc.js.map} +1 -1
  29. package/extensions/dashboard-client/dist/assets/SessionsTab-BcTK9Lg6.js +2 -0
  30. package/extensions/dashboard-client/dist/assets/{SessionsTab-Cg-IsijD.js.map → SessionsTab-BcTK9Lg6.js.map} +1 -1
  31. package/extensions/dashboard-client/dist/assets/{SetupTab-ZE1daXjN.js → SetupTab-Bt-Ru1xp.js} +2 -2
  32. package/extensions/dashboard-client/dist/assets/{SetupTab-ZE1daXjN.js.map → SetupTab-Bt-Ru1xp.js.map} +1 -1
  33. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BkTgi3Af.js → TimeSavedCard-ic_LjJ79.js} +2 -2
  34. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-BkTgi3Af.js.map → TimeSavedCard-ic_LjJ79.js.map} +1 -1
  35. package/extensions/dashboard-client/dist/assets/{TurnsTab-CZvudHQF.js → TurnsTab-D9AZTMgG.js} +4 -4
  36. package/extensions/dashboard-client/dist/assets/{TurnsTab-CZvudHQF.js.map → TurnsTab-D9AZTMgG.js.map} +1 -1
  37. package/extensions/dashboard-client/dist/assets/WikiTab-hRcJPi_m.js +2 -0
  38. package/extensions/dashboard-client/dist/assets/{WikiTab-BVAnGp8_.js.map → WikiTab-hRcJPi_m.js.map} +1 -1
  39. package/extensions/dashboard-client/dist/assets/{button-CUrzhi2A.js → button-BdAtIbVu.js} +2 -2
  40. package/extensions/dashboard-client/dist/assets/{button-CUrzhi2A.js.map → button-BdAtIbVu.js.map} +1 -1
  41. package/extensions/dashboard-client/dist/assets/{card-BsfojHfp.js → card-BFYc_QrK.js} +2 -2
  42. package/extensions/dashboard-client/dist/assets/{card-BsfojHfp.js.map → card-BFYc_QrK.js.map} +1 -1
  43. package/extensions/dashboard-client/dist/assets/generateCategoricalChart-BwzyqytT.js +70 -0
  44. package/extensions/dashboard-client/dist/assets/generateCategoricalChart-BwzyqytT.js.map +1 -0
  45. package/extensions/dashboard-client/dist/assets/{index-OM3w-l2T.js → index-pmtEr3sb.js} +8 -8
  46. package/extensions/dashboard-client/dist/assets/index-pmtEr3sb.js.map +1 -0
  47. package/extensions/dashboard-client/dist/assets/index-za_gmEmQ.css +1 -0
  48. package/extensions/dashboard-client/dist/assets/{switch-qSz2Un3t.js → switch-DEyGlFwJ.js} +2 -2
  49. package/extensions/dashboard-client/dist/assets/{switch-qSz2Un3t.js.map → switch-DEyGlFwJ.js.map} +1 -1
  50. package/extensions/dashboard-client/dist/assets/{toggle-DyBKcLe-.js → toggle-pDMBQYQj.js} +2 -2
  51. package/extensions/dashboard-client/dist/assets/{toggle-DyBKcLe-.js.map → toggle-pDMBQYQj.js.map} +1 -1
  52. package/extensions/dashboard-client/dist/assets/{useSSE-DqVouqww.js → useSSE-BKaC4dao.js} +2 -2
  53. package/extensions/dashboard-client/dist/assets/{useSSE-DqVouqww.js.map → useSSE-BKaC4dao.js.map} +1 -1
  54. package/extensions/dashboard-client/dist/index.html +2 -2
  55. package/extensions/dashboard-client/package-lock.json +56 -0
  56. package/extensions/dashboard-client/package.json +3 -0
  57. package/extensions/dashboard-client/src/api/client.ts +11 -0
  58. package/extensions/dashboard-client/src/components/SessionContextGauges.tsx +14 -8
  59. package/extensions/dashboard-client/src/components/WidgetDetailModal.tsx +69 -0
  60. package/extensions/dashboard-client/src/components/charts/PerfBarChart.tsx +89 -0
  61. package/extensions/dashboard-client/src/components/charts/PerfLineChart.tsx +108 -0
  62. package/extensions/dashboard-client/src/components/layout/AppShell.tsx +1 -1
  63. package/extensions/dashboard-client/src/components/ui/SortableCard.tsx +75 -0
  64. package/extensions/dashboard-client/src/hooks/useCardPositions.ts +74 -0
  65. package/extensions/dashboard-client/src/styles/base.css +2 -4
  66. package/extensions/dashboard-client/src/styles/session-gauges.css +28 -15
  67. package/extensions/dashboard-client/src/tabs/OverviewTab.tsx +310 -79
  68. package/extensions/dashboard-client/src/types/card.ts +40 -0
  69. package/extensions/dashboard-client/tailwind.config.js +1 -1
  70. package/extensions/dashboard-server/api-contracts/endpoints/registry.ts +10 -0
  71. package/extensions/dashboard-server/api-contracts/endpoints/types.ts +36 -0
  72. package/extensions/dashboard-server/api-contracts/endpoints.ts +3 -0
  73. package/extensions/dashboard-server/api-contracts/index.ts +3 -0
  74. package/extensions/dashboard-server/routes-cache.ts +17 -5
  75. package/extensions/dashboard-server/routes-game.ts +72 -0
  76. package/extensions/dashboard-server/routes.ts +1 -0
  77. package/extensions/dashboard-server/server.ts +2 -0
  78. package/extensions/mega-runtime/capture-model.ts +14 -2
  79. package/extensions/mega-runtime/runtime-snapshot.ts +40 -0
  80. package/extensions/mega-runtime/runtime.ts +1 -0
  81. package/package.json +1 -1
  82. package/extensions/dashboard-client/dist/assets/AreaChart-C68tGr2J.js +0 -70
  83. package/extensions/dashboard-client/dist/assets/AreaChart-C68tGr2J.js.map +0 -1
  84. package/extensions/dashboard-client/dist/assets/MetricsTab-DP7Kmdu9.js +0 -2
  85. package/extensions/dashboard-client/dist/assets/MetricsTab-DP7Kmdu9.js.map +0 -1
  86. package/extensions/dashboard-client/dist/assets/OverviewTab-4CGP0djv.js +0 -2
  87. package/extensions/dashboard-client/dist/assets/OverviewTab-4CGP0djv.js.map +0 -1
  88. package/extensions/dashboard-client/dist/assets/SessionsTab-Cg-IsijD.js +0 -2
  89. package/extensions/dashboard-client/dist/assets/WikiTab-BVAnGp8_.js +0 -2
  90. package/extensions/dashboard-client/dist/assets/index-CUA6EDRF.css +0 -1
  91. package/extensions/dashboard-client/dist/assets/index-OM3w-l2T.js.map +0 -1
@@ -13,7 +13,8 @@ import { createRequire } from "node:module";
13
13
  import type { IncomingMessage, ServerResponse } from "node:http";
14
14
 
15
15
  import type { RouteContext } from "./routes-core.js";
16
- import { computeCacheSavings } from "../../src/pricing.js";
16
+ import { computeCacheSavings, lookupModelInputRate } from "../../src/pricing.js";
17
+ import { costApiConfig, lookupCostApiPricing } from "../../src/costApi.js";
17
18
 
18
19
  // ---------------------------------------------------------------------------
19
20
  // handleProviderCache — "/api/provider-cache"
@@ -118,18 +119,29 @@ export function handleProviderCache(
118
119
  } | null = null;
119
120
  try {
120
121
  const snap = latestModelSnapshot(stateDir); // guardrails-allow PREVENT-PI-004: local SQLite read (loopback dashboard)
121
- if (snap && snap.inputRate > 0) {
122
+ let rate = snap?.inputRate ?? 0;
123
+ let modelName = snap?.modelName ?? snap?.modelId ?? "unknown";
124
+ // Fallback chain: SQLite snapshot → local pricing table → cost API
125
+ if (rate <= 0 && modelName !== "unknown") {
126
+ rate = lookupModelInputRate(modelName) ?? 0;
127
+ }
128
+ if (rate <= 0 && costApiConfig().enabled && modelName !== "unknown") {
129
+ // guardrails-allow PREVENT-PI-004: cost API pricing lookup (user-opted-in)
130
+ const apiPricing = lookupCostApiPricing(modelName);
131
+ rate = apiPricing?.inputRate ?? 0;
132
+ }
133
+ if (rate > 0) {
122
134
  const s = computeCacheSavings(
123
135
  lifetime.totalCacheRead,
124
136
  lifetime.totalCacheWrite,
125
- snap.inputRate,
137
+ rate,
126
138
  );
127
139
  savings = {
128
140
  cacheReadSaved: s.cacheReadSaved,
129
141
  cacheWriteCost: s.cacheWriteCost,
130
142
  netSaved: s.netSaved,
131
- model: snap.modelName ?? snap.modelId ?? "unknown",
132
- inputRate: snap.inputRate,
143
+ model: modelName,
144
+ inputRate: rate,
133
145
  };
134
146
  }
135
147
  } catch {
@@ -8,6 +8,7 @@ import type { IncomingMessage, ServerResponse } from "node:http";
8
8
 
9
9
  import type { RouteContext } from "./routes-core.js";
10
10
  import type { GameMetric } from "../../src/game/scoring.js";
11
+ import type { PerfKind } from "../../src/store/sqlite/perf-samples.js";
11
12
 
12
13
  // ---------------------------------------------------------------------------
13
14
  // handleGameState — "/api/game-state" (GET + PUT)
@@ -351,6 +352,77 @@ export function handlePerf(
351
352
  return true;
352
353
  }
353
354
 
355
+ // ---------------------------------------------------------------------------
356
+ // handlePerfSamples — "/api/perf/samples"
357
+ // ---------------------------------------------------------------------------
358
+
359
+ export function handlePerfSamples(
360
+ req: IncomingMessage,
361
+ res: ServerResponse,
362
+ ctx: RouteContext,
363
+ ): boolean {
364
+ if (!req.url?.startsWith("/api/perf/samples")) return false;
365
+
366
+ const { stateDir } = ctx;
367
+
368
+ // /api/perf/samples — dashboard Perf tab chart drill-down. GET returns raw
369
+ // perf_samples for a single kind (?kind=<k>&minutes=<n>) ascending by ts so
370
+ // the client can render a time-series for any perf metric (turn_latency_ms,
371
+ // tps, disk_write_ms, cache_hit_pct, db_recompute_ms, etc.). `kind` is
372
+ // required and validated against the PERF_KINDS allow-list (400 on unknown);
373
+ // `minutes` defaults to 60 and is capped at 1440 (24h). Reads perf_samples
374
+ // via the same require()'d sqlite helper as /api/perf — no new SQLite code.
375
+ // Non-GET -> 405. PREVENT-PI-004: loopback.
376
+ const psReq = createRequire(import.meta.url);
377
+ const { readPerfSamples, PERF_KINDS } = psReq(
378
+ "../../src/store/sqlite.js",
379
+ ) as typeof import("../../src/store/sqlite.js");
380
+ if (req.method !== "GET") {
381
+ res.writeHead(405, { "Content-Type": "application/json" }); // guardrails-allow PREVENT-PI-004: loopback dashboard response (local)
382
+ res.end(JSON.stringify({ error: "method_not_allowed" }));
383
+ return true;
384
+ }
385
+ try {
386
+ const url = new URL(req.url, "http://x"); // guardrails-allow PREVENT-PI-004: localhost dashboard URL base (loopback-only)
387
+ const kindParam = url.searchParams.get("kind") ?? "";
388
+ if (!(PERF_KINDS as readonly string[]).includes(kindParam)) {
389
+ res.writeHead(400, { "Content-Type": "application/json" }); // guardrails-allow PREVENT-PI-004: loopback dashboard response (local)
390
+ res.end(JSON.stringify({ error: "unknown_kind", kind: kindParam }));
391
+ return true;
392
+ }
393
+ let minutes = Number(url.searchParams.get("minutes") ?? "60");
394
+ if (!Number.isFinite(minutes) || minutes <= 0) minutes = 60;
395
+ minutes = Math.min(minutes, 1440); // cap at 24h
396
+ const sinceTs = Date.now() - minutes * 60_000;
397
+ const kind = kindParam as PerfKind; // validated against PERF_KINDS above
398
+ const rows = readPerfSamples(stateDir, sinceTs, kind); // guardrails-allow PREVENT-PI-004: local SQLite read (loopback dashboard)
399
+ const samples = rows.map((r) => ({
400
+ ts: r.ts,
401
+ value: r.value,
402
+ ...((r.meta != null && typeof r.meta === "object"
403
+ ? { meta: JSON.stringify(r.meta) }
404
+ : r.meta != null
405
+ ? { meta: String(r.meta) }
406
+ : {}) as { meta?: string }),
407
+ }));
408
+ res.writeHead(200, { "Content-Type": "application/json" });
409
+ res.end(
410
+ JSON.stringify({
411
+ samples,
412
+ kind: kindParam,
413
+ minutes,
414
+ updatedAt: Date.now(),
415
+ }),
416
+ );
417
+ } catch (e) {
418
+ res.writeHead(500, { "Content-Type": "application/json" });
419
+ res.end(
420
+ JSON.stringify({ error: "perf_samples_unavailable", detail: String(e) }),
421
+ );
422
+ }
423
+ return true;
424
+ }
425
+
354
426
  // ---------------------------------------------------------------------------
355
427
  // handleAchievements — "/api/achievements"
356
428
  // ---------------------------------------------------------------------------
@@ -13,6 +13,7 @@ export {
13
13
  handleGameState,
14
14
  handleGameScores,
15
15
  handlePerf,
16
+ handlePerfSamples,
16
17
  handleAchievements,
17
18
  } from "./routes-game.js";
18
19
  export { handleEvents, handleSessions } from "./routes-sessions.js";
@@ -33,6 +33,7 @@ import {
33
33
  handleGameState,
34
34
  handleGameScores,
35
35
  handlePerf,
36
+ handlePerfSamples,
36
37
  handleAchievements,
37
38
  handleSessions,
38
39
  handleTopics,
@@ -247,6 +248,7 @@ export async function launchDashboardServer(
247
248
  if (handleEvents(req, res, ctx)) return;
248
249
  if (handleGameState(req, res, ctx)) return;
249
250
  if (handleGameScores(req, res, ctx)) return;
251
+ if (handlePerfSamples(req, res, ctx)) return;
250
252
  if (handlePerf(req, res, ctx)) return;
251
253
  if (handleAchievements(req, res, ctx)) return;
252
254
  if (handleSessions(req, res, ctx)) return;
@@ -11,6 +11,7 @@ import { resolveRepoRoot } from "../mega-config.js";
11
11
  import { recordModelSnapshot, recordRepoModel } from "../../src/store/sqlite.js";
12
12
  import type { ModelSnapshot } from "../../src/store/sqlite.js";
13
13
  import { lookupModelInputRate } from "../../src/pricing.js";
14
+ import { costApiConfig, lookupCostApiPricing } from "../../src/costApi.js";
14
15
 
15
16
  // ---------------------------------------------------------------------- types
16
17
 
@@ -45,13 +46,24 @@ export function captureModelImpl(ctx: CaptureModelContext, ectx: ExtensionContex
45
46
  }
46
47
  const modelId = m.id;
47
48
  const fallbackInput = lookupModelInputRate(modelId);
49
+ let inputRate = m.cost?.input || fallbackInput || 0;
50
+ let outputRate = m.cost?.output ?? 0;
51
+ // Cost API last-resort fallback for models not in the local pricing table
52
+ if (inputRate <= 0 && costApiConfig().enabled) {
53
+ // guardrails-allow PREVENT-PI-004: cost API pricing lookup (user-opted-in)
54
+ const apiPricing = lookupCostApiPricing(modelId);
55
+ if (apiPricing) {
56
+ inputRate = apiPricing.inputRate ?? 0;
57
+ if (outputRate <= 0) outputRate = apiPricing.outputRate ?? 0;
58
+ }
59
+ }
48
60
  const snap: Omit<ModelSnapshot, "capturedAt"> = {
49
61
  provider: m.provider,
50
62
  providerName,
51
63
  modelId,
52
64
  modelName: m.name ?? null,
53
- inputRate: m.cost?.input || fallbackInput || 0,
54
- outputRate: m.cost?.output ?? 0,
65
+ inputRate,
66
+ outputRate,
55
67
  contextWindow: m.contextWindow ?? 0,
56
68
  maxTokens: m.maxTokens ?? 0,
57
69
  reasoning: !!m.reasoning,
@@ -72,6 +72,9 @@ export interface RuntimeSnapshotContext extends RuntimeHelpersContext {
72
72
  lastSnapshotSig: string | null;
73
73
  lastWidgetCtx?: ExtensionContext;
74
74
  lastActivityAt: number;
75
+ /** Timestamp (ms) of the last session heartbeat write. Used by the
76
+ * material-change gate fast path to refresh heartbeats on a cadence. */
77
+ lastHeartbeatAt: number;
75
78
  lastLevel: number;
76
79
  diagCtxFastGate: number;
77
80
  diagLiveTrimFires: number;
@@ -117,6 +120,12 @@ export function snapshotImpl(
117
120
  const sig = materialSigImpl(self);
118
121
  if (ctx && self.widgetData && self.lastSnapshotSig === sig) {
119
122
  self.renderWidget(ctx);
123
+ // Refresh heartbeat on a time cadence even when material state hasn't
124
+ // changed, so the session doesn't appear stale and get pruned by the
125
+ // dashboard's 30-min cutoff. The full snapshot recompute is still gated
126
+ // (expensive SQLite opens + writeFileSync), but a heartbeat row is
127
+ // cheap (single INSERT OR REPLACE on the index DB).
128
+ refreshHeartbeatIfStale(self, ctx, 10_000);
120
129
  return;
121
130
  }
122
131
  const perfT0 = performance.now();
@@ -313,4 +322,35 @@ export function snapshotImpl(
313
322
  /* non-fatal: perf instrumentation never blocks the agent */
314
323
  }
315
324
  self.lastSnapshotSig = sig;
325
+ self.lastHeartbeatAt = Date.now();
326
+ }
327
+
328
+ // ----------------------------------------------------------- heartbeat refresh
329
+
330
+ /** Refresh the session heartbeat if enough time has elapsed since the last
331
+ * write. Called from the material-change gate fast path (where the full
332
+ * snapshot recompute is skipped) so the session stays visible in the
333
+ * dashboard during idle/typing periods. Non-fatal — best-effort like the
334
+ * main heartbeat write. */
335
+ function refreshHeartbeatIfStale(
336
+ self: RuntimeSnapshotContext,
337
+ ctx: ExtensionContext | undefined,
338
+ intervalMs: number,
339
+ ): void {
340
+ const now = Date.now();
341
+ if (now - self.lastHeartbeatAt < intervalMs) return;
342
+ try {
343
+ const repo =
344
+ resolveRepoRoot(ctx?.cwd ?? self.currentStateDir) ?? self.currentStateDir;
345
+ recordSessionHeartbeat(
346
+ process.pid,
347
+ self.rt.sessionId,
348
+ repo,
349
+ self.currentStateDir,
350
+ self.lastCtxWindow || 0,
351
+ );
352
+ self.lastHeartbeatAt = now;
353
+ } catch {
354
+ /* non-fatal: heartbeat refresh must never block the snapshot path */
355
+ }
316
356
  }
@@ -209,6 +209,7 @@ export class MegaRuntime {
209
209
  // (already-registered) widget factory is refreshed. Kills the per-event
210
210
  // main-thread block during typing/idle streaming with no material change.
211
211
  lastSnapshotSig: string | null = null;
212
+ lastHeartbeatAt: number = 0;
212
213
  // v0.8.5: bumped whenever the cached game-state memo is evicted (bumpGameState
213
214
  // for in-process /mega-game writes, the fs.watch callback for cross-process
214
215
  // dashboard-server writes, and bindRepo on repo switch) so the snapshot gate
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-mega-compact",
3
- "version": "0.14.0",
3
+ "version": "0.15.0",
4
4
  "description": "Layered, local, vector-backed context compressor for pi — supersede/collapse/cluster compaction with deduped inline recall.",
5
5
  "type": "module",
6
6
  "license": "BSD-3-Clause",