pi-mega-compact 0.21.2 → 0.21.4

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 (141) hide show
  1. package/dist/extensions/dashboard-server/routes-rag-settings-helpers.js +1 -0
  2. package/dist/extensions/mega-compact-child.js +126 -0
  3. package/dist/extensions/mega-config.js +5 -0
  4. package/dist/extensions/mega-events/agent-handlers/turnEndHandler/recordTurnRow.js +12 -2
  5. package/dist/extensions/mega-events/context-handler/afterCompact.js +11 -0
  6. package/dist/extensions/mega-events/context-handler/dbMirrorAppend.js +8 -0
  7. package/dist/extensions/mega-events/health-handler.js +12 -2
  8. package/dist/extensions/mega-events/session-handlers.js +1 -0
  9. package/dist/extensions/mega-pipeline/compact/run.js +11 -0
  10. package/dist/extensions/mega-runtime/helpers.js +45 -0
  11. package/dist/extensions/mega-runtime/internal-errors.js +14 -0
  12. package/dist/extensions/mega-runtime/runtime-instrumentation.js +166 -0
  13. package/dist/extensions/mega-runtime/runtime.js +27 -193
  14. package/dist/extensions/mega-runtime/state.js +1 -1
  15. package/dist/extensions/mega-runtime/widget.js +1 -1
  16. package/dist/extensions/mega-turn-store.js +5 -0
  17. package/dist/src/bridge/factory.js +195 -0
  18. package/dist/src/bridge/types.js +10 -0
  19. package/dist/src/bridge.js +1 -0
  20. package/dist/src/contextHealth.js +13 -5
  21. package/dist/src/metrics/turns.js +6 -1
  22. package/dist/src/store/sqlite/context-health-cache-poison.js +60 -0
  23. package/dist/src/store/sqlite/context-health.js +6 -48
  24. package/dist/src/store/sqlite/embedding-cache.js +4 -0
  25. package/dist/src/store/sqlite/schema.js +4 -0
  26. package/dist/src/store/turns/memory-store/admin.js +119 -0
  27. package/dist/src/store/turns/memory-store/ctx.js +28 -0
  28. package/dist/src/store/turns/memory-store/read.js +110 -0
  29. package/dist/src/store/turns/memory-store/write.js +88 -0
  30. package/dist/src/store/turns/memory-store.js +44 -273
  31. package/dist/src/store/turns/migrations.js +4 -0
  32. package/dist/src/store/turns/schema.js +1 -0
  33. package/dist/src/store/turns/sqlite-store/read.js +13 -0
  34. package/dist/src/store/turns/sqlite-store/rows.js +4 -0
  35. package/dist/src/store/turns/sqlite-store/write.js +3 -3
  36. package/dist/src/store/turns/sqlite-store.js +4 -0
  37. package/extensions/dashboard-client/dist/assets/{AdminTab-BsA8-_bX.js → AdminTab-C4Y0_UVx.js} +2 -2
  38. package/extensions/dashboard-client/dist/assets/{AdminTab-BsA8-_bX.js.map → AdminTab-C4Y0_UVx.js.map} +1 -1
  39. package/extensions/dashboard-client/dist/assets/{AreaChart-BRHEfWhc.js → AreaChart-MCWK9rn6.js} +2 -2
  40. package/extensions/dashboard-client/dist/assets/{AreaChart-BRHEfWhc.js.map → AreaChart-MCWK9rn6.js.map} +1 -1
  41. package/extensions/dashboard-client/dist/assets/{BarChart-C-M0LaFc.js → BarChart-CvSGAiWV.js} +2 -2
  42. package/extensions/dashboard-client/dist/assets/{BarChart-C-M0LaFc.js.map → BarChart-CvSGAiWV.js.map} +1 -1
  43. package/extensions/dashboard-client/dist/assets/{CacheTab-Cj3n59U3.js → CacheTab-DnMEVRro.js} +2 -2
  44. package/extensions/dashboard-client/dist/assets/{CacheTab-Cj3n59U3.js.map → CacheTab-DnMEVRro.js.map} +1 -1
  45. package/extensions/dashboard-client/dist/assets/{ConfigTab-C016XTNC.js → ConfigTab-BExuZYy2.js} +2 -2
  46. package/extensions/dashboard-client/dist/assets/{ConfigTab-C016XTNC.js.map → ConfigTab-BExuZYy2.js.map} +1 -1
  47. package/extensions/dashboard-client/dist/assets/{EventsTab-DE29fuqe.js → EventsTab-CspsEslY.js} +2 -2
  48. package/extensions/dashboard-client/dist/assets/{EventsTab-DE29fuqe.js.map → EventsTab-CspsEslY.js.map} +1 -1
  49. package/extensions/dashboard-client/dist/assets/HealthTab-DiWzo2ML.js +2 -0
  50. package/extensions/dashboard-client/dist/assets/HealthTab-DiWzo2ML.js.map +1 -0
  51. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-4fJ7M4S4.js → MaintenanceTab-9NJuv3dB.js} +2 -2
  52. package/extensions/dashboard-client/dist/assets/{MaintenanceTab-4fJ7M4S4.js.map → MaintenanceTab-9NJuv3dB.js.map} +1 -1
  53. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-CDbcK9e4.js → MemoryMapTab-YpJK1h17.js} +2 -2
  54. package/extensions/dashboard-client/dist/assets/{MemoryMapTab-CDbcK9e4.js.map → MemoryMapTab-YpJK1h17.js.map} +1 -1
  55. package/extensions/dashboard-client/dist/assets/{MetricsCards-VUEcgGzo.js → MetricsCards-D2Mom1M7.js} +2 -2
  56. package/extensions/dashboard-client/dist/assets/{MetricsCards-VUEcgGzo.js.map → MetricsCards-D2Mom1M7.js.map} +1 -1
  57. package/extensions/dashboard-client/dist/assets/MetricsTab-DqWF7wud.js +2 -0
  58. package/extensions/dashboard-client/dist/assets/{MetricsTab-CKqJ0Zl-.js.map → MetricsTab-DqWF7wud.js.map} +1 -1
  59. package/extensions/dashboard-client/dist/assets/{OverviewTab-9Kxo79mi.js → OverviewTab-YuRxjV9U.js} +2 -2
  60. package/extensions/dashboard-client/dist/assets/{OverviewTab-9Kxo79mi.js.map → OverviewTab-YuRxjV9U.js.map} +1 -1
  61. package/extensions/dashboard-client/dist/assets/{ReposTab-C4iveqrP.js → ReposTab-sXMrR7je.js} +2 -2
  62. package/extensions/dashboard-client/dist/assets/{ReposTab-C4iveqrP.js.map → ReposTab-sXMrR7je.js.map} +1 -1
  63. package/extensions/dashboard-client/dist/assets/{SessionsTab-DSEwkMQm.js → SessionsTab-4YCXeGNR.js} +2 -2
  64. package/extensions/dashboard-client/dist/assets/{SessionsTab-DSEwkMQm.js.map → SessionsTab-4YCXeGNR.js.map} +1 -1
  65. package/extensions/dashboard-client/dist/assets/{SetupTab-CCyWgy4O.js → SetupTab-67pZcM_W.js} +2 -2
  66. package/extensions/dashboard-client/dist/assets/{SetupTab-CCyWgy4O.js.map → SetupTab-67pZcM_W.js.map} +1 -1
  67. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CyD8e_e_.js → TimeSavedCard-DInjNETP.js} +2 -2
  68. package/extensions/dashboard-client/dist/assets/{TimeSavedCard-CyD8e_e_.js.map → TimeSavedCard-DInjNETP.js.map} +1 -1
  69. package/extensions/dashboard-client/dist/assets/{TurnMemoryView-5tLbWkRv.js → TurnMemoryView-C3Bif8ix.js} +2 -2
  70. package/extensions/dashboard-client/dist/assets/{TurnMemoryView-5tLbWkRv.js.map → TurnMemoryView-C3Bif8ix.js.map} +1 -1
  71. package/extensions/dashboard-client/dist/assets/TurnsTab-BUGfiANp.js +2 -0
  72. package/extensions/dashboard-client/dist/assets/{TurnsTab-C7pm92Hg.js.map → TurnsTab-BUGfiANp.js.map} +1 -1
  73. package/extensions/dashboard-client/dist/assets/{VcStatusBadge-ObhcIyfN.js → VcStatusBadge-DKU7qz9f.js} +2 -2
  74. package/extensions/dashboard-client/dist/assets/{VcStatusBadge-ObhcIyfN.js.map → VcStatusBadge-DKU7qz9f.js.map} +1 -1
  75. package/extensions/dashboard-client/dist/assets/{VectorCortexTab-BtHZb7o7.js → VectorCortexTab-BsVSf4xm.js} +2 -2
  76. package/extensions/dashboard-client/dist/assets/{VectorCortexTab-BtHZb7o7.js.map → VectorCortexTab-BsVSf4xm.js.map} +1 -1
  77. package/extensions/dashboard-client/dist/assets/{WikiTab-lQ4l3QIK.js → WikiTab-DZw9_Fwd.js} +2 -2
  78. package/extensions/dashboard-client/dist/assets/{WikiTab-lQ4l3QIK.js.map → WikiTab-DZw9_Fwd.js.map} +1 -1
  79. package/extensions/dashboard-client/dist/assets/{button-DhQQAJGm.js → button-BLRPN489.js} +2 -2
  80. package/extensions/dashboard-client/dist/assets/{button-DhQQAJGm.js.map → button-BLRPN489.js.map} +1 -1
  81. package/extensions/dashboard-client/dist/assets/{card-Cfhz9X31.js → card-BGkkcM4Q.js} +2 -2
  82. package/extensions/dashboard-client/dist/assets/{card-Cfhz9X31.js.map → card-BGkkcM4Q.js.map} +1 -1
  83. package/extensions/dashboard-client/dist/assets/{client-extra-CKNOGEz_.js → client-extra-0OJClxHD.js} +2 -2
  84. package/extensions/dashboard-client/dist/assets/{client-extra-CKNOGEz_.js.map → client-extra-0OJClxHD.js.map} +1 -1
  85. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-JKjK9_cB.js → generateCategoricalChart-Dl5lmrHF.js} +2 -2
  86. package/extensions/dashboard-client/dist/assets/{generateCategoricalChart-JKjK9_cB.js.map → generateCategoricalChart-Dl5lmrHF.js.map} +1 -1
  87. package/extensions/dashboard-client/dist/assets/health-lSeDFTz7.js.map +1 -1
  88. package/extensions/dashboard-client/dist/assets/{index-iuxgtzEW.js → index-CH_AD-S_.js} +3 -3
  89. package/extensions/dashboard-client/dist/assets/{index-iuxgtzEW.js.map → index-CH_AD-S_.js.map} +1 -1
  90. package/extensions/dashboard-client/dist/assets/{switch-7EHEQMSP.js → switch-BPP2-d12.js} +2 -2
  91. package/extensions/dashboard-client/dist/assets/{switch-7EHEQMSP.js.map → switch-BPP2-d12.js.map} +1 -1
  92. package/extensions/dashboard-client/dist/assets/{toggle-z1lWY4UM.js → toggle-Dez0JaMV.js} +2 -2
  93. package/extensions/dashboard-client/dist/assets/{toggle-z1lWY4UM.js.map → toggle-Dez0JaMV.js.map} +1 -1
  94. package/extensions/dashboard-client/dist/assets/{useSSE-sctOZuRM.js → useSSE-Cx2W7BTo.js} +2 -2
  95. package/extensions/dashboard-client/dist/assets/{useSSE-sctOZuRM.js.map → useSSE-Cx2W7BTo.js.map} +1 -1
  96. package/extensions/dashboard-client/dist/index.html +1 -1
  97. package/extensions/dashboard-client/src/api/health.ts +1 -0
  98. package/extensions/dashboard-client/src/tabs/HealthTab.tsx +1 -0
  99. package/extensions/dashboard-server/routes-rag-settings-helpers.ts +6 -0
  100. package/extensions/mega-compact-child.ts +129 -0
  101. package/extensions/mega-config-types.ts +5 -0
  102. package/extensions/mega-config.ts +5 -0
  103. package/extensions/mega-events/agent-handlers/turnEndHandler/recordTurnRow.ts +12 -2
  104. package/extensions/mega-events/context-handler/afterCompact.ts +11 -0
  105. package/extensions/mega-events/context-handler/dbMirrorAppend.ts +8 -0
  106. package/extensions/mega-events/health-handler.ts +13 -3
  107. package/extensions/mega-events/session-handlers.ts +1 -0
  108. package/extensions/mega-pipeline/compact/run.ts +11 -0
  109. package/extensions/mega-runtime/helpers.ts +46 -0
  110. package/extensions/mega-runtime/internal-errors.ts +30 -0
  111. package/extensions/mega-runtime/runtime-instrumentation.ts +195 -0
  112. package/extensions/mega-runtime/runtime.ts +29 -209
  113. package/extensions/mega-runtime/state.ts +1 -1
  114. package/extensions/mega-runtime/widget.ts +1 -1
  115. package/extensions/mega-turn-store.ts +9 -0
  116. package/package.json +2 -2
  117. package/src/bridge/factory.ts +245 -0
  118. package/src/bridge/types.ts +140 -0
  119. package/src/bridge.ts +24 -0
  120. package/src/contextHealth.ts +26 -6
  121. package/src/metrics/turns.ts +6 -1
  122. package/src/store/sqlite/context-health-cache-poison.ts +97 -0
  123. package/src/store/sqlite/context-health.ts +12 -84
  124. package/src/store/sqlite/embedding-cache.ts +4 -0
  125. package/src/store/sqlite/schema.ts +4 -0
  126. package/src/store/turns/memory-store/admin.ts +171 -0
  127. package/src/store/turns/memory-store/ctx.ts +62 -0
  128. package/src/store/turns/memory-store/read.ts +172 -0
  129. package/src/store/turns/memory-store/write.ts +126 -0
  130. package/src/store/turns/memory-store.ts +51 -330
  131. package/src/store/turns/migrations.ts +4 -0
  132. package/src/store/turns/schema.ts +1 -0
  133. package/src/store/turns/sqlite-store/read.ts +19 -0
  134. package/src/store/turns/sqlite-store/rows.ts +5 -0
  135. package/src/store/turns/sqlite-store/write.ts +3 -2
  136. package/src/store/turns/sqlite-store.ts +5 -0
  137. package/src/store/turns/types.ts +15 -0
  138. package/extensions/dashboard-client/dist/assets/HealthTab-D4eVI8ar.js +0 -2
  139. package/extensions/dashboard-client/dist/assets/HealthTab-D4eVI8ar.js.map +0 -1
  140. package/extensions/dashboard-client/dist/assets/MetricsTab-CKqJ0Zl-.js +0 -2
  141. package/extensions/dashboard-client/dist/assets/TurnsTab-C7pm92Hg.js +0 -2
