/* * SPEC: PJ2026-0104010803 Workbench唯一投影 draft-2026-06-19-p0-projector-resume; PJ2026-010403 API契约 draft-2026-06-18-r1. * 职责: 生成唯一 Workbench turn projection;trace event 状态只作为输入证据,不直接成为 turn lifecycle。 */ export const TERMINAL_STATUSES = new Set(["completed", "failed", "blocked", "timeout", "cancelled", "canceled", "idle"]); export const RUNNING_STATUSES = new Set(["running", "retrying", "pending", "queued", "accepted", "dispatching", "streaming", "active", "processing", "busy", "creating"]); export function createWorkbenchTurnProjection({ turnId = null, traceId = null, result = null, session = null, trace = null } = {}) { const projectionTraceId = textValue(traceId ?? trace?.traceId ?? result?.traceId ?? session?.lastTraceId) || null; const projectionTurnId = textValue(turnId) || projectionTraceId; const traceTerminal = traceTerminalEvidence(trace); const terminalEvidence = terminalTurnEvidence({ result, traceTerminal }); const activeEvidence = activeTurnEvidence({ result, session, trace }); const status = terminalEvidence?.status ?? activeEvidence?.status ?? "unknown"; const running = RUNNING_STATUSES.has(status); const terminal = Boolean(terminalEvidence && TERMINAL_STATUSES.has(status) && !running); const finalText = terminal ? projectionText(result?.finalResponse, result?.assistantText, result?.reply, result?.text, result?.summary, trace?.finalResponse, trace?.terminalEvidence?.finalResponse, terminalEvidence?.finalResponse) : null; const agentRun = objectValue(result?.agentRun ?? trace?.agentRun); const lastEvent = traceLastEvent(trace); return { turnId: projectionTurnId, traceId: projectionTraceId, status, running, terminal, source: terminalEvidence?.source ?? activeEvidence?.source ?? null, terminalEvidence, finalResponse: finalText ? { text: finalText, status, traceId: projectionTraceId, valuesPrinted: false } : null, assistantText: finalText, lastProjectedSeq: traceLastSeq(trace), sourceRunId: agentRun?.runId ?? lastEvent?.runId ?? lastEvent?.payload?.runId ?? null, sourceCommandId: agentRun?.commandId ?? lastEvent?.commandId ?? lastEvent?.payload?.commandId ?? null, eventCount: normalizedEventCount(trace), updatedAt: trace?.updatedAt ?? result?.updatedAt ?? session?.updatedAt ?? null, valuesRedacted: true }; } export function projectionDiagnostics({ traceId = null, projection = null, result = null, trace = null, refreshError = null } = {}) { const turn = projection ?? createWorkbenchTurnProjection({ traceId, result, trace }); const source = projectionDiagnosticSource(trace); const blocker = refreshError ?? source?.blocker ?? trace?.blocker ?? result?.blocker ?? result?.error ?? null; const hasProjectionInput = hasTraceProjection(trace) || Boolean(result || result?.agentRun); const sourceStatus = normalizeProjectionStatus(source?.projectionStatus ?? trace?.projectionStatus); const status = sourceStatus ?? (blocker ? "blocked" : turn.terminal ? "caught-up" : hasProjectionInput ? "projecting" : "unknown"); const diagnostic = blocker ? diagnosticBlocker(blocker) : null; const projectionHealth = normalizeProjectionHealth(source?.projectionHealth ?? trace?.projectionHealth) ?? projectionHealthFor({ status, turn, hasProjectionInput, blocker: diagnostic }); const staleMs = projectionStaleMs(source?.staleMs ?? trace?.staleMs, turn.updatedAt ?? trace?.updatedAt ?? result?.updatedAt); return { projectionStatus: status, projectionHealth, lastProjectedSeq: turn.lastProjectedSeq ?? null, sourceRunId: turn.sourceRunId ?? null, sourceCommandId: turn.sourceCommandId ?? null, staleMs, blocker: diagnostic, updatedAt: turn.updatedAt ?? null, valuesRedacted: true }; } export function durableTraceStatus(events = []) { const terminal = terminalTraceEventEvidence(events); if (terminal) return terminal.status; const activeStatus = activeTraceEventStatus(events); return activeStatus ?? (events.length > 0 ? "running" : "missing"); } export function traceTerminalEvidence(trace = null) { const direct = objectValue(trace?.terminalEvidence); if (direct) { const status = terminalStatusFromValue(direct.status ?? direct.terminalStatus ?? trace?.status) ?? "completed"; if (status !== "completed" && retryableProviderInterruptionEvidence(direct, trace)) return null; return { source: "trace-terminal-evidence", status, evidence: direct, valuesRedacted: true }; } const finalResponse = objectValue(trace?.finalResponse); if (finalResponse) { const status = terminalStatusFromValue(finalResponse.status ?? trace?.status) ?? "completed"; return { source: "trace-final-response", status, evidence: { textPresent: Boolean(projectionText(finalResponse)), valuesRedacted: true }, valuesRedacted: true }; } const events = Array.isArray(trace?.events) ? trace.events : []; return terminalTraceEventEvidence(events); } export function normalizeWorkbenchStatus(value) { const text = String(value ?? "").trim().toLowerCase().replace(/_/gu, "-"); if (text === "cancelled") return "canceled"; return text || "unknown"; } function terminalTurnEvidence({ result = null, traceTerminal = null } = {}) { const resultStatus = terminalStatusFromValue(result?.status ?? result?.terminalStatus ?? result?.agentRun?.terminalStatus); if (resultStatus) { if (resultStatus !== "completed" && retryableProviderInterruptionEvidence(result, result?.agentRun, result?.providerTrace, traceTerminal?.evidence)) return null; return { source: "result", status: resultStatus, finalResponse: traceTerminal?.finalResponse ?? null, valuesRedacted: true }; } if (traceTerminal && retryableProviderInterruptionEvidence(traceTerminal.evidence)) return null; return traceTerminal; } function activeTurnEvidence({ result = null, session = null, trace = null } = {}) { const retryEvidence = retryableProviderInterruptionEvidence(result, result?.agentRun, result?.providerTrace, trace); if (retryEvidence) return { source: "provider-retry", status: "retrying", evidence: retryEvidence, valuesRedacted: true }; const resultStatus = firstStatus([ result?.status, result?.agentRun?.status, result?.agentRun?.commandState, result?.agentRun?.runStatus ], RUNNING_STATUSES); if (resultStatus) return { source: "result", status: normalizeActiveStatus(resultStatus), valuesRedacted: true }; const traceStatus = firstStatus([trace?.status, trace?.traceStatus], RUNNING_STATUSES); if (traceStatus) return { source: "trace", status: normalizeActiveStatus(traceStatus), valuesRedacted: true }; if (hasTraceProjection(trace)) return { source: "trace-events", status: "running", valuesRedacted: true }; const sessionStatus = firstStatus([session?.status, session?.session?.sessionStatus], RUNNING_STATUSES); if (sessionStatus) return { source: "session", status: normalizeActiveStatus(sessionStatus), valuesRedacted: true }; return null; } function firstStatus(values, accepted) { for (const value of values) { const status = normalizeWorkbenchStatus(value); if (accepted.has(status)) return status; } return null; } function terminalStatusFromValue(value) { const status = normalizeWorkbenchStatus(value); return TERMINAL_STATUSES.has(status) && !RUNNING_STATUSES.has(status) ? status : null; } function normalizeActiveStatus(status) { return status === "active" || status === "busy" || status === "processing" || status === "creating" ? "running" : status; } function terminalTraceEventEvidence(events = []) { const finalResponse = terminalAssistantEventFinalResponse(events); for (let index = events.length - 1; index >= 0; index -= 1) { const event = events[index]; if (!event || typeof event !== "object") continue; if (retryableProviderInterruptionEvidence(event)) continue; const terminal = event.terminal === true || event.final === true || event.replyAuthority === true; if (!terminal) continue; const status = terminalStatusFromValue(event.status ?? event.terminalStatus) ?? (event.error || event.errorCode ? "failed" : "completed"); return { source: "trace-terminal-event", status, seq: eventSeq(event, index), eventType: textValue(event.type ?? event.label) || null, finalResponse, evidence: finalResponse ? { textPresent: true, source: "trace-terminal-assistant-event", valuesRedacted: true } : null, valuesRedacted: true }; } return null; } function terminalAssistantEventFinalResponse(events = []) { for (let index = events.length - 1; index >= 0; index -= 1) { const event = events[index]; if (!event || typeof event !== "object") continue; if (!isAssistantTraceEvent(event)) continue; if (!(event.terminal === true || event.final === true || event.replyAuthority === true)) continue; const text = projectionText(event.finalResponse, event.text, event.content, event.message, event.summary, event.payload?.text, event.payload?.content, event.payload?.message); if (!text) continue; const status = terminalStatusFromValue(event.status ?? event.terminalStatus ?? event.payload?.terminalStatus) ?? "completed"; return { text, status, traceId: textValue(event.traceId) || null, seq: eventSeq(event, index), eventType: textValue(event.type ?? event.label) || null, valuesPrinted: false }; } return null; } function isAssistantTraceEvent(event = {}) { const type = String(event.type ?? event.eventType ?? "").trim().toLowerCase(); if (type === "assistant" || type === "assistant_message") return true; return /assistant:message|assistant_message/u.test(String(event.label ?? "").toLowerCase()); } function activeTraceEventStatus(events = []) { for (const event of [...events].reverse()) { if (retryableProviderInterruptionEvidence(event)) return "retrying"; const status = normalizeWorkbenchStatus(event?.status ?? event?.type); if (RUNNING_STATUSES.has(status)) return normalizeActiveStatus(status); } return null; } function retryableProviderInterruptionEvidence(...values) { for (const value of values) { const direct = retryableProviderInterruptionRecord(value); if (direct) return direct; } return null; } function retryableProviderInterruptionRecord(value) { const record = objectValue(value); if (!record) return null; const kind = normalizeWorkbenchStatus(record.failureKind ?? record.errorCode ?? record.code ?? record.name); if (kind === "provider-stream-disconnected" && record.willRetry === true) { return { failureKind: kind, willRetry: true, runId: textValue(record.runId) || null, commandId: textValue(record.commandId) || null, valuesRedacted: true }; } for (const nested of [record.payload, record.providerTrace, record.agentRun, record.error, record.blocker, record.terminalEvidence]) { const found = retryableProviderInterruptionRecord(nested); if (found) return found; } return null; } function hasTraceProjection(trace) { return Boolean(trace && trace.status !== "missing" && (normalizedEventCount(trace) > 0 || traceLastEvent(trace))); } function normalizedEventCount(trace) { const events = Array.isArray(trace?.events) ? trace.events : []; const count = Number(trace?.eventCount ?? events.length); return Number.isFinite(count) && count >= 0 ? Math.trunc(count) : events.length; } function traceLastSeq(trace) { const events = Array.isArray(trace?.events) ? trace.events : []; const lastEvent = traceLastEvent(trace); const indexedMax = events.reduce((max, event, index) => Math.max(max, eventSeq(event, index)), 0); return Math.max(indexedMax, lastEvent ? eventSeq(lastEvent, Math.max(0, events.length - 1)) : 0) || null; } function traceLastEvent(trace) { const events = Array.isArray(trace?.events) ? trace.events : []; return trace?.lastEvent ?? events.at(-1) ?? null; } function projectionDiagnosticSource(trace) { const direct = objectValue(trace?.projection); if (direct) return direct; if (trace?.projectionHealth || trace?.projectionStatus || trace?.blocker) return trace; return null; } function normalizeProjectionStatus(value) { const text = String(value ?? "").trim().toLowerCase().replace(/_/gu, "-"); return ["caught-up", "projecting", "blocked", "stalled", "unknown"].includes(text) ? text : null; } function normalizeProjectionHealth(value) { const text = String(value ?? "").trim().toLowerCase().replace(/_/gu, "-"); return ["caught-up", "projecting", "degraded", "stalled", "unavailable", "unknown"].includes(text) ? text : null; } function projectionHealthFor({ status, turn, hasProjectionInput, blocker }) { if (blocker?.code === "projection_store_unavailable") return "unavailable"; if (blocker) return status === "stalled" ? "stalled" : "degraded"; if (turn.terminal || status === "caught-up") return "caught-up"; if (status === "stalled") return "stalled"; if (status === "projecting" || hasProjectionInput) return "projecting"; return "unknown"; } function projectionStaleMs(value, updatedAt) { const direct = Number(value); if (Number.isFinite(direct) && direct >= 0) return Math.trunc(direct); const updatedAtMs = Date.parse(String(updatedAt ?? "")); if (!Number.isFinite(updatedAtMs)) return null; return Math.max(0, Date.now() - updatedAtMs); } function eventSeq(event, index) { const seq = Number(event?.seq); return Number.isFinite(seq) && seq > 0 ? Math.trunc(seq) : index + 1; } function projectionText(...values) { for (const value of values) { if (value && typeof value === "object") { const nested = messageAuthorityTextValue(value.text ?? value.content ?? value.message ?? value.summary ?? value.preview ?? value.title); if (nested) return nested; continue; } const text = messageAuthorityTextValue(value); if (text) return text; } return null; } function objectValue(value) { return value && typeof value === "object" && !Array.isArray(value) ? value : null; } function textValue(value) { return String(value ?? "").trim(); } function messageAuthorityTextValue(value) { const text = String(value ?? "").replace(/\r\n?/gu, "\n"); if (!text.trim() || text.trim() === "[object Object]") return ""; return text; } function diagnosticBlocker(value = {}) { if (!value || typeof value !== "object") return { code: "projection_blocked", summary: String(value), valuesRedacted: true }; return { code: String(value.code ?? value.errorCode ?? "projection_blocked"), layer: textValue(value.layer ?? value.source) || null, category: textValue(value.category ?? value.type) || null, summary: String(value.summary ?? value.userMessage ?? value.message ?? value.code ?? "Projection is blocked."), message: textValue(value.message ?? value.summary ?? value.userMessage) || null, userMessage: textValue(value.userMessage ?? value.zh) || null, retryable: typeof value.retryable === "boolean" ? value.retryable : null, runId: textValue(value.runId) || null, commandId: textValue(value.commandId) || null, timeoutMs: Number.isFinite(Number(value.timeoutMs)) ? Math.trunc(Number(value.timeoutMs)) : null, valuesRedacted: true }; }