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.
Files changed (127) hide show
  1. package/README.md +44 -21
  2. package/dist/extensions/mega-cache-replay.test.js +4 -2
  3. package/dist/extensions/mega-compact-s38.test.js +317 -0
  4. package/dist/extensions/mega-compact.js +14 -0
  5. package/dist/extensions/mega-compact.test.js +37 -8
  6. package/dist/extensions/mega-config.js +5 -0
  7. package/dist/extensions/mega-events/agent-handlers.js +184 -4
  8. package/dist/extensions/mega-events/context-handler.js +35 -2
  9. package/dist/extensions/mega-events/error-classifier.js +118 -0
  10. package/dist/extensions/mega-events.js +1 -0
  11. package/dist/extensions/mega-runtime/state.js +16 -0
  12. package/dist/extensions/mega-teamrun.test.js +14 -1
  13. package/extensions/dashboard-client/dist/assets/AchievementTiles-BH26e95w.js +2 -0
  14. package/extensions/dashboard-client/dist/assets/AchievementTiles-BH26e95w.js.map +1 -0
  15. package/extensions/dashboard-client/dist/assets/AchievementsTab-B94wvRV_.js +2 -0
  16. package/extensions/dashboard-client/dist/assets/AchievementsTab-B94wvRV_.js.map +1 -0
  17. package/extensions/dashboard-client/dist/assets/CacheTab-DPmw3NHR.js +2 -0
  18. package/extensions/dashboard-client/dist/assets/CacheTab-DPmw3NHR.js.map +1 -0
  19. package/extensions/dashboard-client/dist/assets/ConfigTab-C3g3smRL.js +2 -0
  20. package/extensions/dashboard-client/dist/assets/ConfigTab-C3g3smRL.js.map +1 -0
  21. package/extensions/dashboard-client/dist/assets/EventsTab-B5PicfFw.js +2 -0
  22. package/extensions/dashboard-client/dist/assets/EventsTab-B5PicfFw.js.map +1 -0
  23. package/extensions/dashboard-client/dist/assets/GameTab-CsVwp78a.js +2 -0
  24. package/extensions/dashboard-client/dist/assets/GameTab-CsVwp78a.js.map +1 -0
  25. package/extensions/dashboard-client/dist/assets/MetricsTab-Cf72UewX.js +2 -0
  26. package/extensions/dashboard-client/dist/assets/MetricsTab-Cf72UewX.js.map +1 -0
  27. package/extensions/dashboard-client/dist/assets/OverviewTab-Dh6aG3gy.js +2 -0
  28. package/extensions/dashboard-client/dist/assets/OverviewTab-Dh6aG3gy.js.map +1 -0
  29. package/extensions/dashboard-client/dist/assets/ReposTab-HXf2dorl.js +2 -0
  30. package/extensions/dashboard-client/dist/assets/ReposTab-HXf2dorl.js.map +1 -0
  31. package/extensions/dashboard-client/dist/assets/TimeSavedCard-kUnoospE.js +2 -0
  32. package/extensions/dashboard-client/dist/assets/TimeSavedCard-kUnoospE.js.map +1 -0
  33. package/extensions/dashboard-client/dist/assets/index-Cfxniu2R.css +1 -0
  34. package/extensions/dashboard-client/dist/assets/{index-XAIN8km6.js → index-DMntLu-8.js} +11 -10
  35. package/extensions/dashboard-client/dist/assets/index-DMntLu-8.js.map +1 -0
  36. package/extensions/dashboard-client/dist/index.html +2 -2
  37. package/extensions/dashboard-client/src/App.tsx +18 -4
  38. package/extensions/dashboard-client/src/components/AchievementTiles.tsx +105 -0
  39. package/extensions/dashboard-client/src/components/ActiveReposTable.tsx +116 -0
  40. package/extensions/dashboard-client/src/components/CacheHitsCard.tsx +73 -0
  41. package/extensions/dashboard-client/src/components/CacheStatusPerModel.tsx +78 -35
  42. package/extensions/dashboard-client/src/components/ConfigSummaryCard.tsx +101 -0
  43. package/extensions/dashboard-client/src/components/ContextGauge.tsx +11 -15
  44. package/extensions/dashboard-client/src/components/DataSafetyCard.tsx +42 -33
  45. package/extensions/dashboard-client/src/components/EventCategoryFilter.tsx +46 -9
  46. package/extensions/dashboard-client/src/components/LegendCard.tsx +49 -0
  47. package/extensions/dashboard-client/src/components/ModelBadge.tsx +42 -11
  48. package/extensions/dashboard-client/src/components/PerfCards.tsx +91 -0
  49. package/extensions/dashboard-client/src/components/RepoAllSessionsCard.tsx +102 -0
  50. package/extensions/dashboard-client/src/components/RepoDetailModal.tsx +9 -2
  51. package/extensions/dashboard-client/src/components/RepoTable.tsx +30 -22
  52. package/extensions/dashboard-client/src/components/SavingsByModelTable.tsx +226 -0
  53. package/extensions/dashboard-client/src/components/SessionInfo.tsx +25 -50
  54. package/extensions/dashboard-client/src/components/SummaryTiles.tsx +20 -11
  55. package/extensions/dashboard-client/src/components/TimeSavedCard.tsx +61 -0
  56. package/extensions/dashboard-client/src/components/TriggerStatus.tsx +31 -19
  57. package/extensions/dashboard-client/src/components/VectorStoreCard.tsx +134 -0
  58. package/extensions/dashboard-client/src/main.tsx +5 -0
  59. package/extensions/dashboard-client/src/styles/base.css +77 -9
  60. package/extensions/dashboard-client/src/styles/config.css +149 -0
  61. package/extensions/dashboard-client/src/styles/game-achievements.css +265 -0
  62. package/extensions/dashboard-client/src/styles/metrics-extra.css +69 -0
  63. package/extensions/dashboard-client/src/styles/overview-extra.css +170 -0
  64. package/extensions/dashboard-client/src/styles/repos-extra.css +99 -0
  65. package/extensions/dashboard-client/src/styles/repos-metrics.css +14 -23
  66. package/extensions/dashboard-client/src/tabs/AchievementsTab.tsx +55 -0
  67. package/extensions/dashboard-client/src/tabs/CacheTab.tsx +50 -0
  68. package/extensions/dashboard-client/src/tabs/ConfigTab.tsx +240 -5
  69. package/extensions/dashboard-client/src/tabs/EventsTab.tsx +55 -3
  70. package/extensions/dashboard-client/src/tabs/GameTab.tsx +287 -0
  71. package/extensions/dashboard-client/src/tabs/MetricsTab.tsx +7 -1
  72. package/extensions/dashboard-client/src/tabs/OverviewTab.tsx +103 -17
  73. package/extensions/dashboard-client/src/tabs/ReposTab.tsx +57 -22
  74. package/extensions/dashboard-client/src/utils/format.ts +85 -0
  75. package/extensions/dashboard-client/src/utils/types.ts +27 -0
  76. package/extensions/mega-cache-replay.test.ts +3 -3
  77. package/extensions/mega-compact-s38.test.ts +330 -0
  78. package/extensions/mega-compact.test.ts +41 -13
  79. package/extensions/mega-compact.ts +14 -0
  80. package/extensions/mega-config.ts +24 -0
  81. package/extensions/mega-dashboard.ts +7 -0
  82. package/extensions/mega-events/agent-handlers.ts +179 -5
  83. package/extensions/mega-events/context-handler.ts +31 -2
  84. package/extensions/mega-events/error-classifier.ts +109 -0
  85. package/extensions/mega-events.ts +1 -0
  86. package/extensions/mega-runtime/helpers.ts +5 -0
  87. package/extensions/mega-runtime/state.ts +17 -1
  88. package/extensions/mega-teamrun.test.ts +13 -1
  89. package/package.json +4 -1
  90. package/dist/extensions/dashboard-server/helpers.js +0 -37
  91. package/dist/extensions/dashboard-server/html/all-repos-tab.js +0 -26
  92. package/dist/extensions/dashboard-server/html/body-open.js +0 -23
  93. package/dist/extensions/dashboard-server/html/current-repo-tab.js +0 -130
  94. package/dist/extensions/dashboard-server/html/head-open.js +0 -16
  95. package/dist/extensions/dashboard-server/html/high-score-tab.js +0 -25
  96. package/dist/extensions/dashboard-server/html/repo-detail-modal.js +0 -26
  97. package/dist/extensions/dashboard-server/html/script.js +0 -259
  98. package/dist/extensions/dashboard-server/html/styles.js +0 -103
  99. package/dist/extensions/dashboard-server/html/summary-tab.js +0 -19
  100. package/dist/extensions/dashboard-server/html-template.js +0 -41
  101. package/dist/src/store/sqlite/connection.js +0 -35
  102. package/dist/src/store/sqlite/index-store.js +0 -167
  103. package/dist/src/store/sqlite/memory.js +0 -54
  104. package/dist/src/store/sqlite/minhash-lsh.js +0 -47
  105. package/dist/src/store/sqlite/sessions.js +0 -39
  106. package/dist/src/store/sqlite/transaction.js +0 -19
  107. package/dist/src/vectorStore/add.js +0 -260
  108. package/dist/src/vectorStore/dedup.js +0 -52
  109. package/dist/src/vectorStore/index.js +0 -10
  110. package/dist/src/vectorStore/queries.js +0 -83
  111. package/dist/src/vectorStore/search.js +0 -95
  112. package/dist/src/vectorStore/session.js +0 -19
  113. package/dist/src/vectorStore/store.js +0 -105
  114. package/dist/src/vectorStore/types.js +0 -6
  115. package/dist/src/vectorStore/utils.js +0 -23
  116. package/extensions/dashboard-client/dist/assets/ConfigTab-RrjXWfvr.js +0 -2
  117. package/extensions/dashboard-client/dist/assets/ConfigTab-RrjXWfvr.js.map +0 -1
  118. package/extensions/dashboard-client/dist/assets/EventsTab-BH_FxJ08.js +0 -2
  119. package/extensions/dashboard-client/dist/assets/EventsTab-BH_FxJ08.js.map +0 -1
  120. package/extensions/dashboard-client/dist/assets/MetricsTab-C-PoFV4p.js +0 -2
  121. package/extensions/dashboard-client/dist/assets/MetricsTab-C-PoFV4p.js.map +0 -1
  122. package/extensions/dashboard-client/dist/assets/OverviewTab-Db-qBqFN.js +0 -2
  123. package/extensions/dashboard-client/dist/assets/OverviewTab-Db-qBqFN.js.map +0 -1
  124. package/extensions/dashboard-client/dist/assets/ReposTab-DhCjJvuj.js +0 -2
  125. package/extensions/dashboard-client/dist/assets/ReposTab-DhCjJvuj.js.map +0 -1
  126. package/extensions/dashboard-client/dist/assets/index-Buer5yxB.css +0 -1
  127. package/extensions/dashboard-client/dist/assets/index-XAIN8km6.js.map +0 -1
