82 lines
3.8 KiB
TypeScript
82 lines
3.8 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 } = {}) {
|
|
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;
|
|
|
|
setImmediate(() => {
|
|
void (async () => {
|
|
let result = currentResult;
|
|
let lastError = null;
|
|
while (true) {
|
|
const cached = getCachedResult?.(traceId);
|
|
if (isCanceled?.(cached)) return;
|
|
if (isTerminal?.(cached?.status)) {
|
|
onTerminal?.(cached, { preserveLastTraceId: true });
|
|
return;
|
|
}
|
|
try {
|
|
const synced = await syncResult({ result });
|
|
result = synced?.result ?? result;
|
|
if (result && isTerminal?.(result.status)) {
|
|
onTerminal?.(result, { preserveLastTraceId: true });
|
|
return;
|
|
}
|
|
const signature = activitySignature?.(result, synced?.runnerTrace ?? traceStore?.snapshot?.(traceId));
|
|
if (signature && signature !== lastActivitySignature) {
|
|
lastActivitySignature = signature;
|
|
lastActivityAt = Date.now();
|
|
idleDegraded = false;
|
|
}
|
|
lastError = null;
|
|
} catch (error) {
|
|
lastError = error;
|
|
}
|
|
const idleMs = Date.now() - lastActivityAt;
|
|
if (noResponseTimeoutMs && idleMs >= noResponseTimeoutMs && !idleDegraded) {
|
|
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) => {
|
|
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)));
|
|
}
|