135 lines
5.2 KiB
TypeScript
135 lines
5.2 KiB
TypeScript
// SPEC: PJ2026-0104010803 Workbench唯一投影 draft-2026-06-24-p1-opencode-message-part-authority.
|
|
// Responsibility: AgentRun cancel disposition payloads and cancel trace diagnostics.
|
|
|
|
export function agentRunCancelTerminalStatus(payload = null) {
|
|
if (!payload || typeof payload !== "object") return null;
|
|
for (const value of [payload.agentRun?.terminalStatus, payload.agentRun?.commandState, payload.agentRun?.commandStatus]) {
|
|
const status = normalizeAgentRunStatus(value);
|
|
if (isAgentRunTerminalStatus(status)) return status;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
export function agentRunAlreadyTerminalCancelPayload({ traceId, payload, terminalStatus, options = {}, traceStore } = {}) {
|
|
const normalizedTerminalStatus = normalizeAgentRunStatus(terminalStatus);
|
|
const agentRun = payload?.agentRun && typeof payload.agentRun === "object" ? payload.agentRun : {};
|
|
traceStore?.append?.(traceId, agentRunCancelTraceEvent({
|
|
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",
|
|
cancelDisposition: {
|
|
status: "already_terminal",
|
|
forwarded: false,
|
|
terminal: true,
|
|
traceId,
|
|
runId: agentRun.runId ?? null,
|
|
commandId: agentRun.commandId ?? null,
|
|
route: "/v1/agent/chat/cancel",
|
|
valuesPrinted: false
|
|
},
|
|
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 function agentRunCancelBlockedPayload({ traceId, payload, error, cancelPath, options = {}, traceStore } = {}) {
|
|
const agentRun = payload?.agentRun && typeof payload.agentRun === "object" ? payload.agentRun : {};
|
|
const code = firstNonEmpty(error?.code, error?.agentRunError?.failureKind, "agentrun_cancel_failed");
|
|
const statusCode = Number.isFinite(Number(error?.statusCode)) ? Number(error.statusCode) : 502;
|
|
const message = firstNonEmpty(error?.message, error?.agentRunError?.message, "AgentRun command cancel API did not accept the cancel request.");
|
|
const userMessage = "取消请求未被 AgentRun 接受;已保留当前 trace/run/command 证据,页面不能把本轮伪装成已取消。";
|
|
const now = nowIso(options.now);
|
|
const nextAgentRun = { ...agentRun, cancelStatus: "blocked", cancelErrorCode: code, cancelStatusCode: statusCode, updatedAt: now, valuesPrinted: false };
|
|
traceStore?.append?.(traceId, agentRunCancelTraceEvent({
|
|
type: "cancel",
|
|
status: "blocked",
|
|
label: "agentrun:cancel:blocked",
|
|
errorCode: code,
|
|
message: `AgentRun cancel was not accepted by the command cancel API: ${message}`,
|
|
runId: agentRun.runId,
|
|
commandId: agentRun.commandId,
|
|
waitingFor: "agentrun-cancel-disposition",
|
|
terminal: false,
|
|
valuesPrinted: false
|
|
}, nextAgentRun));
|
|
const cancelDisposition = {
|
|
status: "blocked",
|
|
code,
|
|
statusCode,
|
|
message,
|
|
userMessage,
|
|
retryable: true,
|
|
terminal: false,
|
|
route: "/v1/agent/chat/cancel",
|
|
upstreamRoute: cancelPath,
|
|
traceId,
|
|
runId: agentRun.runId ?? null,
|
|
commandId: agentRun.commandId ?? null,
|
|
valuesPrinted: false
|
|
};
|
|
const result = {
|
|
...payload,
|
|
status: "blocked",
|
|
canceled: false,
|
|
cancelStatus: "blocked",
|
|
cancelDisposition,
|
|
updatedAt: now,
|
|
agentRun: nextAgentRun,
|
|
runnerTrace: traceStore?.snapshot?.(traceId),
|
|
error: { code, layer: "agentrun", category: "cancel-blocked", retryable: true, message, userMessage, traceId, route: "/v1/agent/chat/cancel", upstreamRoute: cancelPath, statusCode, toolName: "agentrun.command.cancel", valuesPrinted: false },
|
|
userMessage,
|
|
valuesPrinted: false
|
|
};
|
|
options.codeAgentChatResults?.set?.(traceId, result);
|
|
return result;
|
|
}
|
|
|
|
function agentRunCancelTraceEvent(event = {}, agentRun = {}) {
|
|
return {
|
|
...event,
|
|
backend: "agentrun-v01",
|
|
adapter: "agentrun-v01",
|
|
runId: event.runId ?? agentRun.runId ?? null,
|
|
commandId: event.commandId ?? agentRun.commandId ?? null,
|
|
valuesPrinted: false
|
|
};
|
|
}
|
|
|
|
function isAgentRunTerminalStatus(value) {
|
|
return ["completed", "failed", "blocked", "cancelled", "canceled"].includes(normalizeAgentRunStatus(value));
|
|
}
|
|
|
|
function normalizeAgentRunStatus(value) {
|
|
return String(value ?? "").trim().toLowerCase().replace(/_/gu, "-");
|
|
}
|
|
|
|
function firstNonEmpty(...values) {
|
|
for (const value of values) {
|
|
const text = String(value ?? "").trim();
|
|
if (text) return text;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function nowIso(now) {
|
|
return typeof now === "function" ? now() : new Date().toISOString();
|
|
}
|