@@ -1,14 +1,20 @@
1
1
  /**
2
2
  * memory-store.ts — InMemoryTurnStore: test harness + embeddable backend.
3
3
  *
4
- * Same contract as SqliteTurnStore, backed by Maps. No file I/O.
5
- * Used for tests and for hosts that don't want SQLite.
4
+ * Thin delegate shell: each method body lives as a free function in
5
+ * ./memory-store/* (read.ts / write.ts / admin.ts) operating on an internal
6
+ * ctx ({turns, recall, forks, convIndex, sessionConv}); this class wires those
7
+ * to the contract. The public API surface — InMemoryTurnStore implements
8
+ * TurnStore, all methods, asReader/asWriter/asAdmin, close — is unchanged
9
+ * for existing callers.
10
+ *
11
+ * Same contract as SqliteTurnStore, backed by Maps. No file I/O. Used for
12
+ * tests and for hosts that don't want SQLite. Mirrors sqlite-store.ts.
6
13
  *
7
14
  * PREVENT-PI-004: no network. PREVENT-002: no SQL here.
8
15
  */
9
16
 
10
- import { randomBytes } from "node:crypto";
11
- import { DuplicateTurnError, type
17
+ import type {
12
18
  TurnStore,
13
19
  TurnReader,
14
20
  TurnWriter,
@@ -26,35 +32,15 @@ import { DuplicateTurnError, type
26
32
  ConversationStats,
27
33
  TurnStoreOptions,
28
34
  } from "./types.js";
29
-
30
- function newConversationId(): ConversationId {
31
- return `conv_${randomBytes(8).toString("hex")}`;
32
- }
33
-
34
- function normalizeSessionId(sid: SessionId): SessionId {
35
- return sid.replace(/-.*$/, "");
36
- }
37
-
38
- // Auto-increment counter
39
- let nextId = 1;
40
- function allocId(): number {
41
- return nextId++;
42
- }
43
- function resetIdCounter(): void {
44
- nextId = 1;
45
- }
46
-
47
- // Internal row with numeric id
48
- interface TurnRow {
49
- id: number;
50
- entry: TurnEntry;
51
- epochId?: string;
52
- }
53
-
54
- interface RecallRow {
55
- turnId: number;
56
- entry: TurnRecallEntry;
57
- }
35
+ import {
36
+ resetIdCounter,
37
+ type MemoryTurnStoreCtx,
38
+ type RecallRow,
39
+ type TurnRow,
40
+ } from "./memory-store/ctx.js";
41
+ import * as read from "./memory-store/read.js";
42
+ import * as write from "./memory-store/write.js";
43
+ import * as admin from "./memory-store/admin.js";
58
44
 
59
45
  export class InMemoryTurnStore implements TurnStore {
60
46
  private turns: Map<number, TurnRow> = new Map();
@@ -69,371 +55,105 @@ export class InMemoryTurnStore implements TurnStore {
69
55
  resetIdCounter();
70
56
  }
71
57
 
58
+ private ctx(): MemoryTurnStoreCtx {
59
+ return {
60
+ turns: this.turns,
61
+ recall: this.recall,
62
+ forks: this.forks,
63
+ convIndex: this.convIndex,
64
+ sessionConv: this.sessionConv,
65
+ };
66
+ }
67
+
72
68
  // ── TurnReader ──────────────────────────────────────────────
73
69
 
74
70
  query(filter: TurnFilter): TurnEntry[] {
75
- let results = [...this.turns.values()].map((r) => r.entry);
76
-
77
- if (filter.conversationId !== undefined) {
78
- results = results.filter(
79
- (t) => t.conversationId === filter.conversationId,
80
- );
81
- }
82
- if (filter.sessionId !== undefined) {
83
- results = results.filter(
84
- (t) =>
85
- normalizeSessionId(t.sessionId) ===
86
- normalizeSessionId(filter.sessionId!),
87
- );
88
- }
89
- if (filter.sinceMs !== undefined) {
90
- results = results.filter((t) => t.endedAt >= filter.sinceMs!);
91
- }
92
- if (filter.untilMs !== undefined) {
93
- results = results.filter((t) => t.endedAt <= filter.untilMs!);
94
- }
95
- if (filter.pressureBand !== undefined) {
96
- results = results.filter((t) => t.pressureBand === filter.pressureBand);
97
- }
98
-
99
- results.sort((a, b) => a.endedAt - b.endedAt);
100
-
101
- const offset = filter.offset ?? 0;
102
- const limit = filter.limit ?? 1000;
103
- return results.slice(offset, offset + limit);
71
+ return read.query(this.ctx(), filter);
104
72
  }
105
73
 
106
74
  getTurn(turnId: TurnId): TurnEntry | undefined {
107
- const row = this.turns.get(Number(turnId));
108
- return row ? { ...row.entry, epochId: row.epochId } : undefined;
75
+ return read.getTurn(this.ctx(), turnId);
109
76
  }
110
77
 
111
78
  getTurnByIndex(
112
79
  conversationId: ConversationId,
113
80
  turnIndex: number,
114
81
  ): TurnEntry | undefined {
115
- const ids = this.convIndex.get(conversationId) ?? [];
116
- for (const id of ids) {
117
- const row = this.turns.get(id);
118
- if (row && row.entry.turnIndex === turnIndex) {
119
- return { ...row.entry, epochId: row.epochId };
120
- }
121
- }
122
- return undefined;
82
+ return read.getTurnByIndex(this.ctx(), conversationId, turnIndex);
123
83
  }
124
84
 
125
85
  listRecall(turnId: TurnId): TurnRecallEntry[] {
126
- return this.recall
127
- .filter((r) => r.turnId === Number(turnId))
128
- .map((r) => r.entry)
129
- .sort((a, b) => b.score - a.score);
86
+ return read.listRecall(this.ctx(), turnId);
130
87
  }
131
88
 
132
89
  listRecallByIndex(
133
90
  conversationId: ConversationId,
134
91
  turnIndex: number,
135
92
  ): TurnRecallEntry[] {
136
- const turn = this.getTurnByIndex(conversationId, turnIndex);
137
- if (!turn) return [];
138
- // Resolve the numeric row id for this turn via the convIndex.
139
- const ids = this.convIndex.get(conversationId) ?? [];
140
- for (const id of ids) {
141
- const row = this.turns.get(id);
142
- if (row && row.entry.turnIndex === turnIndex) {
143
- return this.listRecall(String(id));
144
- }
145
- }
146
- return [];
93
+ return read.listRecallByIndex(this.ctx(), conversationId, turnIndex);
147
94
  }
148
95
 
149
96
  listForks(conversationId: ConversationId): ConversationFork[] {
150
- return this.forks
151
- .filter((f) => f.parentConversationId === conversationId)
152
- .sort((a, b) => a.createdAt - b.createdAt);
97
+ return read.listForks(this.ctx(), conversationId);
153
98
  }
154
99
 
155
100
  countTurns(conversationId: ConversationId): number {
156
- const ids = this.convIndex.get(conversationId);
157
- return ids?.length ?? 0;
101
+ return read.countTurns(this.ctx(), conversationId);
158
102
  }
159
103
 
160
104
  conversationStats(conversationId: ConversationId): ConversationStats {
161
- const ids = this.convIndex.get(conversationId) ?? [];
162
- const entries = ids
163
- .map((id) => this.turns.get(id)?.entry)
164
- .filter((e): e is TurnEntry => e !== undefined);
165
-
166
- if (entries.length === 0) {
167
- return {
168
- turnCount: 0,
169
- firstTurnAt: 0,
170
- lastTurnAt: 0,
171
- avgCtxPercent: 0,
172
- pressureBands: {},
173
- };
174
- }
175
-
176
- let ctxSum = 0;
177
- let ctxCount = 0;
178
- const bands: Record<string, number> = {};
179
-
180
- for (const e of entries) {
181
- if (e.ctxPercent !== undefined) {
182
- ctxSum += e.ctxPercent;
183
- ctxCount++;
184
- }
185
- if (e.pressureBand)
186
- bands[e.pressureBand] = (bands[e.pressureBand] ?? 0) + 1;
187
- }
105
+ return read.conversationStats(this.ctx(), conversationId);
106
+ }
188
107
 
189
- return {
190
- turnCount: entries.length,
191
- firstTurnAt: entries[0].endedAt,
192
- lastTurnAt: entries[entries.length - 1].endedAt,
193
- avgCtxPercent: ctxCount > 0 ? ctxSum / ctxCount : 0,
194
- pressureBands: bands,
195
- };
108
+ nextTurnIndexFor(conversationId: ConversationId): number {
109
+ return read.nextTurnIndexFor(this.ctx(), conversationId);
196
110
  }
197
111
 
198
112
  // ── TurnWriter ──────────────────────────────────────────────
199
113
 
200
114
  appendTurn(entry: TurnEntry): TurnId {
201
- // Check for duplicate (conversationId, turnIndex)
202
- for (const [, row] of this.turns) {
203
- if (
204
- row.entry.conversationId === entry.conversationId &&
205
- row.entry.turnIndex === entry.turnIndex
206
- ) {
207
- throw new DuplicateTurnError(entry.conversationId, entry.turnIndex);
208
- }
209
- }
210
- const id = allocId();
211
- const sid = normalizeSessionId(entry.sessionId);
212
- const normalized = { ...entry, sessionId: sid };
213
- this.turns.set(id, { id, entry: normalized });
214
-
215
- // Update indices
216
- const convIds = this.convIndex.get(entry.conversationId) ?? [];
217
- convIds.push(id);
218
- this.convIndex.set(entry.conversationId, convIds);
219
-
220
- this.sessionConv.set(sid, entry.conversationId);
221
-
222
- return String(id);
115
+ return write.appendTurn(this.ctx(), entry);
223
116
  }
224
117
 
225
118
  appendRecall(entry: TurnRecallEntry): void {
226
- // De-dup by (turn_id, checkpoint_id)
227
- const exists = this.recall.some(
228
- (r) =>
229
- r.turnId === Number(entry.turnId) &&
230
- r.entry.checkpointId === entry.checkpointId,
231
- );
232
- if (!exists) {
233
- this.recall.push({ turnId: Number(entry.turnId), entry });
234
- }
119
+ write.appendRecall(this.ctx(), entry);
235
120
  }
236
121
 
237
122
  ensureConversationId(sessionId: SessionId): ConversationId {
238
- const sid = normalizeSessionId(sessionId);
239
- const existing = this.sessionConv.get(sid);
240
- if (existing) return existing;
241
- // Generate and store a new conversation ID for this session
242
- const convId = newConversationId();
243
- this.sessionConv.set(sid, convId);
244
- return convId;
123
+ return write.ensureConversationId(this.ctx(), sessionId);
245
124
  }
246
125
 
247
126
  forkConversation(
248
127
  parentId: ConversationId,
249
128
  forkTurnIndex: number,
250
129
  ): ConversationId {
251
- const childId = newConversationId();
252
- const now = Date.now();
253
-
254
- // 1. Record the fork lineage
255
- this.forks.push({
256
- parentConversationId: parentId,
257
- childConversationId: childId,
258
- forkTurnIndex,
259
- createdAt: now,
260
- });
261
-
262
- // 2. Find the parent's turn at forkTurnIndex
263
- const parentTurnIds = this.convIndex.get(parentId) ?? [];
264
- const parentTurnRow = parentTurnIds
265
- .map((id) => this.turns.get(id))
266
- .find((r) => r?.entry.turnIndex === forkTurnIndex);
267
-
268
- if (!parentTurnRow) return childId; // no parent turn — nothing to seed
269
-
270
- // 3. Create a seed turn in the child conversation
271
- const seedId = allocId();
272
- const seedEntry: TurnEntry = {
273
- conversationId: childId,
274
- sessionId: `fork_${childId}`,
275
- turnIndex: 0,
276
- role: "system",
277
- endedAt: now,
278
- };
279
- this.turns.set(seedId, { id: seedId, entry: seedEntry });
280
- this.convIndex.set(childId, [seedId]);
281
-
282
- // 4. Copy the parent's recall entries into the child's seed turn
283
- const parentRecall = this.recall.filter(
284
- (r) => r.turnId === parentTurnRow.id,
285
- );
286
- for (const r of parentRecall) {
287
- this.recall.push({
288
- turnId: seedId,
289
- entry: {
290
- turnId: String(seedId),
291
- checkpointId: r.entry.checkpointId,
292
- score: r.entry.score,
293
- source: r.entry.source,
294
- raptorLevel: r.entry.raptorLevel,
295
- },
296
- });
297
- }
298
-
299
- return childId;
130
+ return write.forkConversation(this.ctx(), parentId, forkTurnIndex);
300
131
  }
301
132
 
302
133
  // ── TurnAdmin ───────────────────────────────────────────────
303
134
 
304
135
  prune(policy: RetentionPolicy): PruneReport {
305
- const cutoff = Date.now() - policy.maxTurnAgeMs;
306
- let turnsRemoved = 0;
307
- let recallRemoved = 0;
308
-
309
- for (const [convId, turnIds] of this.convIndex.entries()) {
310
- const total = turnIds.length;
311
- const canDelete = Math.max(0, total - policy.keepMinPerConversation);
312
- if (canDelete === 0) continue;
313
-
314
- // Find old turns to delete
315
- const oldIds = turnIds
316
- .filter((id) => {
317
- const row = this.turns.get(id);
318
- return row && row.entry.endedAt < cutoff;
319
- })
320
- .slice(0, canDelete);
321
-
322
- for (const id of oldIds) {
323
- // Remove recall
324
- const before = this.recall.length;
325
- this.recall = this.recall.filter((r) => r.turnId !== id);
326
- recallRemoved += before - this.recall.length;
327
-
328
- this.turns.delete(id);
329
- turnsRemoved++;
330
- }
331
-
332
- // Update convIndex
333
- this.convIndex.set(
334
- convId,
335
- turnIds.filter((id) => this.turns.has(id)),
336
- );
337
- }
338
-
339
- const branchesPreserved = this.forks.length;
340
-
341
- return {
342
- turnsRemoved,
343
- recallRemoved,
344
- branchesPreserved,
345
- freedBytes: 0, // in-memory — no file size to measure
346
- };
136
+ return admin.prune(this.ctx(), policy);
347
137
  }
348
138
 
349
139
  vacuum(): void {
350
- // No-op for in-memory store
140
+ admin.vacuum(this.ctx());
351
141
  }
352
142
 
353
143
  checkpoint(): StoreSnapshot {
354
- // Emit turns in a stable order (by internal ID / insertion order)
355
- const sorted = [...this.turns.values()].sort((a, b) => a.id - b.id);
356
- const turns = sorted.map((r) => r.entry);
357
-
358
- // Build mapping: internal ID → 1-based position
359
- const idToPos = new Map<number, number>();
360
- for (let i = 0; i < sorted.length; i++) {
361
- idToPos.set(sorted[i].id, i + 1);
362
- }
363
-
364
- // Replace recall's turnId with the stable 1-based position
365
- const recall = this.recall.map((r) => ({
366
- turnId: String(idToPos.get(r.turnId) ?? r.turnId),
367
- checkpointId: r.entry.checkpointId,
368
- score: r.entry.score,
369
- source: r.entry.source,
370
- raptorLevel: r.entry.raptorLevel,
371
- }));
372
-
373
- return {
374
- version: 1,
375
- exportedAt: Date.now(),
376
- turns,
377
- recall,
378
- forks: [...this.forks],
379
- };
144
+ return admin.checkpoint(this.ctx());
380
145
  }
381
146
 
382
147
  restore(from: StoreSnapshot): void {
383
- this.clear();
384
-
385
- // Map: 1-based position → new internal ID
386
- const posToNewId = new Map<number, number>();
387
-
388
- for (let i = 0; i < from.turns.length; i++) {
389
- const t = from.turns[i];
390
- const newId = allocId();
391
- const sid = normalizeSessionId(t.sessionId);
392
- const normalized = { ...t, sessionId: sid };
393
- this.turns.set(newId, { id: newId, entry: normalized });
394
-
395
- // Update indices
396
- const convIds = this.convIndex.get(t.conversationId) ?? [];
397
- convIds.push(newId);
398
- this.convIndex.set(t.conversationId, convIds);
399
- this.sessionConv.set(sid, t.conversationId);
400
-
401
- posToNewId.set(i + 1, newId);
402
- }
403
-
404
- // Re-attach recall with position-based TurnId → new internal ID mapping
405
- for (const r of from.recall) {
406
- const targetInternalId = posToNewId.get(Number(r.turnId));
407
- if (targetInternalId !== undefined) {
408
- this.recall.push({
409
- turnId: targetInternalId,
410
- entry: r,
411
- });
412
- }
413
- }
414
-
415
- this.forks = [...from.forks];
148
+ admin.restore(this.ctx(), from);
416
149
  }
417
150
 
418
151
  clear(): void {
419
- this.turns.clear();
420
- this.recall = [];
421
- this.forks = [];
422
- this.convIndex.clear();
423
- this.sessionConv.clear();
424
- resetIdCounter();
152
+ admin.clear(this.ctx());
425
153
  }
426
154
 
427
155
  stampTurnsEpoch(sessionId: SessionId, epochId: string): number {
428
- const sid = normalizeSessionId(sessionId);
429
- let stamped = 0;
430
- for (const row of this.turns.values()) {
431
- if (row.epochId === undefined && row.entry.sessionId === sid) {
432
- row.epochId = epochId;
433
- stamped++;
434
- }
435
- }
436
- return stamped;
156
+ return admin.stampTurnsEpoch(this.ctx(), sessionId, epochId);
437
157
  }
438
158
 
439
159
  // ── Capability gating ───────────────────────────────────────
@@ -448,6 +168,7 @@ export class InMemoryTurnStore implements TurnStore {
448
168
  listForks: (id) => this.listForks(id),
449
169
  countTurns: (id) => this.countTurns(id),
450
170
  conversationStats: (id) => this.conversationStats(id),
171
+ nextTurnIndexFor: (id) => this.nextTurnIndexFor(id),
451
172
  };
452
173
  }
453
174
 
@@ -196,6 +196,10 @@ export function migrateTurnTablesIfNeeded(
196
196
  log(
197
197
  `migration failed (will retry on next open): ${e instanceof Error ? e.message : String(e)}`,
198
198
  );
199
+ // Sprint H (Option A): this src/ turn-table migration has no runtime handle
200
+ // (it runs at store-open via `log`). The migration_failure category is
201
+ // recorded by the store-open caller that owns a runtime, not here.
202
+ // guardrails-allow INTERNAL-ERR: src/ turn-table migration, no runtime handle — store-open caller records migration failures
199
203
  }
200
204
  }
201
205
 
@@ -185,6 +185,7 @@ export function initTurnSchema(db: DatabaseSync): void {
185
185
  ensureCol("recall_coverage", "recall_coverage REAL DEFAULT 0");
186
186
  ensureCol("recall_diversity", "recall_diversity REAL DEFAULT 0");
187
187
  ensureCol("recall_specificity", "recall_specificity REAL DEFAULT 0");
188
+ ensureCol("session_turn_index", "session_turn_index INTEGER");
188
189
 
189
190
  // ── Wiki Revival (Spec 3): user curation + topic evolution ────────
190
191
  const sessionCol = (
@@ -172,3 +172,22 @@ export function conversationStats(
172
172
  pressureBands: bands,
173
173
  };
174
174
  }
175
+
176
+ /**
177
+ * S49R: next conversation-monotonic turn index for a conversation.
178
+ * `MAX(turn_index) + 1`, or 0 when the conversation has no turns yet. A
179
+ * resumed session (pi's per-session `turnIndex` resets to 0) uses this instead
180
+ * of `event.turnIndex` so it never collides with UNIQUE(conversation_id, turn_index).
181
+ */
182
+ export function nextTurnIndexFor(
183
+ ctx: SqliteTurnStoreCtx,
184
+ conversationId: ConversationId,
185
+ ): number {
186
+ const { db } = ctx;
187
+ const row = db
188
+ .prepare(
189
+ "SELECT COALESCE(MAX(turn_index), -1) AS m FROM turns WHERE conversation_id = ?",
190
+ )
191
+ .get(conversationId) as { m: number };
192
+ return row.m + 1;
193
+ }
@@ -57,6 +57,11 @@ export function rowToEntry(r: Record<string, unknown>): TurnEntry {
57
57
  diversity: (r.recall_diversity as number) ?? 0,
58
58
  specificity: (r.recall_specificity as number) ?? 0,
59
59
  },
60
+ // S49R: carried per-session counter (null on pre-migration rows).
61
+ sessionTurnIndex:
62
+ r.session_turn_index == null
63
+ ? undefined
64
+ : (r.session_turn_index as number),
60
65
  };
61
66
  }