@@ -0,0 +1,226 @@
1
+ /**
2
+ * dashboard-client/src/components/SavingsByModelTable.tsx — Savings by Model.
3
+ *
4
+ * 14 columns aggregated client-side from /api/index repos by model.
5
+ * Replicates html.ts renderByModel logic: group by modelName, accumulate
6
+ * tokens/cost/sessions, collapse numeric ranges when repos disagree.
7
+ */
8
+
9
+ import type React from "react";
10
+ import { useMemo } from "react";
11
+ import type { IndexesIndexRow } from "@contracts";
12
+
13
+ export interface SavingsByModelTableProps {
14
+ repos: IndexesIndexRow[];
15
+ }
16
+
17
+ interface ModelGroup {
18
+ model: string;
19
+ provider: string;
20
+ checkpoints: number;
21
+ tokensSaved: number;
22
+ tokensIn: number;
23
+ tokensOut: number;
24
+ sessions: number;
25
+ usd: number;
26
+ inRates: number[];
27
+ outRates: number[];
28
+ ctxWindows: number[];
29
+ maxTokens: number[];
30
+ reasoning: boolean | null;
31
+ lastAt: number;
32
+ }
33
+
34
+ /** Collapse numeric samples: "—" | single | "lo–hi" (matches html.ts). */
35
+ function collapseNum(samples: number[]): string {
36
+ if (!samples.length) return "\u2014";
37
+ const lo = Math.min(...samples);
38
+ const hi = Math.max(...samples);
39
+ return lo === hi
40
+ ? lo.toLocaleString()
41
+ : `${lo.toLocaleString()}\u2013${hi.toLocaleString()}`;
42
+ }
43
+
44
+ /** Collapse rate samples: "—" | "$rate" | "$lo–$hi" (matches html.ts). */
45
+ function collapseRate(samples: number[]): string {
46
+ if (!samples.length) return "\u2014";
47
+ const lo = Math.min(...samples);
48
+ const hi = Math.max(...samples);
49
+ const fmt = (v: number): string => `$${v.toFixed(6)}`;
50
+ return lo === hi ? fmt(lo) : `${fmt(lo)}\u2013${fmt(hi)}`;
51
+ }
52
+
53
+ /** Group repos by model, accumulate totals, sort by tokensSaved desc. */
54
+ function aggregate(repos: IndexesIndexRow[]): ModelGroup[] {
55
+ const map: Record<string, ModelGroup> = {};
56
+ for (const r of repos) {
57
+ const key = (r.modelName && r.modelName.trim()) || "(unknown)";
58
+ if (!map[key]) {
59
+ map[key] = {
60
+ model: key,
61
+ provider: r.providerName ?? r.provider ?? "\u2014",
62
+ checkpoints: 0,
63
+ tokensSaved: 0,
64
+ tokensIn: 0,
65
+ tokensOut: 0,
66
+ sessions: 0,
67
+ usd: 0,
68
+ inRates: [],
69
+ outRates: [],
70
+ ctxWindows: [],
71
+ maxTokens: [],
72
+ reasoning: null,
73
+ lastAt: 0,
74
+ };
75
+ }
76
+ const g = map[key];
77
+ g.checkpoints += r.checkpointCount || 0;
78
+ g.tokensSaved += r.tokensSaved || 0;
79
+ g.tokensIn += r.tokensDropped || 0;
80
+ g.tokensOut += r.tokensKept || 0;
81
+ g.sessions += r.sessions || 0;
82
+ if (r.inputRate) {
83
+ g.usd += (r.tokensSaved || 0) * r.inputRate;
84
+ g.inRates.push(r.inputRate);
85
+ }
86
+ if (r.outputRate) g.outRates.push(r.outputRate);
87
+ if (r.contextWindow) g.ctxWindows.push(r.contextWindow);
88
+ if (r.maxTokens) g.maxTokens.push(r.maxTokens);
89
+ if (r.reasoning != null) g.reasoning = r.reasoning;
90
+ if (r.lastCompactedAt && r.lastCompactedAt > g.lastAt)
91
+ g.lastAt = r.lastCompactedAt;
92
+ }
93
+ return Object.values(map).sort((a, b) => b.tokensSaved - a.tokensSaved);
94
+ }
95
+
96
+ /** Tooltip text copied verbatim from html.ts title attributes. */
97
+ const TOOLTIPS = {
98
+ tokensIn: "Tokens dropped from context by compaction (the input reclaimed)",
99
+ tokensOut:
100
+ "Tokens kept as compacted summaries still in context (the output retained)",
101
+ ctxWindow: "Model context window (max input tokens the model accepts)",
102
+ maxOut: "Model max output tokens per turn",
103
+ reas: "Reasoning-capable model",
104
+ sessions: "Distinct sessions with at least one checkpoint",
105
+ inRate: "USD per input token",
106
+ outRate: "USD per output token",
107
+ } as const;
108
+
109
+ export function SavingsByModelTable({
110
+ repos,
111
+ }: SavingsByModelTableProps): React.ReactElement {
112
+ const groups = useMemo(() => aggregate(repos), [repos]);
113
+
114
+ return (
115
+ <div className="savings-by-model">
116
+ <p className="legend-note">
117
+ How much context &amp; cost mega-compact has reclaimed, grouped by
118
+ the model you were running. Compression ratio reflects
119
+ workload/content, not model quality.
120
+ </p>
121
+ <div className="table-scroll">
122
+ <table className="savings-table">
123
+ <thead>
124
+ <tr>
125
+ <th>Model</th>
126
+ <th>Provider</th>
127
+ <th className="num" title={TOOLTIPS.tokensIn}>
128
+ Tokens In
129
+ </th>
130
+ <th className="num" title={TOOLTIPS.tokensOut}>
131
+ Tokens Out
132
+ </th>
133
+ <th className="num">Freed</th>
134
+ <th className="num" title={TOOLTIPS.ctxWindow}>
135
+ Ctx Window
136
+ </th>
137
+ <th className="num" title={TOOLTIPS.maxOut}>
138
+ Max Out
139
+ </th>
140
+ <th className="num" title={TOOLTIPS.reas}>
141
+ Reas.
142
+ </th>
143
+ <th className="num" title={TOOLTIPS.sessions}>
144
+ Sessions
145
+ </th>
146
+ <th className="num">Checkpoints</th>
147
+ <th className="num" title={TOOLTIPS.inRate}>
148
+ In $/tok
149
+ </th>
150
+ <th className="num" title={TOOLTIPS.outRate}>
151
+ Out $/tok
152
+ </th>
153
+ <th className="num">$ Saved</th>
154
+ <th className="num">Last Used</th>
155
+ </tr>
156
+ </thead>
157
+ <tbody>
158
+ {groups.length === 0 && (
159
+ <tr>
160
+ <td colSpan={14} className="repo-empty">
161
+ No repositories registered yet.
162
+ </td>
163
+ </tr>
164
+ )}
165
+ {groups.map((g) => {
166
+ const freed = (g.tokensIn || 0) - (g.tokensOut || 0);
167
+ const usd =
168
+ g.usd > 0 ? `$${g.usd.toFixed(4)}` : "\u2014";
169
+ const when = g.lastAt
170
+ ? new Date(g.lastAt).toLocaleString()
171
+ : "\u2014";
172
+ const reas =
173
+ g.reasoning == null
174
+ ? "\u2014"
175
+ : g.reasoning
176
+ ? "yes"
177
+ : "no";
178
+ return (
179
+ <tr key={g.model}>
180
+ <td className="repo-model">{g.model}</td>
181
+ <td>{g.provider}</td>
182
+ <td className="num">
183
+ {g.tokensIn.toLocaleString()}
184
+ </td>
185
+ <td className="num">
186
+ {g.tokensOut.toLocaleString()}
187
+ </td>
188
+ <td className="num">
189
+ {freed.toLocaleString()}
190
+ </td>
191
+ <td className="num">
192
+ {collapseNum(g.ctxWindows)}
193
+ </td>
194
+ <td className="num">
195
+ {collapseNum(g.maxTokens)}
196
+ </td>
197
+ <td className="num">{reas}</td>
198
+ <td className="num">
199
+ {g.sessions.toLocaleString()}
200
+ </td>
201
+ <td className="num">
202
+ {g.checkpoints.toLocaleString()}
203
+ </td>
204
+ <td className="num">
205
+ {collapseRate(g.inRates)}
206
+ </td>
207
+ <td className="num">
208
+ {collapseRate(g.outRates)}
209
+ </td>
210
+ <td className="num">{usd}</td>
211
+ <td className="num">{when}</td>
212
+ </tr>
213
+ );
214
+ })}
215
+ </tbody>
216
+ </table>
217
+ </div>
218
+ <p className="legend-note">
219
+ Tokens In = Σ original region tokens dropped by compaction. Tokens
220
+ Out = Σ compacted summary tokens still retained in context. Freed =
221
+ Tokens In − Tokens Out (net context reclaimed). Ctx Window / Max Out
222
+ / Reas. come from the latest captured model snapshot for each repo.
223
+ </p>
224
+ </div>
225
+ );
226
+ }
@@ -1,64 +1,39 @@
1
1
  /**
2
- * dashboard-client/src/components/SessionInfo.tsx — session state + store metrics.
2
+ * dashboard-client/src/components/SessionInfo.tsx — Crew / Agents card.
3
3
  *
4
- * State badge, checkpoint count, tokens saved, dedup hit rate.
4
+ * Shows Active Agents, Current Turn, and Status.
5
5
  */
