Files
pikasTech-HWLAB/internal/cloud/workbench-projection-finalizer.ts
T
2026-07-02 17:31:07 +08:00

107 lines
5.7 KiB
TypeScript

/*
* 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;
let nextDelayMs = pollIntervalMs;
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;
nextDelayMs = projectionFinalizerDelayMs(synced?.polling, pollIntervalMs);
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" });
nextDelayMs = projectionFinalizerDelayMs(error?.polling, pollIntervalMs);
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(nextDelayMs);
}
})().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)));
}
function projectionFinalizerDelayMs(polling = null, fallbackMs = 1000) {
const fallback = positiveInteger(fallbackMs, 1000);
const backoffMs = positiveInteger(polling?.backoffMs, 0);
return backoffMs > 0 ? Math.max(fallback, backoffMs) : fallback;
}
function positiveInteger(value, fallback) {
const parsed = Number.parseInt(String(value ?? ""), 10);
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback;
}