62
67
 
@@ -26,8 +26,8 @@ export function appendTurn(ctx: SqliteTurnStoreCtx, entry: TurnEntry): TurnId {
26
26
  hyde_ran, hyde_doc, hyde_raw_count, hyde_hyde_count,
27
27
  hyde_fused_count, hyde_lift, hyde_generation_ms, hyde_reason,
28
28
  recall_score, recall_pass, recall_relevance, recall_coverage,
29
- recall_diversity, recall_specificity)
30
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
29
+ recall_diversity, recall_specificity, session_turn_index)
30
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
31
31
  )
32
32
  .run(
33
33
  entry.conversationId,
@@ -54,6 +54,7 @@ export function appendTurn(ctx: SqliteTurnStoreCtx, entry: TurnEntry): TurnId {
54
54
  entry.recallMetrics?.coverage ?? null,
55
55
  entry.recallMetrics?.diversity ?? null,
56
56
  entry.recallMetrics?.specificity ?? null,
57
+ entry.sessionTurnIndex ?? null,
57
58
  );
58
59
  } catch (e) {
59
60
  if (
@@ -98,6 +98,10 @@ export class SqliteTurnStore implements TurnStore {
98
98
  return read.conversationStats(this.ctx(), conversationId);
99
99
  }
100
100
 
101
+ nextTurnIndexFor(conversationId: ConversationId): number {
102
+ return read.nextTurnIndexFor(this.ctx(), conversationId);
103
+ }
104
+
101
105
  // ── TurnWriter ──────────────────────────────────────────────
102
106
 
103
107
  appendTurn(entry: TurnEntry): TurnId {
@@ -157,6 +161,7 @@ export class SqliteTurnStore implements TurnStore {
157
161
  listForks: (id) => this.listForks(id),
158
162
  countTurns: (id) => this.countTurns(id),
159
163
  conversationStats: (id) => this.conversationStats(id),
164
+ nextTurnIndexFor: (id) => this.nextTurnIndexFor(id),
160
165
  };
161
166
  }
162
167
 
@@ -63,6 +63,14 @@ export interface TurnEntry {
63
63
  hyde?: TurnHydeTelemetry;
64
64
  /** H1: recall-quality snapshot for this turn (persisted to recall_* columns). */
65
65
  recallMetrics?: TurnRecallTelemetry;
66
+ /**
67
+ * S49R: pi's per-session turn counter, carried alongside the
68
+ * conversation-monotonic `turnIndex`. `turnIndex` restarts at 0 on resume,
69
+ * so it is unsafe to use as a join key against `raw_transcript` (which is
70
+ * seeded from the session counter). `sessionTurnIndex` preserves that
71
+ * per-session value for the metrics join. Null on pre-migration rows.
72
+ */
73
+ sessionTurnIndex?: number;
66
74
  }
67
75
 
68
76
  /**
@@ -176,6 +184,13 @@ export interface TurnReader {
176
184
  listForks(conversationId: ConversationId): ConversationFork[];
177
185
  countTurns(conversationId: ConversationId): number;
178
186
  conversationStats(conversationId: ConversationId): ConversationStats;
187
+ /**
188
+ * S49R: the next conversation-monotonic turn index for a conversation —
189
+ * `MAX(turn_index) + 1` (or 0 if the conversation has no turns yet). Used by
190
+ * writers so resumed sessions (where pi's per-session `turnIndex` restarts
191
+ * at 0) never collide with `UNIQUE(conversation_id, turn_index)`.
192
+ */
193
+ nextTurnIndexFor(conversationId: ConversationId): number;
179
194
  }
180
195
 
181
196
  /** Append-only writer — compaction engine, event handlers. Cannot prune. */
@@ -1,2 +0,0 @@
1
- import{r as d,j as e,B as g}from"./index-iuxgtzEW.js";import{fetchContextHealth as N}from"./health-lSeDFTz7.js";import{C as i,c as h,a as u,b as j}from"./card-Cfhz9X31.js";function m(s){return s>.8?"#4ade80":s>=.5?"#facc15":"#f87171"}function v({score:s}){const l=2*Math.PI*70,o=l*(1-Math.max(0,Math.min(1,s))),n=m(s);return e.jsxs("div",{className:"health-gauge-wrap",children:[e.jsxs("svg",{width:"180",height:"180",viewBox:"0 0 180 180",children:[e.jsx("circle",{cx:"90",cy:"90",r:70,fill:"none",stroke:"#1e293b",strokeWidth:"14"}),e.jsx("circle",{cx:"90",cy:"90",r:70,fill:"none",stroke:n,strokeWidth:"14",strokeDasharray:l,strokeDashoffset:o,strokeLinecap:"round",transform:"rotate(-90 90 90)",style:{transition:"stroke-dashoffset 0.5s ease"}}),e.jsx("text",{x:"90",y:"85",textAnchor:"middle",fontSize:"32",fontWeight:"bold",fill:n,children:(s*100).toFixed(0)}),e.jsx("text",{x:"90",y:"108",textAnchor:"middle",fontSize:"14",fill:"#94a3b8",children:"/ 100"})]}),e.jsx("div",{className:"health-gauge-label",children:"Context Health"})]})}function y({trend:s}){if(s.length<2)return e.jsx("div",{className:"health-spark-empty",children:"No trend data yet"});const r=400,l=60,o=1,n=0,f=s.map((a,p)=>{const t=p/(s.length-1)*r,b=l-(a-n)/(o-n)*l;return`${t.toFixed(1)},${b.toFixed(1)}`}),c=s[s.length-1];return e.jsx("svg",{width:r,height:l,viewBox:`0 0 ${r} ${l}`,className:"health-spark",children:e.jsx("polyline",{points:f.join(" "),fill:"none",stroke:m(c),strokeWidth:"2"})})}function x({label:s,value:r}){return e.jsxs("div",{className:"health-subscore",children:[e.jsx("span",{className:"health-subscore-label",children:s}),e.jsx("div",{className:"health-subscore-bar",children:e.jsx("div",{className:"health-subscore-fill",style:{width:`${r*100}%`,background:m(r)}})}),e.jsxs("span",{className:"health-subscore-val",children:[(r*100).toFixed(0),"%"]})]})}function H(){const[s,r]=d.useState(null),[l,o]=d.useState(null),[n,f]=d.useState(!0),c=d.useCallback(()=>{N().then(r).catch(t=>o(t instanceof Error?t.message:String(t))).finally(()=>f(!1))},[]);if(d.useEffect(()=>{c();const t=setInterval(c,5e3);return()=>clearInterval(t)},[c]),n&&!s)return e.jsx("div",{className:"health-loading",children:"Loading health data…"});if(l&&!s)return e.jsxs("div",{className:"health-error",children:["Error: ",l]});if(!s)return e.jsx("div",{className:"health-empty",children:"No health data available."});const a=s.latest,p=a?.composite??0;return e.jsxs("div",{className:"flex flex-col gap-4",children:[e.jsxs("div",{className:"grid grid-cols-1 gap-4 md:grid-cols-2",children:[e.jsx(i,{children:e.jsx(h,{className:"flex justify-center",children:e.jsx(v,{score:p})})}),e.jsxs(i,{children:[e.jsx(u,{children:e.jsx(j,{children:"Sub-Scores"})}),e.jsxs(h,{children:[e.jsx(x,{label:"Drift",value:a?.driftScore??0}),e.jsx(x,{label:"Output Quality",value:a?.outputQuality??0}),e.jsx(x,{label:"Error Rate",value:a?.errorScore??0}),e.jsx(x,{label:"Cache Health",value:a?.cacheHealth??0}),e.jsx(x,{label:"Cache Poison",value:a?.cachePoison??0})]})]})]}),e.jsxs(i,{children:[e.jsx(u,{children:e.jsxs(j,{children:["Health Trend (last ",s.trend.length," turns)"]})}),e.jsx(h,{children:e.jsx(y,{trend:s.trend})})]}),s.alerts.length>0&&e.jsxs(i,{children:[e.jsx(u,{children:e.jsxs(j,{children:["Recent Alerts (",s.alerts.length,")"]})}),e.jsx(h,{children:e.jsx("div",{className:"flex flex-col gap-1",children:s.alerts.slice(-10).map(t=>e.jsxs("div",{className:"flex items-center gap-3 border-b border-border/50 py-1 text-sm",children:[e.jsx("span",{className:"text-xs text-muted-foreground",children:new Date(t.ts).toLocaleTimeString()}),e.jsx("span",{className:"text-muted-foreground",children:t.modelId??"(unknown)"}),e.jsx("span",{style:{color:m(t.composite)},children:(t.composite*100).toFixed(0)}),t.cachePoison<.3&&e.jsx(g,{variant:"danger",children:"CACHE POISON"})]},`${t.ts}-${t.turnIndex}`))})})]}),s.perModel.length>0&&e.jsxs(i,{children:[e.jsx(u,{children:e.jsx(j,{children:"Health by Model"})}),e.jsx(h,{children:e.jsxs("table",{className:"w-full border-collapse text-sm",children:[e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{className:"border-b border-border px-3 py-2 text-left font-medium text-muted-foreground",children:"Model"}),e.jsx("th",{className:"border-b border-border px-3 py-2 text-left font-medium text-muted-foreground",children:"Avg Health"}),e.jsx("th",{className:"border-b border-border px-3 py-2 text-left font-medium text-muted-foreground",children:"Samples"})]})}),e.jsx("tbody",{children:s.perModel.map(t=>e.jsxs("tr",{className:"border-b border-border/50",children:[e.jsx("td",{className:"px-3 py-2",children:t.modelId}),e.jsxs("td",{className:"px-3 py-2",style:{color:m(t.avgComposite)},children:[(t.avgComposite*100).toFixed(0),"%"]}),e.jsx("td",{className:"px-3 py-2",children:t.sampleCount})]},t.modelId))})]})})]})]})}export{H as default};
2
- //# sourceMappingURL=HealthTab-D4eVI8ar.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"HealthTab-D4eVI8ar.js","sources":["../../src/tabs/HealthTab.tsx"],"sourcesContent":["/**\n * HealthTab.tsx — Context Health dashboard tab.\n * Shows composite health gauge, sparkline trend, sub-score bars,\n * alerts, and per-model comparison. Polls /api/context-health every 5s.\n */\n\nimport { useState, useEffect, useCallback } from \"react\";\nimport { fetchContextHealth, type ContextHealthResponse } from \"../api/health\";\nimport { Card, CardContent, CardHeader, CardTitle } from \"../components/ui/card\";\nimport { Badge } from \"../components/ui/badge\";\n\nfunction healthColor(score: number): string {\n\tif (score > 0.8) return \"#4ade80\";\n\tif (score >= 0.5) return \"#facc15\";\n\treturn \"#f87171\";\n}\n\nfunction HealthGauge({ score }: { score: number }): React.ReactElement {\n\tconst r = 70;\n\tconst circ = 2 * Math.PI * r;\n\tconst offset = circ * (1 - Math.max(0, Math.min(1, score)));\n\tconst color = healthColor(score);\n\treturn (\n\t\t<div className=\"health-gauge-wrap\">\n\t\t\t<svg width=\"180\" height=\"180\" viewBox=\"0 0 180 180\">\n\t\t\t\t<circle cx=\"90\" cy=\"90\" r={r} fill=\"none\" stroke=\"#1e293b\" strokeWidth=\"14\" />\n\t\t\t\t<circle\n\t\t\t\t\tcx=\"90\"\n\t\t\t\t\tcy=\"90\"\n\t\t\t\t\tr={r}\n\t\t\t\t\tfill=\"none\"\n\t\t\t\t\tstroke={color}\n\t\t\t\t\tstrokeWidth=\"14\"\n\t\t\t\t\tstrokeDasharray={circ}\n\t\t\t\t\tstrokeDashoffset={offset}\n\t\t\t\t\tstrokeLinecap=\"round\"\n\t\t\t\t\ttransform=\"rotate(-90 90 90)\"\n\t\t\t\t\tstyle={{ transition: \"stroke-dashoffset 0.5s ease\" }}\n\t\t\t\t/>\n\t\t\t\t<text x=\"90\" y=\"85\" textAnchor=\"middle\" fontSize=\"32\" fontWeight=\"bold\" fill={color}>\n\t\t\t\t\t{(score * 100).toFixed(0)}\n\t\t\t\t</text>\n\t\t\t\t<text x=\"90\" y=\"108\" textAnchor=\"middle\" fontSize=\"14\" fill=\"#94a3b8\">/ 100</text>\n\t\t\t</svg>\n\t\t\t<div className=\"health-gauge-label\">Context Health</div>\n\t\t</div>\n\t);\n}\n\nfunction HealthSparkline({ trend }: { trend: number[] }): React.ReactElement {\n\tif (trend.length < 2) return <div className=\"health-spark-empty\">No trend data yet</div>;\n\tconst w = 400;\n\tconst h = 60;\n\tconst max = 1;\n\tconst min = 0;\n\tconst pts = trend.map((v, i) => {\n\t\tconst x = (i / (trend.length - 1)) * w;\n\t\tconst y = h - ((v - min) / (max - min)) * h;\n\t\treturn `${x.toFixed(1)},${y.toFixed(1)}`;\n\t});\n\tconst last = trend[trend.length - 1];\n\treturn (\n\t\t<svg width={w} height={h} viewBox={`0 0 ${w} ${h}`} className=\"health-spark\">\n\t\t\t<polyline points={pts.join(\" \")} fill=\"none\" stroke={healthColor(last)} strokeWidth=\"2\" />\n\t\t</svg>\n\t);\n}\n\nfunction SubScoreBar({ label, value }: { label: string; value: number }): React.ReactElement {\n\treturn (\n\t\t<div className=\"health-subscore\">\n\t\t\t<span className=\"health-subscore-label\">{label}</span>\n\t\t\t<div className=\"health-subscore-bar\">\n\t\t\t\t<div\n\t\t\t\t\tclassName=\"health-subscore-fill\"\n\t\t\t\t\tstyle={{ width: `${value * 100}%`, background: healthColor(value) }}\n\t\t\t\t/>\n\t\t\t</div>\n\t\t\t<span className=\"health-subscore-val\">{(value * 100).toFixed(0)}%</span>\n\t\t</div>\n\t);\n}\n\nexport default function HealthTab(): React.ReactElement {\n\tconst [data, setData] = useState<ContextHealthResponse | null>(null);\n\tconst [err, setError] = useState<string | null>(null);\n\tconst [loading, setLoading] = useState(true);\n\n\tconst poll = useCallback(() => {\n\t\tfetchContextHealth()\n\t\t\t.then(setData)\n\t\t\t.catch((e: unknown) => setError(e instanceof Error ? e.message : String(e)))\n\t\t\t.finally(() => setLoading(false));\n\t}, []);\n\n\tuseEffect(() => {\n\t\tpoll();\n\t\tconst id = setInterval(poll, 5000);\n\t\treturn () => clearInterval(id);\n\t}, [poll]);\n\n\tif (loading && !data) return <div className=\"health-loading\">Loading health data…</div>;\n\tif (err && !data) return <div className=\"health-error\">Error: {err}</div>;\n\tif (!data) return <div className=\"health-empty\">No health data available.</div>;\n\n\tconst latest = data.latest;\n\tconst composite = latest?.composite ?? 0;\n\n\treturn (\n\t\t<div className=\"flex flex-col gap-4\">\n\t\t\t<div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n\t\t\t\t<Card>\n\t\t\t\t\t<CardContent className=\"flex justify-center\">\n\t\t\t\t\t\t<HealthGauge score={composite} />\n\t\t\t\t\t</CardContent>\n\t\t\t\t</Card>\n\t\t\t\t<Card>\n\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t<CardTitle>Sub-Scores</CardTitle>\n\t\t\t\t\t</CardHeader>\n\t\t\t\t\t<CardContent>\n\t\t\t\t\t\t<SubScoreBar label=\"Drift\" value={latest?.driftScore ?? 0} />\n\t\t\t\t\t\t<SubScoreBar label=\"Output Quality\" value={latest?.outputQuality ?? 0} />\n\t\t\t\t\t\t<SubScoreBar label=\"Error Rate\" value={latest?.errorScore ?? 0} />\n\t\t\t\t\t\t<SubScoreBar label=\"Cache Health\" value={latest?.cacheHealth ?? 0} />\n\t\t\t\t\t\t<SubScoreBar label=\"Cache Poison\" value={latest?.cachePoison ?? 0} />\n\t\t\t\t\t</CardContent>\n\t\t\t\t</Card>\n\t\t\t</div>\n\n\t\t\t<Card>\n\t\t\t\t<CardHeader>\n\t\t\t\t\t<CardTitle>Health Trend (last {data.trend.length} turns)</CardTitle>\n\t\t\t\t</CardHeader>\n\t\t\t\t<CardContent>\n\t\t\t\t\t<HealthSparkline trend={data.trend} />\n\t\t\t\t</CardContent>\n\t\t\t</Card>\n\n\t\t\t{data.alerts.length > 0 && (\n\t\t\t\t<Card>\n\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t<CardTitle>Recent Alerts ({data.alerts.length})</CardTitle>\n\t\t\t\t\t</CardHeader>\n\t\t\t\t\t<CardContent>\n\t\t\t\t\t\t<div className=\"flex flex-col gap-1\">\n\t\t\t\t\t\t\t{data.alerts.slice(-10).map((a) => (\n\t\t\t\t\t\t\t\t<div\n\t\t\t\t\t\t\t\t\tkey={`${a.ts}-${a.turnIndex}`}\n\t\t\t\t\t\t\t\t\tclassName=\"flex items-center gap-3 border-b border-border/50 py-1 text-sm\"\n\t\t\t\t\t\t\t\t>\n\t\t\t\t\t\t\t\t\t<span className=\"text-xs text-muted-foreground\">{new Date(a.ts).toLocaleTimeString()}</span>\n\t\t\t\t\t\t\t\t\t<span className=\"text-muted-foreground\">{a.modelId ?? \"(unknown)\"}</span>\n\t\t\t\t\t\t\t\t\t<span style={{ color: healthColor(a.composite) }}>\n\t\t\t\t\t\t\t\t\t\t{(a.composite * 100).toFixed(0)}\n\t\t\t\t\t\t\t\t\t</span>\n\t\t\t\t\t\t\t\t\t{a.cachePoison < 0.3 && <Badge variant=\"danger\">CACHE POISON</Badge>}\n\t\t\t\t\t\t\t\t</div>\n\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t</div>\n\t\t\t\t\t</CardContent>\n\t\t\t\t</Card>\n\t\t\t)}\n\n\t\t\t{data.perModel.length > 0 && (\n\t\t\t\t<Card>\n\t\t\t\t\t<CardHeader>\n\t\t\t\t\t\t<CardTitle>Health by Model</CardTitle>\n\t\t\t\t\t</CardHeader>\n\t\t\t\t\t<CardContent>\n\t\t\t\t\t\t<table className=\"w-full border-collapse text-sm\">\n\t\t\t\t\t\t\t<thead>\n\t\t\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t\t\t<th className=\"border-b border-border px-3 py-2 text-left font-medium text-muted-foreground\">Model</th>\n\t\t\t\t\t\t\t\t\t<th className=\"border-b border-border px-3 py-2 text-left font-medium text-muted-foreground\">Avg Health</th>\n\t\t\t\t\t\t\t\t\t<th className=\"border-b border-border px-3 py-2 text-left font-medium text-muted-foreground\">Samples</th>\n\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t</thead>\n\t\t\t\t\t\t\t<tbody>\n\t\t\t\t\t\t\t\t{data.perModel.map((m) => (\n\t\t\t\t\t\t\t\t\t<tr key={m.modelId} className=\"border-b border-border/50\">\n\t\t\t\t\t\t\t\t\t\t<td className=\"px-3 py-2\">{m.modelId}</td>\n\t\t\t\t\t\t\t\t\t\t<td className=\"px-3 py-2\" style={{ color: healthColor(m.avgComposite) }}>\n\t\t\t\t\t\t\t\t\t\t\t{(m.avgComposite * 100).toFixed(0)}%\n\t\t\t\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t\t\t\t<td className=\"px-3 py-2\">{m.sampleCount}</td>\n\t\t\t\t\t\t\t\t\t</tr>\n\t\t\t\t\t\t\t\t))}\n\t\t\t\t\t\t\t</tbody>\n\t\t\t\t\t\t</table>\n\t\t\t\t\t</CardContent>\n\t\t\t\t</Card>\n\t\t\t)}\n\t\t</div>\n\t);\n}\n"],"names":["healthColor","score","HealthGauge","circ","offset","color","jsxs","jsx","HealthSparkline","trend","w","h","max","min","pts","v","i","x","y","last","SubScoreBar","label","value","HealthTab","data","setData","useState","err","setError","loading","setLoading","poll","useCallback","fetchContextHealth","e","useEffect","id","latest","composite","Card","CardContent","CardHeader","CardTitle","a","Badge","m"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- import{j as r}from"./index-iuxgtzEW.js";import{M as t}from"./MetricsCards-VUEcgGzo.js";import"./card-Cfhz9X31.js";import"./generateCategoricalChart-JKjK9_cB.js";import"./BarChart-C-M0LaFc.js";import"./AreaChart-BRHEfWhc.js";function e(){return r.jsx(t,{})}export{e as default};
2
- //# sourceMappingURL=MetricsTab-CKqJ0Zl-.js.map
@@ -1,2 +0,0 @@
1
- import{j as r}from"./index-iuxgtzEW.js";import{T as o}from"./TurnMemoryView-5tLbWkRv.js";import"./card-Cfhz9X31.js";import"./button-DhQQAJGm.js";function e(){return r.jsx(o,{})}export{e as default};
2
- //# sourceMappingURL=TurnsTab-C7pm92Hg.js.map