Merge pull request #1501 from pikasTech/fix/issue-1422-trace-read-effects-async
fix: avoid blocking terminal trace reads
This commit is contained in:
@@ -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 () => {
|
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";
|
||||||
|
|||||||
@@ -2564,9 +2564,7 @@ export async function handleCodeAgentTraceHttp(request, response, url, options)
|
|||||||
agentRunResult = synced.result ?? agentRunResult;
|
agentRunResult = synced.result ?? agentRunResult;
|
||||||
}
|
}
|
||||||
if (isTraceCommandTerminalStatus(agentRunResult?.status)) {
|
if (isTraceCommandTerminalStatus(agentRunResult?.status)) {
|
||||||
await finalizeCodeAgentBillingUsage({ payload: agentRunResult, params: agentRunResult, options: requestOptions });
|
scheduleCodeAgentTerminalTurnStatusEffects({ payload: agentRunResult, params: agentRunResult, options: requestOptions, preserveLastTraceId: true });
|
||||||
recordCodeAgentConversationFact(agentRunResult, requestOptions);
|
|
||||||
await recordCodeAgentSessionOwner({ payload: agentRunResult, params: agentRunResult, options: requestOptions, status: codeAgentOwnerStatusForResult(agentRunResult), preserveLastTraceId: true });
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
refreshError = error;
|
refreshError = error;
|
||||||
|
|||||||
Reference in New Issue
Block a user