/* * SPEC: PJ2026-0104010803 Workbench唯一投影; HWLAB#2464 transactional Kafka projector. * Responsibility: atomically promote durable admission into initial nonterminal Workbench facts and build pure Kafka projection facts. * Local admission may create the first running session/message/turn snapshot; all later lifecycle and terminal authority comes only from commitAgentRunKafkaProjection. */ import { createHash } from "node:crypto"; import { safeTraceId } from "./server-http-utils.ts"; import { normalizeWorkbenchStatus, terminalFinalResponse, TERMINAL_STATUSES } from "./workbench-turn-projection.ts"; export async function recordWorkbenchSessionOwner({ accessController, traceId, ownerUserId, ownerRole = null, sessionId = null, projectId = null, conversationId = null, threadId = null, status = "active", session = {}, preserveLastTraceId = false } = {}) { if (!ownerUserId || typeof accessController?.recordAgentSessionOwner !== "function") return null; return accessController.recordAgentSessionOwner({ ownerUserId, ownerRole, sessionId, projectId, conversationId, threadId, traceId: preserveLastTraceId ? undefined : safeTraceId(traceId), status, session }); } export async function writeWorkbenchSessionAdmissionFact({ runtimeStore = null, session = null, ownerUserId = null, ownerRole = null, projectId = null, conversationId = null, threadId = null, status = "idle", now = new Date().toISOString() } = {}) { if (typeof runtimeStore?.writeWorkbenchSessionAdmissionFact !== "function") { throw workbenchProjectionError("workbench_admission_store_unconfigured", "Workbench admission requires the durable session admission store."); } const fact = buildWorkbenchSessionAdmissionFact({ session, ownerUserId, ownerRole, projectId, conversationId, threadId, status, now }); return runtimeStore.writeWorkbenchSessionAdmissionFact({ fact }, { sessionId: fact.sessionId, ownerUserId: fact.ownerUserId, projectId: fact.projectId, valuesPrinted: false }); } export async function promoteWorkbenchTurnAdmission({ runtimeStore = null, session = null, inputFact = null, payload = {}, params = {}, ownerUserId = null, ownerRole = null, projectId = null, conversationId = null, threadId = null, now = new Date().toISOString() } = {}) { if (typeof runtimeStore?.promoteWorkbenchTurnAdmission !== "function") { throw workbenchProjectionError("workbench_admission_promotion_store_unconfigured", "Workbench admission promotion requires the durable atomic promotion store."); } if (!inputFact || typeof inputFact !== "object") { throw workbenchProjectionError("workbench_admission_input_required", "Workbench admission promotion requires an input fact."); } const facts = buildWorkbenchAdmissionPromotionFacts({ session, inputFact, payload, params, ownerUserId, ownerRole, projectId, conversationId, threadId, now }); const sessionFact = facts.sessions[0]; return runtimeStore.promoteWorkbenchTurnAdmission({ facts }, { sessionId: sessionFact.sessionId, traceId: inputFact.traceId ?? null, turnId: inputFact.turnId ?? null, messageId: inputFact.messageId ?? null, ownerUserId: sessionFact.ownerUserId, projectId: sessionFact.projectId, valuesPrinted: false }); } export function buildWorkbenchAdmissionPromotionFacts({ session = null, inputFact = null, payload = {}, params = {}, ownerUserId = null, ownerRole = null, projectId = null, conversationId = null, threadId = null, now = new Date().toISOString() } = {}) { const sessionFact = buildWorkbenchSessionAdmissionFact({ session, ownerUserId, ownerRole, projectId, conversationId, threadId, status: "running", now }); const traceId = safeTraceId(payload.traceId ?? params.traceId ?? inputFact?.traceId); if (!traceId) throw workbenchProjectionError("workbench_admission_trace_required", "Workbench admission promotion requires traceId."); const turnId = textValue(payload.turnId ?? params.turnId ?? inputFact?.turnId) || traceId; const userMessageId = textValue(payload.userMessageId ?? params.userMessageId ?? inputFact?.messageId) || eventProjectionUserMessageId(traceId, {}); const assistantMessageId = textValue(payload.assistantMessageId ?? params.assistantMessageId) || eventProjectionAssistantMessageId(traceId, {}); const sourceEventId = `${traceId}:admission-promotion`; const occurredAt = timestampValue(payload.createdAt ?? now); const timing = eventTimingProjection({ startedAt: occurredAt, lastEventAt: occurredAt, terminal: false }); const userText = textValue(params.message ?? params.prompt ?? params.text); const common = { sessionId: sessionFact.sessionId, turnId, traceId, projectedSeq: 0, sourceSeq: 0, sourceEventId, terminal: false, sealed: false, timing, startedAt: timing.startedAt, lastEventAt: timing.lastEventAt, finishedAt: null, durationMs: null, createdAt: occurredAt, updatedAt: occurredAt, valuesPrinted: false }; const userMessage = { ...common, messageId: userMessageId, role: "user", status: "sent", text: userText }; const assistantMessage = { ...common, messageId: assistantMessageId, role: "agent", status: "running", text: "" }; const turn = { ...common, messageId: assistantMessageId, status: "running", finalResponse: null, assistantText: "", failureKind: null, diagnostic: { authority: "local-admission-promotion", admissionState: "promoted", sourceRunId: textValue(payload.agentRun?.runId) || null, sourceCommandId: textValue(payload.agentRun?.commandId) || null, dispatchIntentId: textValue(payload.agentRun?.dispatchIntentId) || null, runnerJobId: textValue(payload.agentRun?.runnerJobId) || null, valuesRedacted: true } }; return { inputs: [{ ...inputFact, status: "promoted", commandId: textValue(payload.agentRun?.commandId ?? inputFact.commandId) || null, runId: textValue(payload.agentRun?.runId) || null, dispatchIntentId: textValue(payload.agentRun?.dispatchIntentId) || null, runnerJobId: textValue(payload.agentRun?.runnerJobId) || null, durableDispatch: payload.agentRun?.durableDispatch === true, updatedAt: occurredAt, valuesRedacted: true, secretMaterialStored: false }], sessions: [{ ...sessionFact, status: "running", lastTraceId: traceId, sessionJson: { ...sessionFact.sessionJson, admissionState: "promoted", agentRun: payload.agentRun ?? sessionFact.sessionJson?.agentRun ?? null, valuesRedacted: true, secretMaterialStored: false }, updatedAt: occurredAt }], messages: [userMessage, assistantMessage], parts: userText ? messagePartFacts(userMessage) : [], turns: [turn], traceEvents: [], checkpoints: [] }; } export function buildWorkbenchSessionAdmissionFact({ session = null, ownerUserId = null, ownerRole = null, projectId = null, conversationId = null, threadId = null, status = "idle", now = new Date().toISOString() } = {}) { if (!session || typeof session !== "object") { throw workbenchProjectionError("workbench_admission_session_required", "Workbench admission requires a session record."); } const sessionId = textValue(session.id ?? session.sessionId); if (!sessionId) { throw workbenchProjectionError("workbench_admission_session_required", "Workbench admission requires sessionId."); } const createdAt = timestampValue(session.startedAt ?? session.createdAt ?? session.session?.createdAt ?? now); const updatedAt = timestampValue(session.updatedAt ?? createdAt); const normalizedStatus = normalizeWorkbenchStatus(session.status ?? session.session?.sessionStatus ?? status) || "idle"; const sessionJson = workbenchSessionJsonWithLaunchContext(plainObjectValue(session.session) ?? {}, { payload: session }); return { sessionId, ownerUserId: textValue(session.ownerUserId ?? ownerUserId) || null, ownerRole: textValue(session.ownerRole ?? ownerRole) || null, projectId: textValue(session.projectId ?? projectId) || null, agentId: textValue(session.agentId) || "hwlab-code-agent", conversationId: textValue(session.conversationId ?? conversationId) || null, threadId: textValue(session.threadId ?? threadId) || null, status: normalizedStatus, lastTraceId: safeTraceId(session.lastTraceId ?? session.traceId) || null, projectedSeq: 0, sourceSeq: 0, sourceEventId: `${sessionId}:session-admission`, terminal: false, sealed: false, sessionJson: { ...sessionJson, sessionStatus: normalizedStatus, source: textValue(sessionJson.source ?? session.session?.source) || "session-admission", providerProfile: textValue(sessionJson.providerProfile ?? session.session?.providerProfile) || null, valuesRedacted: true, secretMaterialStored: false }, createdAt, updatedAt, valuesRedacted: true }; } export function buildWorkbenchProjectionEventFacts({ event = {}, requestMeta = {}, previousCheckpoint = null, projectedSeq, projectedAt = new Date().toISOString() } = {}) { const traceId = safeTraceId(event.traceId ?? requestMeta.traceId); if (!traceId) throw workbenchProjectionError("workbench_projection_trace_required", "Kafka projection event requires traceId."); const durableProjectedSeq = nonNegativeInteger(projectedSeq); if (durableProjectedSeq <= 0) throw workbenchProjectionError("workbench_projection_seq_required", "Kafka projection event requires an allocated projectedSeq."); const sourceSeq = nonNegativeInteger(event.sourceSeq ?? event.seq); const sessionId = textValue(event.sessionId ?? requestMeta.sessionId) || null; const turnId = textValue(event.turnId) || traceId; const eventType = textValue(event.eventType ?? event.type ?? event.label) || "event"; const sourceTerminal = event.terminal === true || TERMINAL_STATUSES.has(normalizeWorkbenchStatus(event.status)); const sourceStatus = sourceTerminal ? normalizeWorkbenchStatus(event.status) : "running"; const previousTiming = normalizeTimingProjection(previousCheckpoint?.timing) ?? normalizeTimingProjection(previousCheckpoint); const sourceOccurredAt = timestampValue(event.createdAt ?? event.occurredAt ?? event.updatedAt ?? projectedAt); const occurredAt = latestTimestamp(previousTiming?.lastEventAt, sourceOccurredAt) ?? sourceOccurredAt; if (checkpointIsTerminal(previousCheckpoint)) { return { written: false, suppressedAfterSeal: true, traceId, sessionId, sourceSeq, sourceEventId: workbenchSourceEventId(event, { traceId, sourceSeq, occurredAt }), projectedSeq: nonNegativeInteger(previousCheckpoint?.projectedSeq) || durableProjectedSeq, valuesPrinted: false }; } const explicitEventTiming = normalizeTimingProjection(event.timing) ?? normalizeTimingProjection(event); const explicitDurationMs = durationValue(event.durationMs ?? explicitEventTiming?.durationMs); const explicitStartedAt = optionalTimestampValue(event.startedAt ?? event.traceStartedAt ?? event.runnerStartedAt ?? explicitEventTiming?.startedAt); const provisionalLastEventAt = latestTimestamp(previousTiming?.lastEventAt, explicitEventTiming?.lastEventAt, occurredAt); const provisionalFinishedAt = sourceTerminal ? latestTimestamp(explicitEventTiming?.finishedAt, optionalTimestampValue(event.finishedAt), provisionalLastEventAt, occurredAt) : null; const startedAt = previousTiming?.startedAt ?? explicitStartedAt ?? startedAtFromDuration(provisionalFinishedAt, explicitDurationMs) ?? (sourceSeq <= 1 ? occurredAt : null); const lastEventAt = provisionalLastEventAt; const finishedAt = sourceTerminal ? latestTimestamp(provisionalFinishedAt, lastEventAt) : null; const sourceEventId = workbenchSourceEventId(event, { traceId, sourceSeq, occurredAt }); const eventId = textValue(event.id) || stableFactId("wte", { traceId, sourceEventId }); const previousFinalText = finalResponseTextValue(previousCheckpoint?.finalResponse, previousCheckpoint?.assistantText, previousCheckpoint?.finalText); const liveAssistantText = isAssistantMessageTraceEvent(event) ? finalResponseTextValue(event.assistantText, event.text, event.reply, event.message) : finalResponseTextValue(event.assistantText, event.reply); const eventTerminalFinalResponse = sourceTerminal ? terminalFinalResponse(sourceStatus, event, { evidence: event, finalResponse: event.finalResponse }) : null; const messageFinalText = sourceTerminal ? finalResponseTextValue(event.finalResponse, liveAssistantText, previousFinalText, eventTerminalFinalResponse) : null; const checkpointAssistantText = messageFinalText ?? liveAssistantText ?? previousFinalText ?? null; const checkpointFinalResponse = sourceTerminal && messageFinalText ? { ...(eventTerminalFinalResponse ?? {}), text: messageFinalText, status: sourceStatus, traceId, source: "agentrun-kafka-projector", valuesPrinted: false } : previousCheckpoint?.finalResponse ?? null; const terminalSealBlocked = sourceTerminal && sourceStatus === "completed" && !messageFinalText; const effectiveTerminal = sourceTerminal && !terminalSealBlocked; const effectiveStatus = effectiveTerminal ? sourceStatus : "running"; const timing = eventTimingProjection({ startedAt, lastEventAt, finishedAt, terminal: effectiveTerminal, durationMs: explicitDurationMs ?? previousTiming?.durationMs }); const timingAuthorityIssue = terminalTimingAuthorityIssue(timing, { terminal: effectiveTerminal, traceId, source: "kafka", status: effectiveStatus, label: event.label, sourceSeq }); const projectionDiagnostic = { timingAuthorityIssue, terminalSealBlocked, sourceTerminal, sourceStatus, authority: "agentrun-kafka-projector", valuesRedacted: true }; const userMessageFact = sessionId && previousCheckpoint?.userMessage ? normalizeMessageFact(previousCheckpoint.userMessage, 0, { traceId, sessionId, turnId, terminal: false, terminalStatus: "sent", finalText: null, timestamp: projectedAt, timing }) : null; const messageFact = sessionId ? normalizeMessageFact({ messageId: eventProjectionAssistantMessageId(traceId, event), role: "agent", status: effectiveStatus, traceId, turnId, text: messageFinalText ?? liveAssistantText ?? previousFinalText ?? "", projectedSeq: durableProjectedSeq, sourceSeq, sourceEventId, terminal: effectiveTerminal, sealed: effectiveTerminal, timing, startedAt: timing.startedAt, lastEventAt: timing.lastEventAt, finishedAt: timing.finishedAt, durationMs: timing.durationMs, createdAt: occurredAt, updatedAt: projectedAt, source: "agentrun-kafka-projector" }, userMessageFact ? 1 : 0, { traceId, sessionId, turnId, terminal: effectiveTerminal, terminalStatus: effectiveStatus, finalText: messageFinalText, terminalSealBlocked, timestamp: projectedAt, timing }) : null; const facts = { sessions: sessionId ? [{ sessionId, status: effectiveStatus, lastTraceId: traceId, projectedSeq: durableProjectedSeq, sourceSeq, sourceEventId, terminal: effectiveTerminal, sealed: effectiveTerminal, timing, startedAt: timing.startedAt, lastEventAt: timing.lastEventAt, finishedAt: timing.finishedAt, durationMs: timing.durationMs, updatedAt: projectedAt }] : [], traceEvents: [{ ...event, id: eventId, traceId, sessionId, turnId, messageId: textValue(event.messageId) || null, status: effectiveStatus, sourceSeq, sourceEventId, projectedSeq: durableProjectedSeq, eventType, terminal: effectiveTerminal, sealed: effectiveTerminal, diagnostic: projectionDiagnostic, timing, startedAt: timing.startedAt, lastEventAt: timing.lastEventAt, finishedAt: timing.finishedAt, durationMs: timing.durationMs, createdAt: occurredAt, occurredAt, updatedAt: projectedAt }], messages: [userMessageFact, messageFact].filter(Boolean), parts: [userMessageFact, messageFact].filter(Boolean).flatMap((message) => messagePartFacts(message, { finalText: message === messageFact ? messageFinalText : null })), turns: sessionId ? [{ turnId, sessionId, traceId, messageId: messageFact?.messageId ?? null, status: effectiveStatus, projectedSeq: durableProjectedSeq, sourceSeq, sourceEventId, terminal: effectiveTerminal, sealed: effectiveTerminal, finalResponse: checkpointFinalResponse, assistantText: checkpointAssistantText, failureKind: textValue(event.failureKind ?? event.errorCode) || null, diagnostic: projectionDiagnostic, timing, startedAt: timing.startedAt, lastEventAt: timing.lastEventAt, finishedAt: timing.finishedAt, durationMs: timing.durationMs, createdAt: occurredAt, updatedAt: projectedAt }] : [], checkpoints: [{ traceId, sessionId, turnId, runId: textValue(event.runId) || null, commandId: textValue(event.commandId) || null, projectedSeq: durableProjectedSeq, sourceSeq, sourceEventId, projectionStatus: effectiveTerminal ? "caught_up" : "projecting", projectionHealth: terminalSealBlocked ? "degraded" : "healthy", terminal: effectiveTerminal, sealed: effectiveTerminal, assistantText: checkpointAssistantText, finalResponse: checkpointFinalResponse, timing, startedAt: timing.startedAt, lastEventAt: timing.lastEventAt, finishedAt: timing.finishedAt, durationMs: timing.durationMs, diagnostic: { ...projectionDiagnostic, lastEventLabel: textValue(event.label) || null, lastEventStatus: textValue(event.status) || null }, updatedAt: projectedAt }] }; return { written: true, suppressedAfterSeal: false, traceId, sessionId, sourceSeq, sourceEventId, projectedSeq: durableProjectedSeq, facts, terminal: effectiveTerminal, sourceTerminal, terminalSealBlocked, valuesPrinted: false }; } function eventProjectionAssistantMessageId(traceId, event = {}) { return eventProjectionMessageId(traceId, "agent", event); } function eventProjectionUserMessageId(traceId, event = {}) { return eventProjectionMessageId(traceId, "user", event); } function eventProjectionMessageId(traceId, role, event = {}) { const explicit = role === "user" ? textValue(event.userMessageId ?? event.userMessage?.messageId) : textValue(event.messageId ?? event.assistantMessageId ?? event.assistantMessage?.messageId); if (explicit) return explicit; const traceSuffix = (safeTraceId(traceId) || String(traceId || "trace")) .replace(/^trc_/u, "") .replace(/[^A-Za-z0-9_.:-]/gu, "_") .slice(0, 48) || "trace"; return `msg_${traceSuffix}_${role === "user" ? "user" : "agent"}`; } function workbenchSessionJsonWithLaunchContext(session = {}, { payload = null, params = null } = {}) { const base = plainObjectValue(session) ?? {}; const launchContext = firstPlainObjectValue( base.launchContext, base.sessionJson?.launchContext, base.session?.launchContext, payload?.launchContext, payload?.sessionJson?.launchContext, payload?.session?.launchContext, params?.launchContext, params?.sessionJson?.launchContext, params?.session?.launchContext ); if (!launchContext) return base; const next = { ...base, launchContext: { ...launchContext, valuesRedacted: true }, valuesRedacted: true }; if (!Object.prototype.hasOwnProperty.call(next, "secretMaterialStored")) next.secretMaterialStored = false; return next; } function firstPlainObjectValue(...values) { for (const value of values) { const object = plainObjectValue(value); if (object) return object; } return null; } function plainObjectValue(value) { return value && typeof value === "object" && !Array.isArray(value) ? value : null; } function workbenchProjectionError(code, message) { const error = new Error(message); error.code = code; error.valuesRedacted = true; return error; } function messagePartFacts(message = {}, { finalText = null } = {}) { const messageId = textValue(message.messageId); if (!messageId || !message.sessionId) return []; const role = textValue(message.role) || "agent"; const terminal = message.terminal === true; const finalResponseText = role !== "user" && terminal ? finalResponseTextValue(finalText, message.finalResponse) : null; const text = finalResponseText ?? textValue(message.text); if (!text) return []; const partType = finalResponseText ? "final_response" : "text"; return [{ partId: stableFactId("wpt", { messageId, index: 0, partType }), messageId, sessionId: message.sessionId, turnId: message.turnId, traceId: message.traceId, partIndex: 0, partType, status: message.status, projectedSeq: message.projectedSeq, sourceSeq: message.sourceSeq, sourceEventId: message.sourceEventId, terminal, sealed: finalResponseText ? true : message.sealed, text, createdAt: message.createdAt, updatedAt: message.updatedAt, valuesPrinted: false }]; } function normalizeMessageFact(message = {}, index, { traceId, sessionId, conversationId, threadId, terminal, terminalStatus = "completed", finalText = null, terminalSealBlocked = false, timestamp, timing: contextTiming }) { const role = textValue(message.role) || "agent"; const messageId = textValue(message.messageId ?? message.id) || (role === "user" ? eventProjectionUserMessageId(traceId, message) : eventProjectionAssistantMessageId(traceId, message)); if (!sessionId || !messageId) return null; const messageTraceId = safeTraceId(message.traceId ?? message.turnId); const appliesToContextTrace = role !== "user" && Boolean(traceId && (!messageTraceId || messageTraceId === traceId)); const contextTerminalStatus = normalizeWorkbenchStatus(terminalStatus) || "completed"; const status = appliesToContextTrace && terminalSealBlocked ? "running" : appliesToContextTrace && terminal ? contextTerminalStatus : normalizeWorkbenchStatus(message.status ?? (terminal && role !== "user" ? contextTerminalStatus : role === "user" ? "sent" : "running")); const messageTerminal = role !== "user" && !(appliesToContextTrace && terminalSealBlocked) && TERMINAL_STATUSES.has(status); const messageTiming = normalizeTimingProjection(message.timing) ?? normalizeTimingProjection(message); const contextTimingProjection = appliesToContextTrace ? normalizeTimingProjection(contextTiming) : null; const timing = role === "user" ? messageTiming ?? emptyTimingProjection() : contextTimingProjection ?? messageTiming ?? emptyTimingProjection(); const terminalCompleted = messageTerminal && status === "completed"; const text = appliesToContextTrace && terminal && finalText ? finalText : textValue(message.text ?? message.content ?? message.message); return { ...message, messageId, sessionId, conversationId, threadId, turnId: textValue(message.turnId) || traceId, traceId: safeTraceId(message.traceId ?? traceId) ?? traceId, role, status, projectedSeq: nonNegativeInteger(message.projectedSeq ?? message.seq ?? index + 1), sourceSeq: nonNegativeInteger(message.sourceSeq ?? message.seq ?? index + 1), sourceEventId: textValue(message.sourceEventId) || `${messageId}:0`, terminal: messageTerminal, sealed: messageTerminal, text, timing, startedAt: timing.startedAt, lastEventAt: timing.lastEventAt, finishedAt: timing.finishedAt, durationMs: timing.durationMs, createdAt: timestampValue(message.createdAt ?? timestamp), updatedAt: timestampValue(message.updatedAt ?? timestamp), ...(terminalCompleted ? { error: null, blocker: null, projection: null, projectionStatus: null, projectionHealth: null, staleMs: null } : {}), valuesPrinted: false }; } function finalResponseTextValue(...values) { for (const value of values) { if (value && typeof value === "object") { const nested = finalResponseTextValue(value.text, value.content, value.message, value.summary, value.preview); if (nested) return nested; continue; } const text = textValue(value); if (text) return text; } return null; } function checkpointIsTerminal(value) { const source = value && typeof value === "object" ? value : null; if (!source) return false; if (source.terminal === true || source.sealed === true) return true; const status = normalizeWorkbenchStatus(source.status ?? source.terminalStatus ?? source.traceStatus); return TERMINAL_STATUSES.has(status); } function eventTimingProjection({ startedAt = null, lastEventAt = null, finishedAt = null, terminal = false, durationMs = null } = {}) { const normalizedStartedAt = optionalTimestampValue(startedAt); const normalizedLastEventAt = optionalTimestampValue(lastEventAt); const normalizedFinishedAt = terminal ? optionalTimestampValue(finishedAt ?? normalizedLastEventAt) : null; const normalizedDurationMs = durationValue(durationMs); return { startedAt: normalizedStartedAt, lastEventAt: normalizedLastEventAt, finishedAt: normalizedFinishedAt, durationMs: terminal ? normalizedDurationMs ?? elapsedBetween(normalizedStartedAt, normalizedFinishedAt ?? normalizedLastEventAt) : null, valuesRedacted: true }; } function normalizeTimingProjection(value) { const source = value && typeof value === "object" ? value : null; if (!source) return null; const startedAt = optionalTimestampValue(source.startedAt); const lastEventAt = optionalTimestampValue(source.lastEventAt); const finishedAt = optionalTimestampValue(source.finishedAt); const durationMs = durationValue(source.durationMs); if (!startedAt && !lastEventAt && !finishedAt && durationMs === null) return null; return { ...source, startedAt, lastEventAt, finishedAt, durationMs, valuesRedacted: source.valuesRedacted !== false }; } function emptyTimingProjection() { return { startedAt: null, lastEventAt: null, finishedAt: null, durationMs: null, valuesRedacted: true }; } function durationValue(value) { if (value === null || value === undefined || value === "") return null; const number = Number(value); return Number.isFinite(number) && number >= 0 ? Math.trunc(number) : null; } function elapsedBetween(startedAt, endedAt) { const start = Date.parse(String(startedAt ?? "")); const end = Date.parse(String(endedAt ?? "")); if (!Number.isFinite(start) || !Number.isFinite(end) || end < start) return null; return Math.trunc(end - start); } function startedAtFromDuration(finishedAt, durationMs) { const end = Date.parse(String(finishedAt ?? "")); const duration = durationValue(durationMs); if (!Number.isFinite(end) || duration === null) return null; return new Date(Math.max(0, end - duration)).toISOString(); } function terminalTimingAuthorityIssue(timing, { terminal = false, traceId = null, source = null, status = null, label = null, sourceSeq = null } = {}) { if (!terminal) return null; const startedAt = optionalTimestampValue(timing?.startedAt); const finishedAt = optionalTimestampValue(timing?.finishedAt); const durationMs = durationValue(timing?.durationMs); if (startedAt && finishedAt && durationMs !== null) return null; return { code: "workbench_terminal_timing_authority_missing", layer: "workbench-projection", category: "timing-authority", traceId, source, status, label: textValue(label) || null, sourceSeq: nonNegativeInteger(sourceSeq), missingStartedAt: !startedAt, missingFinishedAt: !finishedAt, missingDurationMs: durationMs === null, retryable: true, message: "Workbench terminal projection is missing durable timing authority; the UI must surface this diagnostic instead of hiding elapsed time.", valuesPrinted: false }; } function latestTimestamp(...values) { let latest = null; let latestMs = Number.NEGATIVE_INFINITY; for (const value of values) { const timestamp = optionalTimestampValue(value); const ms = Date.parse(String(timestamp ?? "")); if (!Number.isFinite(ms) || ms < latestMs) continue; latest = timestamp; latestMs = ms; } return latest; } function stableFactId(prefix, value) { return `${prefix}_${createHash("sha256").update(JSON.stringify(sortJson(value))).digest("hex").slice(0, 32)}`; } function workbenchSourceEventId(event = {}, { traceId, sourceSeq, occurredAt } = {}) { const explicit = textValue(event.sourceEventId ?? event.id); if (explicit) return explicit; if (isAssistantMessageTraceEvent(event)) { const messageId = eventProjectionAssistantMessageId(traceId, event); return `${safeTraceId(traceId) ?? traceId}:assistant-message:${messageId}`; } const source = textValue(event.source); const label = textValue(event.label ?? event.type ?? event.eventType) || "event"; if (source && sourceSeq > 0) return `${source}:${sourceSeq}:${label}`; return stableFactId("wte_source", { traceId, source: source || null, sourceSeq, label, eventType: textValue(event.eventType ?? event.type) || null, status: textValue(event.status) || null, itemId: textValue(event.itemId ?? event.messageId) || null, runId: textValue(event.runId) || null, commandId: textValue(event.commandId) || null, occurredAt }); } function isAssistantMessageTraceEvent(event = {}) { const type = textValue(event?.eventType ?? event?.type).toLowerCase(); const label = textValue(event?.label).toLowerCase(); return type === "assistant" || /assistant:message|assistant_message/u.test(label); } function sortJson(value) { if (Array.isArray(value)) return value.map(sortJson); if (value && typeof value === "object") return Object.fromEntries(Object.keys(value).sort().map((key) => [key, sortJson(value[key])])); return value; } function nonNegativeInteger(value) { const parsed = Number.parseInt(value ?? "", 10); return Number.isInteger(parsed) && parsed >= 0 ? parsed : 0; } function timestampValue(value) { const ms = Date.parse(String(value ?? "")); return Number.isFinite(ms) ? new Date(ms).toISOString() : new Date().toISOString(); } function optionalTimestampValue(value) { const ms = Date.parse(String(value ?? "")); return Number.isFinite(ms) ? new Date(ms).toISOString() : null; } function textValue(value) { return String(value ?? "").trim(); }