fix: skip completed AgentRun turn refresh
This commit is contained in:
@@ -440,6 +440,10 @@ function isAgentRunCommandAlreadyClaimed(error) {
|
||||
export async function syncAgentRunChatResult({ traceId, currentResult = null, options = {}, traceStore = defaultCodeAgentTraceStore, appendResultEvent = true, refreshEvents = true, forceResultSync = false }) {
|
||||
const initial = currentResult ?? await loadPersistedAgentRunResult(traceId, options);
|
||||
const mapped = initial ? await resolveAgentRunTraceCommandMapping({ traceId, mapped: initial, options }) : initial;
|
||||
if (!forceResultSync && options.skipAgentRunTerminalRefreshIfComplete === true && agentRunTerminalResultRefreshSatisfied(mapped, traceId)) {
|
||||
const runnerTrace = mapped.runnerTrace ?? traceStore.snapshot(traceId);
|
||||
return { result: mapped, runnerTrace, found: true, eventsRefreshed: false, resultSynced: false, terminalRefreshSkipped: true };
|
||||
}
|
||||
if (!mapped?.agentRun?.runId || !mapped?.agentRun?.commandId) return { result: currentResult, runnerTrace: traceStore.snapshot(traceId), found: Boolean(currentResult), eventsRefreshed: false, resultSynced: false };
|
||||
const env = options.env ?? process.env;
|
||||
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
||||
@@ -491,6 +495,33 @@ function agentRunCommandResultSyncRequired(mapped = {}, eventsResponse = null) {
|
||||
return Boolean(agentRunTerminalStatusFromEvents(eventsResponse?.events));
|
||||
}
|
||||
|
||||
function agentRunTerminalResultRefreshSatisfied(mapped = {}, traceId = null) {
|
||||
const safeId = safeTraceId(traceId ?? mapped?.traceId);
|
||||
const agentRun = mapped?.agentRun && typeof mapped.agentRun === "object" ? mapped.agentRun : null;
|
||||
const traceSummary = mapped?.traceSummary && typeof mapped.traceSummary === "object" ? mapped.traceSummary : null;
|
||||
const finalResponse = mapped?.finalResponse && typeof mapped.finalResponse === "object" ? mapped.finalResponse : null;
|
||||
const providerTrace = agentRun?.providerTrace && typeof agentRun.providerTrace === "object"
|
||||
? agentRun.providerTrace
|
||||
: mapped?.providerTrace && typeof mapped.providerTrace === "object"
|
||||
? mapped.providerTrace
|
||||
: null;
|
||||
const commandId = text(agentRun?.commandId);
|
||||
const runId = text(agentRun?.runId);
|
||||
if (!safeId || !runId || !commandId) return false;
|
||||
if (!isAgentRunTerminalStatus(mapped?.status ?? agentRun?.terminalStatus ?? agentRun?.commandState ?? agentRun?.status)) return false;
|
||||
if (safeTraceId(mapped?.traceId ?? agentRun?.traceId) !== safeId) return false;
|
||||
if (safeTraceId(providerTrace?.traceId) !== safeId) return false;
|
||||
const finalTraceId = safeTraceId(finalResponse?.traceId ?? safeId);
|
||||
if (finalTraceId && finalTraceId !== safeId) return false;
|
||||
if (!String(finalResponse?.text ?? "").trim()) return false;
|
||||
if (traceSummary?.source !== "agentrun-command-result") return false;
|
||||
if (safeTraceId(traceSummary.traceId ?? safeId) !== safeId) return false;
|
||||
const summaryAgentRun = traceSummary.agentRun && typeof traceSummary.agentRun === "object" ? traceSummary.agentRun : null;
|
||||
if (text(summaryAgentRun?.runId) !== runId) return false;
|
||||
if (text(summaryAgentRun?.commandId) !== commandId) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function agentRunTerminalStatusFromEvents(events = []) {
|
||||
if (!Array.isArray(events)) return null;
|
||||
for (const event of [...events].reverse()) {
|
||||
@@ -529,7 +560,7 @@ async function resolveAgentRunTraceCommandMapping({ traceId, mapped, options = {
|
||||
const traceSummaryCommandId = text(mapped?.traceSummary?.agentRun?.commandId);
|
||||
const commandId = text(agentRun.commandId);
|
||||
const traceFieldsMatch = agentRunTraceId === safeId && providerTraceId === safeId && (!traceSummaryCommandId || traceSummaryCommandId === commandId);
|
||||
if (mapped.status === "running" && traceFieldsMatch) return mapped;
|
||||
if (traceFieldsMatch && (mapped.status === "running" || (options.skipAgentRunTerminalRefreshIfComplete === true && agentRunTerminalResultRefreshSatisfied(mapped, safeId)))) return mapped;
|
||||
const env = options.env ?? process.env;
|
||||
const fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
||||
const managerUrl = resolveAgentRunManagerUrl(env, agentRun.managerUrl);
|
||||
|
||||
@@ -2303,6 +2303,99 @@ test("cloud api turn status reuses AgentRun result-sync trace refresh (#1422)",
|
||||
}
|
||||
});
|
||||
|
||||
test("cloud api turn status skips AgentRun refresh for complete terminal evidence (#1422)", async () => {
|
||||
const calls = [];
|
||||
const traceId = "trc_issue1422_terminal_fast_path";
|
||||
const runId = "run_issue1422_terminal_fast_path";
|
||||
const commandId = "cmd_issue1422_terminal_fast_path";
|
||||
const finalText = "终态结果已经由 AgentRun command result 投影完成。";
|
||||
const agentRunServer = createHttpServer(async (request, response) => {
|
||||
const url = new URL(request.url || "/", "http://127.0.0.1");
|
||||
calls.push({ method: request.method, path: url.pathname, search: url.search });
|
||||
response.writeHead(500, { "content-type": "application/json" });
|
||||
response.end(`${JSON.stringify({ ok: false, message: `unexpected refresh ${request.method} ${url.pathname}` })}\n`);
|
||||
});
|
||||
await new Promise((resolve) => agentRunServer.listen(0, "127.0.0.1", resolve));
|
||||
|
||||
const agentRunPort = agentRunServer.address().port;
|
||||
const codeAgentChatResults = new Map([[traceId, {
|
||||
accepted: true,
|
||||
status: "completed",
|
||||
shortConnection: true,
|
||||
traceId,
|
||||
conversationId: "cnv_issue1422_terminal_fast_path",
|
||||
sessionId: "ses_issue1422_terminal_fast_path",
|
||||
threadId: "thread-issue1422-terminal-fast-path",
|
||||
ownerUserId: TEST_AGENT_ACTOR.id,
|
||||
ownerRole: TEST_AGENT_ACTOR.role,
|
||||
finalResponse: { text: finalText, textChars: finalText.length, role: "assistant", status: "completed", traceId, valuesPrinted: false },
|
||||
traceSummary: {
|
||||
traceId,
|
||||
source: "agentrun-command-result",
|
||||
sourceEventCount: 3,
|
||||
terminalStatus: "completed",
|
||||
finalAssistantRow: { role: "assistant", status: "completed", textChars: finalText.length, textPreview: finalText, messageId: `msg_${traceId.slice(4)}`, valuesPrinted: false },
|
||||
agentRun: { runId, commandId, lastSeq: 3, valuesPrinted: false },
|
||||
valuesPrinted: false
|
||||
},
|
||||
agentRun: {
|
||||
adapter: "agentrun-v01",
|
||||
managerUrl: `http://127.0.0.1:${agentRunPort}`,
|
||||
runId,
|
||||
commandId,
|
||||
traceId,
|
||||
lastSeq: 3,
|
||||
status: "completed",
|
||||
commandState: "completed",
|
||||
terminalStatus: "completed",
|
||||
providerTrace: { traceId, runId, commandId, terminalStatus: "completed", valuesPrinted: false },
|
||||
valuesPrinted: false
|
||||
},
|
||||
providerTrace: { traceId, runId, commandId, terminalStatus: "completed", valuesPrinted: false },
|
||||
valuesPrinted: false
|
||||
}]]);
|
||||
const server = createCloudApiServer({
|
||||
traceStore: createCodeAgentTraceStore(),
|
||||
codeAgentChatResults,
|
||||
env: {
|
||||
HWLAB_CODE_AGENT_ADAPTER: "agentrun-v01",
|
||||
AGENTRUN_MGR_URL: `http://127.0.0.1:${agentRunPort}`,
|
||||
HWLAB_CODE_AGENT_AGENTRUN_ALLOW_NON_K3S_URL: "1",
|
||||
HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID: "D601",
|
||||
HWLAB_ENVIRONMENT: "v02",
|
||||
HWLAB_GITOPS_PROFILE: "v02"
|
||||
},
|
||||
accessController: {
|
||||
required: false,
|
||||
async authenticate() {
|
||||
return { ok: true, actor: TEST_AGENT_ACTOR, session: TEST_AUTH_SESSION };
|
||||
}
|
||||
}
|
||||
});
|
||||
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
||||
|
||||
try {
|
||||
const { port } = server.address();
|
||||
const response = await fetch(`http://127.0.0.1:${port}/v1/agent/turns/${traceId}`, {
|
||||
headers: { cookie: "hwlab_session=test-stub-session" }
|
||||
});
|
||||
assert.equal(response.status, 200);
|
||||
const body = await response.json();
|
||||
assert.equal(body.status, "completed");
|
||||
assert.equal(body.terminal, true);
|
||||
assert.equal(body.finalResponse.text, finalText);
|
||||
assert.equal(body.traceSummary.agentRun.commandId, commandId);
|
||||
assert.deepEqual(calls, []);
|
||||
} finally {
|
||||
await new Promise((resolve, reject) => {
|
||||
server.close((error) => (error ? reject(error) : resolve()));
|
||||
});
|
||||
await new Promise((resolve, reject) => {
|
||||
agentRunServer.close((error) => (error ? reject(error) : resolve()));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test("cloud api repairs historical same-session AgentRun trace after lastTraceId advances (#955)", async () => {
|
||||
const calls = [];
|
||||
const ownerSessions = new Map();
|
||||
|
||||
@@ -1219,7 +1219,7 @@ async function resolveCodeAgentTurnStatusSnapshot(traceId, options) {
|
||||
try {
|
||||
const synced = await syncAgentRunChatResult({ traceId, currentResult: result, options: refreshOptions, traceStore });
|
||||
result = synced.result ?? result;
|
||||
turnRefreshSatisfiedByResultSync = synced.eventsRefreshed === true || synced.resultSynced === true;
|
||||
turnRefreshSatisfiedByResultSync = synced.eventsRefreshed === true || synced.resultSynced === true || synced.terminalRefreshSkipped === true;
|
||||
if (result && !canAccessOwnedResult(result, options.actor)) return forbiddenTurnSnapshot(traceId);
|
||||
if (result && isTraceCommandTerminalStatus(result.status)) {
|
||||
await finalizeCodeAgentBillingUsage({ payload: result, params: result, options });
|
||||
@@ -1286,6 +1286,7 @@ function codeAgentTurnStatusRefreshOptions(options = {}) {
|
||||
const timeoutMs = Math.max(1, Math.min(configuredBudgetMs, upstreamTimeoutMs));
|
||||
return {
|
||||
...options,
|
||||
skipAgentRunTerminalRefreshIfComplete: true,
|
||||
env: {
|
||||
...env,
|
||||
HWLAB_CODE_AGENT_AGENTRUN_HTTP_TIMEOUT_MS: String(timeoutMs),
|
||||
|
||||
Reference in New Issue
Block a user