Merge pull request #1498 from pikasTech/fix/issue-1422-trace-terminal-refresh-skip
fix: skip cached terminal trace refresh
This commit is contained in:
@@ -2396,6 +2396,120 @@ test("cloud api turn status reuses request session lookup for project check and
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("cloud api trace skips AgentRun refresh when terminal snapshot is already complete (#1422)", async () => {
|
||||||
|
const calls = [];
|
||||||
|
const traceId = "trc_issue1422_trace_terminal_snapshot";
|
||||||
|
const runId = "run_issue1422_trace_terminal_snapshot";
|
||||||
|
const commandId = "cmd_issue1422_trace_terminal_snapshot";
|
||||||
|
const finalText = "trace endpoint should reuse the local terminal snapshot.";
|
||||||
|
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 traceStore = createCodeAgentTraceStore();
|
||||||
|
traceStore.append(traceId, {
|
||||||
|
type: "backend",
|
||||||
|
status: "running",
|
||||||
|
label: "agentrun:backend:runner-job-created",
|
||||||
|
source: "agentrun",
|
||||||
|
sourceSeq: 1,
|
||||||
|
runId,
|
||||||
|
commandId,
|
||||||
|
valuesPrinted: false
|
||||||
|
});
|
||||||
|
traceStore.append(traceId, {
|
||||||
|
type: "result",
|
||||||
|
status: "completed",
|
||||||
|
label: "agentrun:terminal:completed",
|
||||||
|
source: "agentrun",
|
||||||
|
sourceSeq: 3,
|
||||||
|
runId,
|
||||||
|
commandId,
|
||||||
|
terminal: true,
|
||||||
|
valuesPrinted: false
|
||||||
|
});
|
||||||
|
const codeAgentChatResults = new Map([[traceId, {
|
||||||
|
accepted: true,
|
||||||
|
status: "completed",
|
||||||
|
shortConnection: true,
|
||||||
|
traceId,
|
||||||
|
conversationId: "cnv_issue1422_trace_terminal_snapshot",
|
||||||
|
sessionId: "ses_issue1422_trace_terminal_snapshot",
|
||||||
|
threadId: "thread-issue1422-trace-terminal-snapshot",
|
||||||
|
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",
|
||||||
|
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,
|
||||||
|
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/traces/${traceId}?limit=1`, {
|
||||||
|
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.eventCount, 2);
|
||||||
|
assert.equal(body.agentRun.commandId, commandId);
|
||||||
|
assert.equal(body.finalResponse.text, finalText);
|
||||||
|
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 turn status skips AgentRun refresh for complete terminal evidence (#1422)", async () => {
|
test("cloud api turn status skips AgentRun refresh for complete terminal evidence (#1422)", async () => {
|
||||||
const calls = [];
|
const calls = [];
|
||||||
const traceId = "trc_issue1422_terminal_fast_path";
|
const traceId = "trc_issue1422_terminal_fast_path";
|
||||||
|
|||||||
@@ -2553,9 +2553,13 @@ export async function handleCodeAgentTraceHttp(request, response, url, options)
|
|||||||
}
|
}
|
||||||
if (agentRunResult?.agentRun) {
|
if (agentRunResult?.agentRun) {
|
||||||
try {
|
try {
|
||||||
const snapshot = await refreshAgentRunTrace({ traceId, result: agentRunResult, options: requestOptions, traceStore });
|
const existingSnapshot = traceStore.snapshot(traceId);
|
||||||
|
const terminalSnapshotRefreshSatisfied = traceRefreshSatisfiedByTerminalSnapshot(agentRunResult, existingSnapshot);
|
||||||
|
const snapshot = terminalSnapshotRefreshSatisfied
|
||||||
|
? existingSnapshot
|
||||||
|
: await refreshAgentRunTrace({ traceId, result: agentRunResult, options: requestOptions, traceStore });
|
||||||
agentRunResult = requestOptions.codeAgentChatResults?.get?.(traceId) ?? agentRunResult;
|
agentRunResult = requestOptions.codeAgentChatResults?.get?.(traceId) ?? agentRunResult;
|
||||||
if (traceNeedsCommandResultSync(agentRunResult, snapshot)) {
|
if (!terminalSnapshotRefreshSatisfied && traceNeedsCommandResultSync(agentRunResult, snapshot)) {
|
||||||
const synced = await syncAgentRunChatResult({ traceId, currentResult: agentRunResult, options: requestOptions, traceStore, appendResultEvent: false, refreshEvents: false, forceResultSync: true });
|
const synced = await syncAgentRunChatResult({ traceId, currentResult: agentRunResult, options: requestOptions, traceStore, appendResultEvent: false, refreshEvents: false, forceResultSync: true });
|
||||||
agentRunResult = synced.result ?? agentRunResult;
|
agentRunResult = synced.result ?? agentRunResult;
|
||||||
}
|
}
|
||||||
@@ -2644,6 +2648,27 @@ function snapshotHasTerminalEvent(snapshot) {
|
|||||||
return events.some((event) => event?.terminal === true || (event?.type === "result" && isTraceCommandTerminalStatus(event?.status)));
|
return events.some((event) => event?.terminal === true || (event?.type === "result" && isTraceCommandTerminalStatus(event?.status)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function traceRefreshSatisfiedByTerminalSnapshot(result, snapshot) {
|
||||||
|
if (!result?.agentRun || snapshot?.status === "missing") return false;
|
||||||
|
const status = String(result.status ?? result.agentRun.terminalStatus ?? result.agentRun.commandState ?? result.agentRun.status ?? "");
|
||||||
|
if (!isTraceCommandTerminalStatus(status)) return false;
|
||||||
|
const events = Array.isArray(snapshot?.events) ? snapshot.events : [];
|
||||||
|
if (events.length === 0) return false;
|
||||||
|
const commandId = textValue(result.agentRun.commandId);
|
||||||
|
const terminalEvents = events.filter((event) => {
|
||||||
|
if (!(event?.terminal === true || (event?.type === "result" && isTraceCommandTerminalStatus(event?.status)))) return false;
|
||||||
|
return !commandId || textValue(event?.commandId) === commandId;
|
||||||
|
});
|
||||||
|
if (terminalEvents.length === 0) return false;
|
||||||
|
const expectedLastSeq = Number(result.agentRun.lastSeq ?? result.traceSummary?.agentRun?.lastSeq ?? 0);
|
||||||
|
if (!Number.isFinite(expectedLastSeq) || expectedLastSeq <= 0) return true;
|
||||||
|
const observedLastSeq = Math.max(0, ...events
|
||||||
|
.filter((event) => !commandId || textValue(event?.commandId) === commandId)
|
||||||
|
.map((event) => Number(event?.sourceSeq ?? 0))
|
||||||
|
.filter(Number.isFinite));
|
||||||
|
return observedLastSeq === 0 || observedLastSeq >= expectedLastSeq;
|
||||||
|
}
|
||||||
|
|
||||||
function traceSnapshotWithTerminalEvidence(snapshot, result, traceId, refreshError = null) {
|
function traceSnapshotWithTerminalEvidence(snapshot, result, traceId, refreshError = null) {
|
||||||
const evidence = agentRunTerminalTraceEvidence(result, traceId);
|
const evidence = agentRunTerminalTraceEvidence(result, traceId);
|
||||||
if (snapshot?.status !== "missing") return traceSnapshotWithAttachedTerminalEvidence(snapshot, evidence, refreshError);
|
if (snapshot?.status !== "missing") return traceSnapshotWithAttachedTerminalEvidence(snapshot, evidence, refreshError);
|
||||||
|
|||||||
Reference in New Issue
Block a user