6
6
 
7
7
  import type React from "react";
8
8
 
9
9
  export interface SessionInfoProps {
10
- /** Session lifecycle state, or null when no session is active. */
11
- state: string | null;
12
- /** Whether a checkpoint has been persisted during this session. */
13
- persistedThisSession: boolean;
14
- /** Total checkpoints across all sessions. */
15
- checkpointCount: number;
16
- /** Total tokens saved through compaction. */
17
- tokensSaved: number;
18
- /** Dedup hit rate (percent, 0–100). */
19
- dedupHitRate: number;
10
+ /** Number of active crew agents. */
11
+ activeAgents: number;
12
+ /** Current turn index in the crew round-robin. */
13
+ currentTurn: number;
20
14
  }
21
15
 
22
- function fmt(n: number): string {
23
- if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
24
- if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;
25
- return String(n);
26
- }
16
+ export function SessionInfo(
17
+ props: SessionInfoProps,
18
+ ): React.ReactElement {
19
+ const { activeAgents, currentTurn } = props;
20
+ const status =
21
+ activeAgents > 0 ? `▶ ${activeAgents} running` : "idle";
27
22
 
28
- export function SessionInfo({
29
- state,
30
- persistedThisSession,
31
- checkpointCount,
32
- tokensSaved,
33
- dedupHitRate,
34
- }: SessionInfoProps): React.ReactElement {
35
- const badge = state ?? "no session";
36
23
  return (
37
- <div className="card session-info">
38
- <h3>Session</h3>
39
- <p>
40
- <span
41
- className={`state-badge state-${(state ?? "none").toLowerCase()}`}
42
- >
43
- {badge}
44
- </span>
45
- {persistedThisSession && (
46
- <span className="persisted-tag">persisted</span>
47
- )}
48
- </p>
49
- <div className="stat-grid">
50
- <div className="stat">
51
- <span className="stat-label">Checkpoints</span>
52
- <span className="stat-value">{checkpointCount.toLocaleString()}</span>
53
- </div>
54
- <div className="stat stat-highlight">
55
- <span className="stat-label">Saved</span>
56
- <span className="stat-value">{fmt(tokensSaved)}</span>
57
- </div>
58
- <div className="stat">
59
- <span className="stat-label">Dedup hit</span>
60
- <span className="stat-value">{dedupHitRate.toFixed(1)}%</span>
61
- </div>
24
+ <div className="card crew-card">
25
+ <h3>Crew / Agents</h3>
26
+ <div className="ov-stat-row">
27
+ <span className="ov-stat-label">Active Agents</span>
28
+ <span className="ov-stat-value">{activeAgents}</span>
29
+ </div>
30
+ <div className="ov-stat-row">
31
+ <span className="ov-stat-label">Current Turn</span>
32
+ <span className="ov-stat-value">{currentTurn}</span>
33
+ </div>
34
+ <div className="ov-stat-row">
35
+ <span className="ov-stat-label">Status</span>
36
+ <span className="ov-stat-value">{status}</span>
62
37
  </div>
63
38
  </div>
64
39
  );
@@ -1,7 +1,8 @@
1
1
  /**
2
2
  * dashboard-client/src/components/SummaryTiles.tsx — aggregate header tiles.
3
3
  *
4
- * Total repos, active repos (24h), total checkpoints, total tokens saved.
4
+ * 4 tiles: Repositories, Total Checkpoints, Total Tokens Saved,
5
+ * Compressed-Original. Matches old dashboard #panel-summary tiles.
5
6
  */
6
7
 
7
8
  import type React from "react";
@@ -9,14 +10,22 @@ import type React from "react";
9
10
  export interface SummaryTilesProps {
10
11
  /** Total repos in the registry. */
11
12
  totalRepos: number;
12
- /** Repos active within the last 24 hours. */
13
- activeRepos: number;
14
13
  /** Total checkpoints across all repos. */
15
14
  totalCheckpoints: number;
16
15
  /** Total tokens saved across all repos. */
17
16
  totalTokensSaved: number;
17
+ /** Total compressed-original bytes across all repos. */
18
+ compressedOriginalBytes: number;
18
19
  }
19
20
 
21
+ /** Format bytes → MiB/KiB/B (matches html.ts fmtBytesTop). */
22
+ function fmtBytesTop(b: number): string {
23
+ if (b >= 1_048_576) return `${(b / 1_048_576).toFixed(1)} MiB`;
24
+ if (b >= 1024) return `${(b / 1024).toFixed(1)} KiB`;
25
+ return `${b} B`;
26
+ }
27
+
28
+ /** Format large numbers with K/M suffix. */
20
29
  function fmt(n: number): string {
21
30
  if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
22
31
  if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;
@@ -25,28 +34,28 @@ function fmt(n: number): string {
25
34
 
26
35
  export function SummaryTiles({
27
36
  totalRepos,
28
- activeRepos,
29
37
  totalCheckpoints,
30
38
  totalTokensSaved,
39
+ compressedOriginalBytes,
31
40
  }: SummaryTilesProps): React.ReactElement {
32
41
  return (
33
42
  <div className="summary-tiles">
34
43
  <div className="tile">
35
- <span className="tile-label">Total repos</span>
44
+ <span className="tile-label">Repositories</span>
36
45
  <span className="tile-value">{totalRepos.toLocaleString()}</span>
37
46
  </div>
38
47
  <div className="tile">
39
- <span className="tile-label">Active (24h)</span>
40
- <span className="tile-value">{activeRepos.toLocaleString()}</span>
41
- </div>
42
- <div className="tile">
43
- <span className="tile-label">Checkpoints</span>
48
+ <span className="tile-label">Total Checkpoints</span>
44
49
  <span className="tile-value">{totalCheckpoints.toLocaleString()}</span>
45
50
  </div>
46
51
  <div className="tile tile-highlight">
47
- <span className="tile-label">Tokens saved</span>
52
+ <span className="tile-label">Total Tokens Saved</span>
48
53
  <span className="tile-value">{fmt(totalTokensSaved)}</span>
49
54
  </div>
55
+ <div className="tile">
56
+ <span className="tile-label">Compressed-Original</span>
57
+ <span className="tile-value">{fmtBytesTop(compressedOriginalBytes)}</span>
58
+ </div>
50
59
  </div>
51
60
  );
52
61
  }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * dashboard-client/src/components/TimeSavedCard.tsx — Time Saved (est.) card.
3
+ *
4
+ * 4 fields: Compact (session), Compact (total),
5
+ * Cache Hit (session), Cache Hit (total).
6
+ */
7
+
8
+ import type React from "react";
9
+ import { fmtSec } from "../utils/format";
10
+
11
+ export interface TimeSavedCardProps {
12
+ /** Seconds saved by compaction this session. */
13
+ compactSessionSec: number;
14
+ /** Total seconds saved by compaction. */
15
+ compactTotalSec: number;
16
+ /** Seconds saved by cache hits this session. */
17
+ cacheHitSessionSec: number;
18
+ /** Total seconds saved by cache hits. */
19
+ cacheHitTotalSec: number;
20
+ }
21
+
22
+ function StatRow({
23
+ label,
24
+ value,
25
+ }: {
26
+ label: string;
27
+ value: string;
28
+ }): React.ReactElement {
29
+ return (
30
+ <div className="ov-stat-row">
31
+ <span className="ov-stat-label">{label}</span>
32
+ <span className="ov-stat-value">{value}</span>
33
+ </div>
34
+ );
35
+ }
36
+
37
+ export function TimeSavedCard(
38
+ props: TimeSavedCardProps,
39
+ ): React.ReactElement {
40
+ return (
41
+ <div className="card time-saved-card">
42
+ <h3>⏱ Time Saved (est.)</h3>
43
+ <StatRow
44
+ label="Compact (session)"
45
+ value={fmtSec(props.compactSessionSec)}
46
+ />
47
+ <StatRow
48
+ label="Compact (total)"
49
+ value={fmtSec(props.compactTotalSec)}
50
+ />
51
+ <StatRow
52
+ label="Cache Hit (session)"
53
+ value={fmtSec(props.cacheHitSessionSec)}
54
+ />
55
+ <StatRow
56
+ label="Cache Hit (total)"
57
+ value={fmtSec(props.cacheHitTotalSec)}
58
+ />
59
+ </div>
60
+ );
61
+ }
@@ -1,7 +1,11 @@
1
1
  /**
2
2
  * dashboard-client/src/components/TriggerStatus.tsx — trigger armed/ready status.
3
3
  *
4
- * Two bullets (armed, ready) + threshold display + fast-gate %.
4
+ * Two bullets (Armed, Ready) + state text.
5
+ * Ready bullet shows "na" (yellow) when not armed.
6
+ * State text: ready → "THRESHOLD EXCEEDED — compacting next event",
7
+ * armed → "past fast gate — monitoring token count",
8
+ * idle → "idle — below fast gate".
5
9
  */
6
10
 
7
11
  import type React from "react";
@@ -19,15 +23,19 @@ export interface TriggerStatusProps {
19
23
  fastGatePct: number;
20
24
  }
21
25
 
26
+ type BulletState = "on" | "off" | "na";
27
+
22
28
  function Bullet({
23
- on,
29
+ state,
24
30
  label,
25
31
  }: {
26
- on: boolean;
32
+ state: BulletState;
27
33
  label: string;
28
34
  }): React.ReactElement {
35
+ const cls =
36
+ state === "on" ? "bullet-on" : state === "na" ? "bullet-na" : "bullet-off";
29
37
  return (
30
- <div className={`bullet ${on ? "bullet-on" : "bullet-off"}`}>
38
+ <div className={`bullet ${cls}`}>
31
39
  <span className="bullet-dot" aria-hidden="true" />
32
40
  <span className="bullet-label">{label}</span>
33
41
  </div>
@@ -37,26 +45,30 @@ function Bullet({
37
45
  export function TriggerStatus({
38
46
  armed,
39
47
  ready,
40
- currentTokens,
41
- thresholdTokens,
42
- fastGatePct,
43
48
  }: TriggerStatusProps): React.ReactElement {
44
- const cur =
45
- currentTokens !== null && currentTokens !== undefined
46
- ? currentTokens.toLocaleString()
47
- : "unknown";
49
+ // tr-armed: on if armed, off if not
50
+ // tr-ready: on if ready, na (yellow) if not armed, off if armed but not ready
51
+ const armedState: BulletState = armed ? "on" : "off";
52
+ const readyState: BulletState = ready
53
+ ? "on"
54
+ : !armed
55
+ ? "na"
56
+ : "off";
57
+
58
+ const stateText = ready
59
+ ? "THRESHOLD EXCEEDED — compacting next event"
60
+ : armed
61
+ ? "past fast gate — monitoring token count"
62
+ : "idle — below fast gate";
63
+
48
64
  return (
49
65
  <div className="card trigger-status">
50
- <h3>Trigger</h3>
66
+ <h3>Trigger Status</h3>
51
67
  <div className="bullet-row">
52
- <Bullet on={armed} label="Armed" />
53
- <Bullet on={ready} label="Ready" />
68
+ <Bullet state={armedState} label="Armed" />
69
+ <Bullet state={readyState} label="Ready" />
54
70
  </div>
55
- <p className="trigger-detail">
56
- Threshold: {thresholdTokens.toLocaleString()} tokens
57
- </p>
58
- <p className="trigger-detail">Current: {cur} tokens</p>
59
- <p className="trigger-detail">Fast gate: {fastGatePct}%</p>
71
+ <p className="trigger-state-text">{stateText}</p>
60
72
  </div>
61
73
  );
62
74
  }
@@ -0,0 +1,134 @@
1
+ /**
2
+ * dashboard-client/src/components/VectorStoreCard.tsx — Vector Store (session) card.
3
+ *
4
+ * 9 fields: Checkpoints, Original (dropped), Kept (summaries), Freed,
5
+ * Injected, Recall Relevance %, Storage Dedup %, Collapsed, Last ID.
6
+ * Plus a compression meter bar (green ≥90%, yellow ≥60%, red <60%).
7
+ *
8
+ * NOTE: dedupHitRate, storageDedupRate, compressionPct, dedupPct are
9
+ * 0–1 fractions at runtime (despite contract saying 0–100).
10
+ * Tooltips copied verbatim from html.ts.
11
+ */
12
+
13
+ import type React from "react";
14
+ import { fmtPctFromFraction } from "../utils/format";
15
+
16
+ export interface VectorStoreCardProps {
17
+ checkpointCount: number;
18
+ /** Original tokens dropped into compaction (session). */
19
+ tokensIn: number;
20
+ /** Kept summary tokens (session). */
21
+ tokensOut: number;
22
+ /** Tokens freed = dropped − kept (session). */
23
+ tokensFreed: number;
24
+ /** Recall injection count. */
25
+ injectedCount: number;
26
+ /** Recall relevance — 0–1 fraction. */
27
+ dedupHitRate: number;
28
+ /** Storage dedup rate — 0–1 fraction. */
29
+ storageDedupRate: number;
30
+ /** Duplicate chunks collapsed. */
31
+ dedupCollapsed: number;
32
+ /** Last checkpoint ID or null. */
33
+ lastCheckpointId: string | null;
34
+ /** Session compression ratio — 0–1 fraction. */
35
+ compressionPct: number;
36
+ /** Session dedup contribution — 0–1 fraction. */
37
+ dedupPct: number;
38
+ }
39
+
40
+ /** Compression meter colour class (inverse of context: high = good). */
41
+ function compressClass(sp: number): string {
42
+ if (sp >= 0.9) return "meter-green";
43
+ if (sp >= 0.6) return "meter-yellow";
44
+ return "meter-red";
45
+ }
46
+
47
+ /** Stat row with optional tooltip. */
48
+ function StatRow({
49
+ label,
50
+ value,
51
+ title,
52
+ }: {
53
+ label: string;
54
+ value: string | number;
55
+ title?: string;
56
+ }): React.ReactElement {
57
+ return (
58
+ <div className="ov-stat-row">
59
+ <span className="ov-stat-label" title={title}>
60
+ {label}
61
+ </span>
62
+ <span className="ov-stat-value">{value}</span>
63
+ </div>
64
+ );
65
+ }
66
+
67
+ export function VectorStoreCard(
68
+ props: VectorStoreCardProps,
69
+ ): React.ReactElement {
70
+ const sp = props.compressionPct;
71
+ const barCls = compressClass(sp);
72
+ const barWidth = Math.max(sp * 100, 0.5);
73
+ const compressSub = `${fmtPctFromFraction(sp)} tokens saved · dedup: ${fmtPctFromFraction(props.dedupPct)}`;
74
+
75
+ return (
76
+ <div className="card vector-store-card">
77
+ <h3>Vector Store (session)</h3>
78
+ <StatRow
79
+ label="Checkpoints"
80
+ value={props.checkpointCount.toLocaleString()}
81
+ title="A saved summary of a chunk of your conversation that was compacted to free up space."
82
+ />
83
+ <StatRow
84
+ label="Original (dropped)"
85
+ value={props.tokensIn.toLocaleString()}
86
+ title="Total size of the original conversation text dropped into compaction this session, including redundant regions skipped by dedup. This is the 'in'."
87
+ />
88
+ <StatRow
89
+ label="Kept (summaries)"
90
+ value={props.tokensOut.toLocaleString()}
91
+ title="Compact summaries we are currently holding as 'memory' for this session (the 'out'). Smaller is better."
92
+ />
93
+ <StatRow
94
+ label="Freed"
95
+ value={props.tokensFreed.toLocaleString()}
96
+ title="Conversation space freed = dropped − kept (the 'saved')."
97
+ />
98
+ <StatRow
99
+ label="Injected"
100
+ value={props.injectedCount}
101
+ title="How many times old context was automatically brought back into the conversation because it was relevant to what you were doing."
102
+ />
103
+ <StatRow
104
+ label="Recall Relevance"
105
+ value={fmtPctFromFraction(props.dedupHitRate)}
106
+ title="Of the times we recalled old context, how often it was actually on-topic."
107
+ />
108
+ <StatRow
109
+ label="Storage Dedup"
110
+ value={fmtPctFromFraction(props.storageDedupRate)}
111
+ title="How often new content matched something we already had, so we skipped storing a duplicate copy. Higher = less wasted space."
112
+ />
113
+ <StatRow
114
+ label="Collapsed"
115
+ value={props.dedupCollapsed}
116
+ title="How many duplicate chunks we collapsed into one instead of storing separately."
117
+ />
118
+ <StatRow
119
+ label="Last ID"
120
+ value={props.lastCheckpointId ?? "—"}
121
+ title="The ID of the most recent saved checkpoint."
122
+ />
123
+ <div className="ov-compression-meter">
124
+ <div className="meter-track">
125
+ <div
126
+ className={`meter-fill ${barCls}`}
127
+ style={{ width: `${barWidth}%` }}
128
+ />
129
+ </div>
130
+ <span className="meter-sub">{compressSub}</span>
131
+ </div>
132
+ </div>
133
+ );
134
+ }
@@ -10,6 +10,11 @@ import App from "./App";
10
10
  import "./styles/base.css";
11
11
  import "./styles/overview-events.css";
12
12
  import "./styles/repos-metrics.css";
13
+ import "./styles/repos-extra.css";
14
+ import "./styles/overview-extra.css";
15
+ import "./styles/config.css";
16
+ import "./styles/metrics-extra.css";
17
+ import "./styles/game-achievements.css";
13
18
 
14
19
  const rootEl = document.getElementById("root");
15
20
  if (!rootEl) {