/* * SPEC: PJ2026-0104010803 Workbench唯一投影 draft-2026-06-18-p0-unique-projection; PJ2026-010205 HWLAB接入 draft-2026-06-17-r0. * 职责: WorkbenchProjectionFinalizer 组件入口。以 checkpoint/轮询驱动 AgentRun facts 追平,不让 GET path 承担 finalize/repair。 */ import { appendProjectionDiagnostic } from "./workbench-projection-writer.ts"; export function scheduleWorkbenchProjectionFinalizer({ traceId, currentResult = null, traceStore, getCachedResult, isCanceled, isTerminal, syncResult, onTerminal, activitySignature, noResponseTimeoutMs = null, pollIntervalMs = 1000, sleep = defaultSleep, performanceStore = null } = {}) { if (!traceId || !currentResult?.agentRun?.runId || !currentResult?.agentRun?.commandId || typeof syncResult !== "function") return null; let lastActivityAt = Date.now(); let lastActivitySignature = activitySignature?.(currentResult, traceStore?.snapshot?.(traceId)) ?? null; let idleDegraded = false; performanceStore?.recordWorkbenchProjectorCandidate?.({ status: "scheduled", reason: "finalizer" }); setImmediate(() => { void (async () => { let result = currentResult; let lastError = null; while (true) { const cached = getCachedResult?.(traceId); if (isCanceled?.(cached)) return; if (cached && isTerminal?.(cached, traceStore?.snapshot?.(traceId))) { performanceStore?.recordWorkbenchProjectorCandidate?.({ status: "completed", reason: "cached_terminal" }); onTerminal?.(cached, { preserveLastTraceId: true }); return; } const syncStartedAt = Date.now(); try { const synced = await syncResult({ result }); performanceStore?.recordWorkbenchProjectorBatch?.({ phase: "candidate_scan", status: "ok", durationMs: Date.now() - syncStartedAt }); result = synced?.result ?? result; const runnerTrace = synced?.runnerTrace ?? traceStore?.snapshot?.(traceId); if (result && isTerminal?.(result, runnerTrace)) { performanceStore?.recordWorkbenchProjectorCandidate?.({ status: "completed", reason: "synced_terminal" }); onTerminal?.(result, { preserveLastTraceId: true }); return; } const signature = activitySignature?.(result, runnerTrace); if (signature && signature !== lastActivitySignature) { lastActivitySignature = signature; lastActivityAt = Date.now(); idleDegraded = false; } lastError = null; } catch (error) { performanceStore?.recordWorkbenchProjectorBatch?.({ phase: "candidate_scan", status: error?.code === "agentrun_timeout" ? "timeout" : "error", durationMs: Date.now() - syncStartedAt }); performanceStore?.recordWorkbenchProjectorError?.({ reason: error?.code ?? "finalizer_sync_failed" }); lastError = error; } const idleMs = Date.now() - lastActivityAt; if (noResponseTimeoutMs && idleMs >= noResponseTimeoutMs && !idleDegraded) { performanceStore?.recordWorkbenchProjectorCandidate?.({ status: "degraded", reason: "no_response_idle_timeout" }); performanceStore?.recordWorkbenchProjectorError?.({ reason: lastError?.code ?? "no_response_idle_timeout" }); appendProjectionDiagnostic(traceStore, traceId, { type: "turn-status", status: "degraded", label: "projection-finalizer:no-response-idle-timeout", errorCode: lastError?.code ?? "no_response_idle_timeout", degradedReason: lastError ? "projection_activity_stale" : "no_response_idle_timeout", message: lastError?.message ?? `AgentRun projection has no new activity for ${idleMs}ms; idle threshold ${noResponseTimeoutMs}ms.`, runId: result?.agentRun?.runId ?? currentResult.agentRun.runId, commandId: result?.agentRun?.commandId ?? currentResult.agentRun.commandId, timeoutMs: noResponseTimeoutMs, idleMs, lastActivityAt: new Date(lastActivityAt).toISOString(), waitingFor: "agentrun-result-activity", terminal: false }); idleDegraded = true; } await sleep(pollIntervalMs); } })().catch((error) => { performanceStore?.recordWorkbenchProjectorError?.({ reason: error?.code ?? "workbench_projection_finalizer_crashed" }); appendProjectionDiagnostic(traceStore, traceId, { type: "turn-status", status: "degraded", label: "projection-finalizer:sync-crashed", errorCode: error?.code ?? "workbench_projection_finalizer_crashed", message: error?.message ?? "Workbench projection finalizer crashed.", runId: currentResult.agentRun.runId, commandId: currentResult.agentRun.commandId, waitingFor: "agentrun-result", terminal: false }); }); }); return { scheduled: true, traceId, noResponseTimeoutMs, pollIntervalMs, valuesPrinted: false }; } function defaultSleep(ms) { return new Promise((resolve) => setTimeout(resolve, Math.max(0, Number(ms) || 0))); }