// The run's distributed-trace ledger (traces.json): every request the // session made against the target, with the trace id that names its // click→server→DB waterfall in the OTLP store the run exported to. // // Two writers share it — the browser surface (ids harvested off the wire, // the web app sends traceparent itself) and the MCP surface (ids MINTED // here, since mcporter's plain fetch sends none; the server joins whatever // traceparent arrives). Append is read-merge-write so neither clobbers the // other; entries stay sorted by wall clock. import { existsSync, readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; export interface TraceEntry { readonly id: string; readonly at: number; readonly url: string; readonly ms?: number; readonly status?: number; /** Which window made the request — the viewer's rail tags rows with it. */ readonly source?: "terminal" | "browser"; /** Readable name when the URL alone says nothing (MCP: every call POSTs * the same endpoint; the JSON-RPC method/tool is the real identity). */ readonly label?: string; } const fileFor = (runDir: string) => join(runDir, "traces.json"); export const appendTraces = (runDir: string, entries: ReadonlyArray): void => { if (entries.length === 0) return; const file = fileFor(runDir); const existing: TraceEntry[] = existsSync(file) ? (JSON.parse(readFileSync(file, "utf8")) as TraceEntry[]) : []; const merged = [...existing, ...entries].sort((a, b) => a.at - b.at); writeFileSync(file, JSON.stringify(merged, null, 1)); };