131 lines
5.4 KiB
TypeScript
131 lines
5.4 KiB
TypeScript
// SPEC: PJ2026-0104010803 Workbench唯一投影 draft-2026-06-19-p1-agentrun-incremental-cursor; PJ2026-01060505 Workbench Performance draft-2026-06-17-p0.
|
|
// Responsibility: Focused AgentRun projection polling/backoff regressions for Cloud API chat result sync.
|
|
|
|
import assert from "node:assert/strict";
|
|
import { createServer as createHttpServer } from "node:http";
|
|
import { test } from "bun:test";
|
|
|
|
import { syncAgentRunChatResult } from "./code-agent-agentrun-adapter.ts";
|
|
import { createCodeAgentTraceStore } from "./code-agent-trace-store.ts";
|
|
|
|
test("AgentRun sync stops upstream polling when terminal command result is already sealed (#1445)", async () => {
|
|
const traceId = "trc_1445_sealed_terminal";
|
|
const runId = "run_1445_sealed_terminal";
|
|
const commandId = "cmd_1445_sealed_terminal";
|
|
const traceStore = createCodeAgentTraceStore();
|
|
let fetched = false;
|
|
const synced = await syncAgentRunChatResult({
|
|
traceId,
|
|
currentResult: {
|
|
ok: true,
|
|
status: "completed",
|
|
traceId,
|
|
finalResponse: { text: "sealed final response", traceId },
|
|
traceSummary: { source: "agentrun-command-result", traceId, agentRun: { runId, commandId, lastSeq: 17 } },
|
|
agentRun: {
|
|
adapter: "agentrun-v01",
|
|
runId,
|
|
commandId,
|
|
traceId,
|
|
status: "completed",
|
|
runStatus: "completed",
|
|
commandState: "completed",
|
|
terminalStatus: "completed",
|
|
providerTrace: { traceId, runId, commandId, valuesPrinted: false },
|
|
lastSeq: 17,
|
|
managerUrl: "http://127.0.0.1:1",
|
|
valuesPrinted: false
|
|
},
|
|
valuesPrinted: false
|
|
},
|
|
options: {
|
|
fetchImpl: async () => {
|
|
fetched = true;
|
|
throw new Error("sealed terminal result should not fetch AgentRun");
|
|
},
|
|
env: { HWLAB_CODE_AGENT_AGENTRUN_ALLOW_NON_K3S_URL: "1" }
|
|
},
|
|
traceStore
|
|
});
|
|
|
|
assert.equal(fetched, false);
|
|
assert.equal(synced.terminalRefreshSkipped, true);
|
|
assert.equal(synced.resultSynced, false);
|
|
assert.equal(synced.polling.rootCause, "terminal_result_sealed");
|
|
assert.equal(synced.polling.backoffMs, 0);
|
|
assert.equal(synced.polling.terminalConsistencyGap, 0);
|
|
});
|
|
|
|
test("AgentRun events rate limit writes durable projection backoff (#1445)", async () => {
|
|
const traceId = "trc_1445_events_rate_limit";
|
|
const runId = "run_1445_events_rate_limit";
|
|
const commandId = "cmd_1445_events_rate_limit";
|
|
const calls = [];
|
|
const writes = [];
|
|
const runtimeStore = {
|
|
async getWorkbenchProjectionState() {
|
|
return { projectionState: { traceId, sourceRunId: runId, sourceCommandId: commandId, lastSourceSeq: 40, pollCount: 2, noProgressPollCount: 1, resultSyncState: "not_started", createdAt: "2026-07-02T00:00:00.000Z" } };
|
|
},
|
|
async writeWorkbenchProjectionState(input = {}) {
|
|
writes.push(input.projectionState);
|
|
return { written: true, projectionState: input.projectionState };
|
|
}
|
|
};
|
|
const agentRunServer = createHttpServer(async (request, response) => {
|
|
const url = new URL(request.url || "/", "http://127.0.0.1");
|
|
calls.push(`${request.method} ${url.pathname}${url.search}`);
|
|
if (request.method === "GET" && url.pathname === `/api/v1/runs/${runId}/events`) {
|
|
response.writeHead(429, { "content-type": "application/json" });
|
|
response.end(`${JSON.stringify({ ok: false, failureKind: "provider_rate_limited", message: "provider rate limited; retry later" })}\n`);
|
|
return;
|
|
}
|
|
response.writeHead(404, { "content-type": "application/json" });
|
|
response.end(`${JSON.stringify({ ok: false, message: `unexpected ${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();
|
|
try {
|
|
await assert.rejects(
|
|
syncAgentRunChatResult({
|
|
traceId,
|
|
currentResult: {
|
|
status: "running",
|
|
traceId,
|
|
agentRun: { adapter: "agentrun-v01", runId, commandId, traceId, providerTrace: { traceId }, lastSeq: 40, managerUrl: `http://127.0.0.1:${agentRunPort}`, valuesPrinted: false },
|
|
valuesPrinted: false
|
|
},
|
|
options: {
|
|
runtimeStore,
|
|
env: {
|
|
HWLAB_CODE_AGENT_AGENTRUN_ALLOW_NON_K3S_URL: "1",
|
|
HWLAB_CODE_AGENT_AGENTRUN_PROJECTION_SYNC_BACKOFF_INITIAL_MS: "1000",
|
|
HWLAB_CODE_AGENT_AGENTRUN_HTTP_TIMEOUT_MS: "2500"
|
|
}
|
|
},
|
|
traceStore
|
|
}),
|
|
(error) => {
|
|
assert.equal(error.statusCode, 429);
|
|
assert.equal(error.polling.rootCause, "provider_rate_limited");
|
|
assert.equal(error.polling.backoffReason, "provider_retry_or_rate_limit");
|
|
assert.equal(error.polling.backoffMs, 2000);
|
|
assert.ok(error.polling.nextRetryAt);
|
|
return true;
|
|
}
|
|
);
|
|
|
|
assert.equal(calls.length, 1);
|
|
assert.match(calls[0], /afterSeq=40/u);
|
|
assert.equal(writes.length, 1);
|
|
assert.equal(writes[0].projectionStatus, "degraded");
|
|
assert.equal(writes[0].projectionHealth, "degraded");
|
|
assert.equal(writes[0].backoffMs, 2000);
|
|
assert.equal(writes[0].backoffReason, "provider_retry_or_rate_limit");
|
|
assert.equal(writes[0].rootCause, "provider_rate_limited");
|
|
assert.ok(writes[0].nextRetryAt);
|
|
} finally {
|
|
await new Promise((resolve, reject) => agentRunServer.close((error) => (error ? reject(error) : resolve())));
|
|
}
|
|
});
|