fix: recover Workbench terminal outbox projection

This commit is contained in:
lyon
2026-06-20 20:06:33 +08:00
parent c4e44e1db8
commit bad5a478bd
7 changed files with 256 additions and 16 deletions
+67 -1
View File
@@ -1,4 +1,4 @@
// SPEC: PJ2026-0104010803 Workbench唯一投影 draft-2026-06-19-p1-agentrun-incremental-cursor; PJ2026-010205 HWLAB接入 draft-2026-06-17-r0; PJ2026-01060505 Workbench Performance draft-2026-06-17-p0.
// SPEC: PJ2026-0104010803 Workbench唯一投影 draft-2026-06-19-p1-agentrun-incremental-cursor; draft-2026-06-20-p2-terminal-outbox-recovery; PJ2026-010205 HWLAB接入 draft-2026-06-17-r0; PJ2026-01060505 Workbench Performance draft-2026-06-17-p0.
// Responsibility: AgentRun v0.1 adapter, incremental Workbench projection cursor sync, and upstream timing projection for Cloud API observability.
import { createHash, randomUUID } from "node:crypto";
@@ -737,6 +737,33 @@ export async function refreshAgentRunTrace({ traceId, result = null, options = {
export async function cancelAgentRunChatTurn({ traceId, currentResult = null, options = {}, traceStore = defaultCodeAgentTraceStore }) {
const mapped = currentResult ?? await loadPersistedAgentRunResult(traceId, options);
if (!mapped?.agentRun?.commandId) return null;
const localTerminal = agentRunCancelTerminalStatus(mapped);
if (localTerminal) return agentRunAlreadyTerminalCancelPayload({ traceId, payload: mapped, terminalStatus: localTerminal, options, traceStore });
try {
const guard = await syncAgentRunChatResult({
traceId,
currentResult: mapped,
options: { ...options, deferAgentRunResultSync: false },
traceStore,
forceResultSync: true,
refreshEvents: true
});
const guarded = guard?.result ?? null;
const guardedTerminal = agentRunCancelTerminalStatus(guarded);
if (guardedTerminal) return agentRunAlreadyTerminalCancelPayload({ traceId, payload: guarded, terminalStatus: guardedTerminal, options, traceStore });
} catch (error) {
traceStore.append(traceId, agentRunTraceEvent({
type: "cancel",
status: "running",
label: "agentrun:cancel:terminal_guard_failed",
errorCode: error?.code ?? "agentrun_cancel_terminal_guard_failed",
message: "HWLAB could not confirm AgentRun terminal state before forwarding cancel; continuing with cancel because no terminal authority was observed.",
runId: mapped.agentRun.runId,
commandId: mapped.agentRun.commandId,
waitingFor: "cancel-terminal-guard",
valuesPrinted: false
}, mapped.agentRun));
}
const env = options.env ?? process.env;
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
const managerUrl = resolveAgentRunManagerUrl(env, mapped.agentRun.managerUrl);
@@ -783,6 +810,45 @@ export async function cancelAgentRunChatTurn({ traceId, currentResult = null, op
return payload;
}
function agentRunCancelTerminalStatus(payload = null) {
if (!payload || typeof payload !== "object") return null;
for (const value of [payload.status, payload.agentRun?.terminalStatus, payload.agentRun?.commandState, payload.agentRun?.status, payload.agentRun?.runStatus]) {
const status = normalizeAgentRunStatus(value);
if (isAgentRunTerminalStatus(status)) return status;
}
return null;
}
function agentRunAlreadyTerminalCancelPayload({ traceId, payload, terminalStatus, options = {}, traceStore = defaultCodeAgentTraceStore } = {}) {
const normalizedTerminalStatus = normalizeAgentRunStatus(terminalStatus);
const agentRun = payload?.agentRun && typeof payload.agentRun === "object" ? payload.agentRun : {};
traceStore.append(traceId, agentRunTraceEvent({
type: "cancel",
status: normalizedTerminalStatus || "completed",
label: "agentrun:cancel:already_terminal",
message: "AgentRun command is already terminal; HWLAB did not forward cancel or overwrite the terminal Workbench projection.",
runId: agentRun.runId,
commandId: agentRun.commandId,
terminal: true,
valuesPrinted: false
}, agentRun));
const payloadStatus = normalizeAgentRunStatus(payload?.status);
const status = isAgentRunTerminalStatus(payloadStatus) ? payloadStatus : normalizedTerminalStatus || payloadStatus || "completed";
const result = {
...payload,
status,
canceled: false,
alreadyTerminal: true,
cancelStatus: "already_terminal",
updatedAt: nowIso(options.now),
runnerTrace: traceStore.snapshot(traceId),
agentRun: { ...agentRun, terminalStatus: agentRun.terminalStatus ?? normalizedTerminalStatus, valuesPrinted: false },
valuesPrinted: false
};
options.codeAgentChatResults?.set?.(traceId, result);
return result;
}
export async function steerAgentRunChatTurn({ traceId, currentResult = null, params = {}, options = {}, traceStore = defaultCodeAgentTraceStore }) {
const mapped = currentResult ?? await loadPersistedAgentRunResult(traceId, options);
if (!safeTraceId(traceId) || !mapped?.agentRun?.runId || !mapped?.agentRun?.commandId) return null;