fix: avoid blocking terminal trace reads

This commit is contained in:
lyon
2026-06-18 18:08:25 +08:00
parent 5604cd0f8a
commit e0c3b80c48
2 changed files with 138 additions and 3 deletions
+137
View File
@@ -2510,6 +2510,143 @@ test("cloud api trace skips AgentRun refresh when terminal snapshot is already c
}
});
test("cloud api trace schedules terminal side effects without blocking reads (#1422)", async () => {
const ownerCalls = [];
const factCalls = [];
let releaseOwnerWrite;
let ownerWriteReleased = false;
const ownerWrite = new Promise((resolve) => {
releaseOwnerWrite = () => {
if (ownerWriteReleased) return;
ownerWriteReleased = true;
resolve();
};
});
const traceId = "trc_issue1422_trace_effects_async";
const runId = "run_issue1422_trace_effects_async";
const commandId = "cmd_issue1422_trace_effects_async";
const finalText = "trace reads should not wait for terminal side effects.";
const traceStore = createCodeAgentTraceStore();
traceStore.append(traceId, {
type: "backend",
status: "running",
label: "agentrun:backend:run-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_effects_async",
sessionId: "ses_issue1422_trace_effects_async",
threadId: "thread-issue1422-trace-effects-async",
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:1",
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",
HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID: "D601",
HWLAB_ENVIRONMENT: "v02",
HWLAB_GITOPS_PROFILE: "v02"
},
sessionRegistry: {
recordFact(conversationId, fact) {
factCalls.push({ conversationId, fact });
return { ok: true };
}
},
accessController: {
required: false,
async authenticate() {
return { ok: true, actor: TEST_AGENT_ACTOR, session: TEST_AUTH_SESSION };
},
async recordAgentSessionOwner(input) {
ownerCalls.push(input);
await ownerWrite;
return { ok: true, sessionId: input.sessionId };
}
}
});
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
try {
const { port } = server.address();
const fetchTrace = async () => {
const response = await Promise.race([
fetch(`http://127.0.0.1:${port}/v1/agent/traces/${traceId}?limit=1`, {
headers: { cookie: "hwlab_session=test-stub-session" }
}),
delay(250).then(() => null)
]);
assert.ok(response, "trace read should not wait for terminal side effects to finish");
assert.equal(response.status, 200);
const body = await response.json();
assert.equal(body.status, "completed");
assert.equal(body.eventCount, 2);
assert.equal(body.finalResponse.text, finalText);
};
for (let i = 0; i < 2; i += 1) {
await fetchTrace();
}
for (let i = 0; i < 20 && ownerCalls.length < 1; i += 1) await delay(10);
assert.equal(ownerCalls.length, 1);
assert.equal(factCalls.length, 1);
assert.equal(codeAgentChatResults.get(traceId).turnStatusTerminalEffects.pending, true);
releaseOwnerWrite();
for (let i = 0; i < 20 && codeAgentChatResults.get(traceId).turnStatusTerminalEffects?.recorded !== true; i += 1) await delay(10);
assert.equal(codeAgentChatResults.get(traceId).turnStatusTerminalEffects.recorded, true);
} finally {
releaseOwnerWrite?.();
await new Promise((resolve, reject) => {
server.close((error) => (error ? reject(error) : resolve()));
});
}
});
test("cloud api turn status skips AgentRun refresh for complete terminal evidence (#1422)", async () => {
const calls = [];
const traceId = "trc_issue1422_terminal_fast_path";
+1 -3
View File
@@ -2564,9 +2564,7 @@ export async function handleCodeAgentTraceHttp(request, response, url, options)
agentRunResult = synced.result ?? agentRunResult;
}
if (isTraceCommandTerminalStatus(agentRunResult?.status)) {
await finalizeCodeAgentBillingUsage({ payload: agentRunResult, params: agentRunResult, options: requestOptions });
recordCodeAgentConversationFact(agentRunResult, requestOptions);
await recordCodeAgentSessionOwner({ payload: agentRunResult, params: agentRunResult, options: requestOptions, status: codeAgentOwnerStatusForResult(agentRunResult), preserveLastTraceId: true });
scheduleCodeAgentTerminalTurnStatusEffects({ payload: agentRunResult, params: agentRunResult, options: requestOptions, preserveLastTraceId: true });
}
} catch (error) {
refreshError = error;