| 1 | // Content-free, bounded traces survive component unmounts. Keys are canonical |
| 2 | // session routes; observations explicitly identify their renderer lifetime. |
| 3 | const startedAt = new Date().toISOString(); |
| 4 | type Observation = { at: string; action: string; tabId: string; generation: number; sequence: number; status?: string; |
| 5 | prompt?: { id?: string; turnId?: string; runtimeEpoch?: string; kind?: string; bindingGeneration: number | null } }; |
| 6 | const traces = new Map<string, { events: Observation[]; dropped: number }>(); |
| 7 | export function noteSessionObservation(path: string, value: Omit<Observation, "at">): void { |
| 8 | const key = JSON.stringify([value.tabId, path]); |
| 9 | const trace = traces.get(key) ?? { events: [], dropped: 0 }; |
| 10 | if (trace.events.length === 256) { trace.events.shift(); trace.dropped++; } |
| 11 | trace.events.push({ ...value, at: new Date().toISOString() }); |
| 12 | traces.delete(key); traces.set(key, trace); |
| 13 | while (traces.size > 64) traces.delete(traces.keys().next().value!); |
| 14 | } |
| 15 | export function sessionObservationDiagnostics(path: string, tabId: string) { |
| 16 | const trace = traces.get(JSON.stringify([tabId, path])); |
| 17 | return { processStartedAt: startedAt, capturedAt: new Date().toISOString(), events: trace?.events ?? [], dropped: trace?.dropped ?? 0, |
| 18 | unavailable: ["Observations before this renderer started or after trace eviction are unavailable."] }; |
| 19 | } |
| 20 |