import { randomUUID } from "node:crypto"; const DEFAULT_MAX_TRACES = 256; const DEFAULT_MAX_EVENTS = 6000; const TEXT_LIMIT = 1200; const SECRET_PATTERN = /\b(?:sk-[A-Za-z0-9_-]+|OPENAI_API_KEY|DATABASE_URL|secretRef:[^\s,;]+|secret|token|password|passwd|credential|private[_ -]?key|kubeconfig|BEGIN [A-Z ]*PRIVATE KEY)\b/giu; export function createCodeAgentTraceStore(options = {}) { const maxTraces = positiveInteger(options.maxTraces, DEFAULT_MAX_TRACES); const maxEvents = positiveInteger(options.maxEvents, DEFAULT_MAX_EVENTS); const traces = new Map(); function ensure(traceId, meta = {}) { const id = cleanTraceId(traceId) || `trc_${randomUUID()}`; let trace = traces.get(id); if (!trace) { const timestamp = timestampFor(meta.now); trace = { traceId: id, status: "running", createdAt: timestamp, updatedAt: timestamp, startedAt: timestamp, finishedAt: null, nextSeq: 1, events: [], assistantStreams: new Map(), listeners: new Set(), meta: { runnerKind: meta.runnerKind ?? null, workspace: meta.workspace ?? null, sandbox: meta.sandbox ?? null, sessionMode: meta.sessionMode ?? null, implementationType: meta.implementationType ?? null } }; traces.set(id, trace); prune(); } else { trace.meta = { ...trace.meta, ...dropEmpty({ runnerKind: meta.runnerKind, workspace: meta.workspace, sandbox: meta.sandbox, sessionMode: meta.sessionMode, implementationType: meta.implementationType }) }; } return trace; } function append(traceId, event = {}, meta = {}) { const trace = ensure(traceId, meta); const normalized = normalizeTraceEvent(event, { traceId: trace.traceId, seq: trace.nextSeq, now: meta.now, fallbackRunnerKind: trace.meta.runnerKind }); trace.nextSeq += 1; trace.events.push(normalized); if (trace.events.length > maxEvents) { trace.events.splice(0, trace.events.length - maxEvents); } trace.updatedAt = normalized.createdAt; if (normalized.terminal === true) { trace.status = normalized.status === "completed" ? "completed" : normalized.status === "canceled" ? "canceled" : normalized.type; trace.finishedAt = normalized.createdAt; } else if (normalized.status === "failed" || normalized.type === "error" || normalized.type === "timeout") { trace.status = normalized.type; trace.finishedAt = normalized.createdAt; } notify(trace, normalized); return normalized; } function appendAssistantDelta(traceId, delta = {}, meta = {}) { const trace = ensure(traceId, meta); const normalized = normalizeAssistantDelta(delta, { traceId: trace.traceId, now: meta.now, fallbackRunnerKind: trace.meta.runnerKind }); if (!normalized) return null; const key = normalized.itemId || "assistant-message"; const previous = trace.assistantStreams.get(key) ?? { traceId: trace.traceId, itemId: key, type: "assistant_message_stream", status: "streaming", label: "assistant:stream", createdAt: normalized.createdAt, updatedAt: normalized.createdAt, runnerKind: normalized.runnerKind, sessionId: normalized.sessionId, sessionStatus: normalized.sessionStatus, turn: normalized.turn, threadId: normalized.threadId, turnId: normalized.turnId, waitingFor: normalized.waitingFor, chunkCount: 0, text: "", lastChunk: "", valuesPrinted: false }; const text = `${previous.text ?? ""}${normalized.chunk}`; const stream = dropUndefined({ ...previous, updatedAt: normalized.createdAt, runnerKind: normalized.runnerKind ?? previous.runnerKind, sessionId: normalized.sessionId ?? previous.sessionId, sessionStatus: normalized.sessionStatus ?? previous.sessionStatus, turn: normalized.turn ?? previous.turn, threadId: normalized.threadId ?? previous.threadId, turnId: normalized.turnId ?? previous.turnId, waitingFor: normalized.waitingFor ?? previous.waitingFor, chunkCount: previous.chunkCount + 1, text, lastChunk: normalized.chunk, valuesPrinted: false }); trace.assistantStreams.set(key, stream); trace.updatedAt = normalized.createdAt; notify(trace, null); return stream; } function snapshot(traceId, extra = {}) { const trace = traces.get(cleanTraceId(traceId)); if (!trace) return emptySnapshot(traceId, extra); const events = trace.events.map((event) => ({ ...event })); const assistantStreams = assistantStreamsSnapshot(trace); const lastEvent = events.at(-1) ?? null; return { traceId: trace.traceId, status: trace.status, createdAt: trace.createdAt, updatedAt: trace.updatedAt, startedAt: trace.startedAt, finishedAt: trace.finishedAt, eventCount: events.length, events, assistantStreams, eventLabels: events.map((event) => event.label).filter(Boolean), lastEvent, elapsedMs: elapsedMs(trace.startedAt, trace.finishedAt ?? trace.updatedAt), waitingFor: snapshotWaitingFor(trace, events, assistantStreams, lastEvent), runnerKind: extra.runnerKind ?? trace.meta.runnerKind ?? null, workspace: extra.workspace ?? trace.meta.workspace ?? null, sandbox: extra.sandbox ?? trace.meta.sandbox ?? null, sessionMode: extra.sessionMode ?? trace.meta.sessionMode ?? null, implementationType: extra.implementationType ?? trace.meta.implementationType ?? null, sessionId: extra.sessionId ?? lastEvent?.sessionId ?? null, sessionStatus: extra.sessionStatus ?? lastEvent?.sessionStatus ?? null, sessionLifecycleStatus: extra.sessionLifecycleStatus ?? lastEvent?.sessionLifecycleStatus ?? null, turn: extra.turn ?? lastEvent?.turn ?? null, outputTruncated: events.some((event) => event.outputTruncated === true), valuesPrinted: false }; } function subscribe(traceId, listener, meta = {}) { const trace = ensure(traceId, meta); trace.listeners.add(listener); return () => { trace.listeners.delete(listener); }; } function clear() { traces.clear(); } function prune() { if (traces.size <= maxTraces) return; const stale = [...traces.values()] .sort((left, right) => String(left.updatedAt).localeCompare(String(right.updatedAt))) .slice(0, traces.size - maxTraces); for (const trace of stale) { traces.delete(trace.traceId); } } return { ensure, append, appendAssistantDelta, snapshot, subscribe, clear }; } export const defaultCodeAgentTraceStore = createCodeAgentTraceStore(); export function createCodeAgentTraceRecorder({ traceStore = defaultCodeAgentTraceStore, traceId, now, runnerKind = null, workspace = null, sandbox = null, sessionMode = null, implementationType = null } = {}) { const startedAt = timestampFor(now); const startedEpochMs = Date.now(); const baseMeta = { now, runnerKind, workspace, sandbox, sessionMode, implementationType }; traceStore.ensure(traceId, baseMeta); function append(event = {}) { return traceStore.append(traceId, { elapsedMs: Date.now() - startedEpochMs, ...event }, baseMeta); } function appendAssistantDelta(delta = {}) { return traceStore.appendAssistantDelta(traceId, delta, baseMeta); } function snapshot(extra = {}) { return traceStore.snapshot(traceId, { runnerKind, workspace, sandbox, sessionMode, implementationType, ...extra }); } function runnerTrace(extra = {}) { const current = snapshot(extra); return { traceId: current.traceId, runnerKind: extra.runnerKind ?? current.runnerKind, workspace: extra.workspace ?? current.workspace, sandbox: extra.sandbox ?? current.sandbox, sessionMode: extra.sessionMode ?? current.sessionMode, sessionId: extra.sessionId ?? current.sessionId, sessionStatus: extra.sessionStatus ?? current.sessionStatus, sessionLifecycleStatus: extra.sessionLifecycleStatus ?? current.sessionLifecycleStatus, idleTimeoutMs: extra.idleTimeoutMs ?? null, lastTraceId: extra.lastTraceId ?? current.traceId, turn: extra.turn ?? current.turn, sessionReused: extra.sessionReused ?? false, implementationType: extra.implementationType ?? current.implementationType, limitations: Array.isArray(extra.limitations) ? extra.limitations : [], startedAt: extra.startedAt ?? startedAt, finishedAt: extra.finishedAt ?? current.finishedAt ?? timestampFor(now), updatedAt: current.updatedAt, events: current.events, assistantStreams: current.assistantStreams, eventLabels: current.eventLabels, lastEvent: current.lastEvent, elapsedMs: current.elapsedMs, waitingFor: current.waitingFor, outputTruncated: current.outputTruncated || extra.outputTruncated === true, valuesPrinted: false, note: extra.note ?? "Real-time runnerTrace is appended as Codex stdio/session events are captured." }; } return { traceId, append, appendAssistantDelta, snapshot, runnerTrace }; } export function runnerTraceFromSnapshot(snapshot = {}, extra = {}) { return { traceId: snapshot.traceId ?? extra.traceId ?? null, runnerKind: extra.runnerKind ?? snapshot.runnerKind ?? null, workspace: extra.workspace ?? snapshot.workspace ?? null, sandbox: extra.sandbox ?? snapshot.sandbox ?? null, sessionMode: extra.sessionMode ?? snapshot.sessionMode ?? null, sessionId: extra.sessionId ?? snapshot.sessionId ?? null, sessionStatus: extra.sessionStatus ?? snapshot.sessionStatus ?? null, sessionLifecycleStatus: extra.sessionLifecycleStatus ?? snapshot.sessionLifecycleStatus ?? null, idleTimeoutMs: extra.idleTimeoutMs ?? null, lastTraceId: extra.lastTraceId ?? snapshot.traceId ?? null, turn: extra.turn ?? snapshot.turn ?? null, sessionReused: extra.sessionReused ?? false, implementationType: extra.implementationType ?? snapshot.implementationType ?? null, limitations: Array.isArray(extra.limitations) ? extra.limitations : [], startedAt: extra.startedAt ?? snapshot.startedAt ?? null, finishedAt: extra.finishedAt ?? snapshot.finishedAt ?? snapshot.updatedAt ?? null, updatedAt: snapshot.updatedAt ?? null, events: Array.isArray(snapshot.events) ? snapshot.events : [], assistantStreams: Array.isArray(snapshot.assistantStreams) ? snapshot.assistantStreams : [], eventLabels: Array.isArray(snapshot.eventLabels) ? snapshot.eventLabels : [], lastEvent: snapshot.lastEvent ?? null, elapsedMs: snapshot.elapsedMs ?? null, waitingFor: snapshot.waitingFor ?? null, outputTruncated: snapshot.outputTruncated === true || extra.outputTruncated === true, valuesPrinted: false, note: extra.note ?? "runnerTrace snapshot" }; } function normalizeTraceEvent(event, { traceId, seq, now, fallbackRunnerKind } = {}) { const type = safeToken(event.type ?? event.kind ?? "event"); const status = safeToken(event.status ?? "observed"); const toolName = safeText(event.toolName ?? event.name, 120); const label = safeText(event.label, 180) || labelFor({ type, status, toolName }); return dropUndefined({ seq, traceId, type, stage: safeToken(event.stage ?? type), status, label, createdAt: timestampFor(now), elapsedMs: typeof event.elapsedMs === "number" ? Math.max(0, Math.trunc(event.elapsedMs)) : null, runnerKind: safeText(event.runnerKind ?? fallbackRunnerKind, 140), source: safeText(event.source, 120), sourceSeq: Number.isInteger(event.sourceSeq) ? event.sourceSeq : undefined, runId: safeText(event.runId, 180), commandId: safeText(event.commandId, 180), attemptId: safeText(event.attemptId, 180), runnerId: safeText(event.runnerId, 180), jobName: safeText(event.jobName, 180), namespace: safeText(event.namespace, 180), sessionId: safeText(event.sessionId, 180), sessionStatus: safeText(event.sessionStatus, 80), sessionLifecycleStatus: safeText(event.sessionLifecycleStatus, 80), sessionReused: typeof event.sessionReused === "boolean" ? event.sessionReused : undefined, turn: typeof event.turn === "number" ? event.turn : undefined, toolName, itemId: safeText(event.itemId, 180), command: safeText(event.command, 900), exitCode: Number.isInteger(event.exitCode) ? event.exitCode : undefined, durationMs: typeof event.durationMs === "number" ? Math.max(0, Math.trunc(event.durationMs)) : undefined, outputBytes: typeof event.outputBytes === "number" ? Math.max(0, Math.trunc(event.outputBytes)) : undefined, promptSummary: safeText(event.promptSummary, 240), outputSummary: safeText(event.outputSummary, 400), stdoutSummary: safeText(event.stdoutSummary, 600), stderrSummary: safeText(event.stderrSummary, 600), chunk: safeText(event.chunk, 400), message: safeText(event.message, 1200), errorCode: safeText(event.errorCode, 120), diagnosisCode: safeText(event.diagnosisCode ?? event.diagnosis?.code, 160), diagnosis: normalizeDiagnosis(event.diagnosis), waitingFor: safeText(event.waitingFor, 220), timeoutMs: typeof event.timeoutMs === "number" ? Math.trunc(event.timeoutMs) : undefined, hardTimeoutMs: typeof event.hardTimeoutMs === "number" ? Math.trunc(event.hardTimeoutMs) : undefined, lastActivityAt: safeText(event.lastActivityAt, 80), idleMs: typeof event.idleMs === "number" ? Math.max(0, Math.trunc(event.idleMs)) : undefined, outputTruncated: event.outputTruncated === true, terminal: event.terminal === true, valuesPrinted: false }); } function normalizeAssistantDelta(delta, { traceId, now, fallbackRunnerKind } = {}) { const chunk = safeText(delta.chunk ?? delta.delta, 400); if (!chunk) return null; return dropUndefined({ traceId, itemId: safeText(delta.itemId, 180), chunk, createdAt: timestampFor(now), runnerKind: safeText(delta.runnerKind ?? fallbackRunnerKind, 140), sessionId: safeText(delta.sessionId, 180), sessionStatus: safeText(delta.sessionStatus, 80), turn: typeof delta.turn === "number" ? delta.turn : undefined, threadId: safeText(delta.threadId, 180), turnId: safeText(delta.turnId, 180), waitingFor: safeText(delta.waitingFor, 220), valuesPrinted: false }); } function assistantStreamsSnapshot(trace) { if (!(trace?.assistantStreams instanceof Map)) return []; return [...trace.assistantStreams.values()].map((stream) => ({ ...stream })); } function labelFor({ type, status, toolName }) { if (type === "session") return `session:${status}`; if (type === "prompt") return `prompt:${status}`; if (type === "tool_call") return `tool:${toolName || "codex"}:${status}`; if (type === "assistant_message") return `assistant:${status}`; if (["timeout", "cancel", "error"].includes(type)) return type; if (type === "request") return `request:${status}`; if (type === "stdio") return `stdio:${status}`; return `${type}:${status}`; } function notify(trace, event) { const assistantStreams = assistantStreamsSnapshot(trace); const snapshot = { ...emptySnapshot(trace.traceId), ...{ traceId: trace.traceId, status: trace.status, createdAt: trace.createdAt, updatedAt: trace.updatedAt, startedAt: trace.startedAt, finishedAt: trace.finishedAt, eventCount: trace.events.length, events: trace.events.map((item) => ({ ...item })), assistantStreams, eventLabels: trace.events.map((item) => item.label).filter(Boolean), lastEvent: event ?? trace.events.at(-1) ?? null, elapsedMs: elapsedMs(trace.startedAt, trace.finishedAt ?? trace.updatedAt), waitingFor: snapshotWaitingFor(trace, trace.events, assistantStreams, event ?? trace.events.at(-1) ?? null), runnerKind: trace.meta.runnerKind, workspace: trace.meta.workspace, sandbox: trace.meta.sandbox, sessionMode: trace.meta.sessionMode, implementationType: trace.meta.implementationType, sessionId: event?.sessionId ?? assistantStreams.at(-1)?.sessionId ?? null, sessionStatus: event?.sessionStatus ?? assistantStreams.at(-1)?.sessionStatus ?? null, sessionLifecycleStatus: event?.sessionLifecycleStatus ?? null, outputTruncated: trace.events.some((item) => item.outputTruncated === true), valuesPrinted: false } }; for (const listener of trace.listeners) { try { listener(event, snapshot); } catch { trace.listeners.delete(listener); } } } function emptySnapshot(traceId, extra = {}) { const id = cleanTraceId(traceId); return { traceId: id, status: "missing", createdAt: null, updatedAt: null, startedAt: null, finishedAt: null, eventCount: 0, events: [], assistantStreams: [], eventLabels: [], lastEvent: null, elapsedMs: null, waitingFor: null, runnerKind: extra.runnerKind ?? null, workspace: extra.workspace ?? null, sandbox: extra.sandbox ?? null, sessionMode: extra.sessionMode ?? null, implementationType: extra.implementationType ?? null, sessionId: null, sessionStatus: null, sessionLifecycleStatus: null, turn: null, outputTruncated: false, valuesPrinted: false }; } function lastWaitingFor(events) { for (const event of [...events].reverse()) { if (event.waitingFor) return event.waitingFor; if (event.type === "tool_call" && ["started", "output_chunk"].includes(event.status)) { return `tool:${event.toolName ?? "codex"}`; } if (event.type === "prompt" && event.status === "sent") return "assistant-message"; } return null; } function snapshotWaitingFor(trace, events, assistantStreams, lastEvent) { if (lastEvent?.terminal === true) { return lastEvent.waitingFor ?? null; } if (trace?.finishedAt) return null; return lastWaitingFor(events) ?? assistantStreams.at(-1)?.waitingFor ?? null; } function elapsedMs(start, end) { const startMs = Date.parse(start ?? ""); const endMs = Date.parse(end ?? ""); if (!Number.isFinite(startMs) || !Number.isFinite(endMs)) return null; return Math.max(0, endMs - startMs); } function normalizeDiagnosis(value) { if (!value || typeof value !== "object" || Array.isArray(value)) return undefined; const lastRoutedEvent = value.lastRoutedEvent && typeof value.lastRoutedEvent === "object" && !Array.isArray(value.lastRoutedEvent) ? dropUndefined({ type: safeText(value.lastRoutedEvent.type, 80), status: safeText(value.lastRoutedEvent.status, 80), label: safeText(value.lastRoutedEvent.label, 180), stage: safeText(value.lastRoutedEvent.stage, 80), toolName: safeText(value.lastRoutedEvent.toolName, 120), itemId: safeText(value.lastRoutedEvent.itemId, 180), waitingFor: safeText(value.lastRoutedEvent.waitingFor, 220), exitCode: Number.isInteger(value.lastRoutedEvent.exitCode) ? value.lastRoutedEvent.exitCode : undefined, durationMs: typeof value.lastRoutedEvent.durationMs === "number" ? Math.max(0, Math.trunc(value.lastRoutedEvent.durationMs)) : undefined }) : undefined; return dropUndefined({ code: safeText(value.code, 160), layer: safeText(value.layer, 120), kind: safeText(value.kind, 120), summary: safeText(value.summary, 500), waitingFor: safeText(value.waitingFor, 220), lastActivityAt: safeText(value.lastActivityAt, 80), idleMs: typeof value.idleMs === "number" ? Math.max(0, Math.trunc(value.idleMs)) : undefined, lastActivityLabel: safeText(value.lastActivityLabel, 180), threadId: safeText(value.threadId, 180), turnId: safeText(value.turnId, 180), lastRoutedEvent }); } function safeText(value, limit = TEXT_LIMIT) { if (value === undefined || value === null || value === "") return undefined; const redacted = String(value).replace(SECRET_PATTERN, "[redacted]").replace(/\s+/gu, " ").trim(); if (!redacted) return undefined; return redacted.length > limit ? `${redacted.slice(0, limit - 3)}...` : redacted; } function safeToken(value) { return String(value ?? "") .trim() .replace(/[^A-Za-z0-9_.:-]/gu, "_") .slice(0, 80) || "event"; } function cleanTraceId(value) { const id = String(value ?? "").trim(); return /^trc_[A-Za-z0-9_.:-]+$/u.test(id) ? id : null; } function timestampFor(now) { const value = typeof now === "function" ? now() : now; const date = value ? new Date(value) : new Date(); return Number.isNaN(date.getTime()) ? new Date().toISOString() : date.toISOString(); } function positiveInteger(value, fallback) { const parsed = Number.parseInt(value ?? "", 10); return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback; } function dropEmpty(value) { return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined && item !== null && item !== "")); } function dropUndefined(value) { return Object.fromEntries(Object.entries(value).filter(([, item]) => item !== undefined)); }