8cc114f730
Split the oversized server AgentRun regression suite by responsibility and migrate the six stale runner-jobs mocks to command dispatch intents without changing the v0.3 baseline failure set. Co-Authored-By: Codex <noreply@openai.com>
1747 lines
81 KiB
TypeScript
1747 lines
81 KiB
TypeScript
// SPEC: PJ2026-0104010803 Workbench唯一投影 draft-2026-06-20-p2-terminal-outbox-recovery; PJ2026-01060505 Workbench Performance draft-2026-06-17-p0.
|
|
// Responsibility: Cloud API AgentRun session/profile continuity and durable projection resume regression tests.
|
|
|
|
import assert from "node:assert/strict";
|
|
import { createServer as createHttpServer } from "node:http";
|
|
import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { test } from "bun:test";
|
|
|
|
import { createCloudApiServer } from "./server.ts";
|
|
import { createCloudRuntimeStore } from "../db/runtime-store.ts";
|
|
import { validateCodeAgentChatSchema } from "./code-agent-chat.ts";
|
|
import { createCodexStdioSessionManager } from "./codex-stdio-session.ts";
|
|
import { createCodeAgentTraceStore } from "./code-agent-trace-store.ts";
|
|
import { submitAgentRunChatTurn, syncAgentRunChatResult } from "./code-agent-agentrun-adapter.ts";
|
|
import {
|
|
codexStdioChatFixture,
|
|
codexStdioReadyFixture,
|
|
createFakeAppServerClient,
|
|
createFakeCodexCommand,
|
|
createManualAgentSession,
|
|
delay,
|
|
ensureTestAgentAuth,
|
|
pollAgentResult,
|
|
postAgent,
|
|
postAgentRaw,
|
|
prepareFakeCodexHome
|
|
} from "./server-test-helpers.ts";
|
|
|
|
const TEST_AGENT_ACTOR = { id: "usr_agent_owner", username: "agent-owner", displayName: "Agent Owner", role: "user", status: "active" };
|
|
const TEST_AUTH_SESSION = { id: "auth_ses_agent_owner" };
|
|
|
|
function testAgentSessionRecord(input = {}) {
|
|
const sessionId = input.sessionId ?? input.id;
|
|
return {
|
|
id: sessionId,
|
|
sessionId,
|
|
projectId: input.projectId ?? "pikasTech/HWLAB",
|
|
agentId: input.agentId ?? "hwlab-code-agent",
|
|
status: input.status ?? "idle",
|
|
ownerUserId: input.ownerUserId ?? TEST_AGENT_ACTOR.id,
|
|
ownerRole: input.ownerRole ?? TEST_AGENT_ACTOR.role,
|
|
conversationId: input.conversationId ?? null,
|
|
threadId: input.threadId ?? null,
|
|
lastTraceId: input.traceId ?? input.lastTraceId ?? null,
|
|
session: input.session ?? {},
|
|
updatedAt: input.updatedAt ?? "2026-06-03T00:00:00.000Z"
|
|
};
|
|
}
|
|
|
|
test("cloud api AgentRun adapter keeps AgentRun sessions scoped to HWLAB session across backend profile switches", async () => {
|
|
const calls = [];
|
|
const hwlabSessionId = "ses_server-test-profile-switch";
|
|
const minimaxSessionId = "ses_agentrun_server_test_profile_switch";
|
|
const ownerSessions = new Map([[hwlabSessionId, testAgentSessionRecord({
|
|
sessionId: hwlabSessionId,
|
|
conversationId: "cnv_server-test-profile-switch",
|
|
threadId: "019e8078-db67-7750-a5d9-1a99f3abd445",
|
|
traceId: "trc_existing_deepseek",
|
|
status: "active",
|
|
session: {
|
|
agentRun: {
|
|
adapter: "agentrun-v01",
|
|
backendProfile: "deepseek",
|
|
runId: "run_existing_deepseek",
|
|
commandId: "cmd_existing_deepseek",
|
|
jobName: "agentrun-v01-runner-existing-deepseek",
|
|
namespace: "agentrun-v01",
|
|
sessionId: "ses_agentrun_server_test_profile_switch",
|
|
reuseEligible: true,
|
|
managerUrl: "http://127.0.0.1:1",
|
|
status: "runner-job-created"
|
|
}
|
|
}
|
|
})]]);
|
|
|
|
const agentRunServer = createHttpServer(async (request, response) => {
|
|
const url = new URL(request.url || "/", "http://127.0.0.1");
|
|
const chunks = [];
|
|
for await (const chunk of request) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
const body = chunks.length ? JSON.parse(Buffer.concat(chunks).toString("utf8")) : null;
|
|
calls.push({ method: request.method, path: url.pathname, body });
|
|
const send = (data) => {
|
|
response.writeHead(200, { "content-type": "application/json" });
|
|
response.end(`${JSON.stringify({ ok: true, data, traceId: "trc_fake_profile_switch" })}\n`);
|
|
};
|
|
if (request.method === "GET" && url.pathname === "/api/v1/runs/run_existing_deepseek") {
|
|
response.writeHead(500, { "content-type": "application/json" });
|
|
response.end(`${JSON.stringify({ ok: false, failureKind: "unexpected-reuse", message: "deepseek run must not be reused for minimax-m3" })}\n`);
|
|
return;
|
|
}
|
|
if (request.method === "POST" && url.pathname === "/api/v1/runs") {
|
|
assert.equal(body.backendProfile, "minimax-m3");
|
|
assert.equal(body.sessionRef.sessionId, minimaxSessionId);
|
|
assert.equal(body.sessionRef.threadId, undefined);
|
|
assert.equal(body.sessionRef.metadata.hwlabSessionId, hwlabSessionId);
|
|
assert.equal(body.sessionRef.metadata.agentRunSessionProfile, "minimax-m3");
|
|
return send({ id: "run_hwlab_profile_switch_m3", status: "pending", backendProfile: "minimax-m3", sessionRef: body.sessionRef, resourceBundleRef: body.resourceBundleRef });
|
|
}
|
|
if (request.method === "POST" && url.pathname === "/api/v1/runs/run_hwlab_profile_switch_m3/commands") {
|
|
assert.equal(body.payload.providerProfile, "minimax-m3");
|
|
assert.equal(body.payload.sessionId, minimaxSessionId);
|
|
assert.equal(body.payload.hwlabSessionId, hwlabSessionId);
|
|
assert.equal(body.payload.threadId, null);
|
|
assert.equal(body.dispatch?.kind, "kubernetes-job");
|
|
assert.equal(body.dispatch?.input?.commandId, undefined);
|
|
return send({
|
|
id: "cmd_hwlab_profile_switch_m3",
|
|
runId: "run_hwlab_profile_switch_m3",
|
|
state: "pending",
|
|
type: "turn",
|
|
seq: 1,
|
|
dispatchIntent: {
|
|
id: "dispatch_hwlab_profile_switch_m3",
|
|
state: "pending",
|
|
runnerJobId: "rjob_hwlab_profile_switch_m3",
|
|
attemptCount: 0,
|
|
durable: true
|
|
}
|
|
});
|
|
}
|
|
if (request.method === "GET" && url.pathname === "/api/v1/runs/run_hwlab_profile_switch_m3/commands") {
|
|
return send({ items: [
|
|
{ id: "cmd_hwlab_profile_switch_m3", runId: "run_hwlab_profile_switch_m3", state: "completed", type: "turn", seq: 1, idempotencyKey: "trc_server-test-agentrun-profile-switch", payload: { traceId: "trc_server-test-agentrun-profile-switch", conversationId: "cnv_server-test-profile-switch", hwlabSessionId, threadId: "019e8078-db67-7750-a5d9-1a99f3abd445", providerProfile: "minimax-m3" } }
|
|
] });
|
|
}
|
|
if (request.method === "GET" && url.pathname === "/api/v1/runs/run_hwlab_profile_switch_m3/events") {
|
|
return send({ items: [
|
|
{ id: "evt_profile_switch_1", runId: "run_hwlab_profile_switch_m3", seq: 1, type: "backend_status", payload: { phase: "runner-job-created", commandId: "cmd_hwlab_profile_switch_m3", attemptId: "attempt_hwlab_profile_switch_m3", jobName: "agentrun-v01-runner-profile-switch-m3", namespace: "agentrun-v01" }, createdAt: "2026-06-02T00:00:00.000Z" },
|
|
{ id: "evt_profile_switch_2", runId: "run_hwlab_profile_switch_m3", seq: 2, type: "assistant_message", payload: { commandId: "cmd_hwlab_profile_switch_m3", text: "AGENTRUN_HWLAB_MINIMAX_M3_OK" }, createdAt: "2026-06-02T00:00:01.000Z" },
|
|
{ id: "evt_profile_switch_3", runId: "run_hwlab_profile_switch_m3", seq: 3, type: "terminal_status", payload: { commandId: "cmd_hwlab_profile_switch_m3", terminalStatus: "completed" }, createdAt: "2026-06-02T00:00:02.000Z" }
|
|
] });
|
|
}
|
|
if (request.method === "GET" && url.pathname === "/api/v1/runs/run_hwlab_profile_switch_m3/commands/cmd_hwlab_profile_switch_m3/result") {
|
|
return send({
|
|
runId: "run_hwlab_profile_switch_m3",
|
|
commandId: "cmd_hwlab_profile_switch_m3",
|
|
attemptId: "attempt_hwlab_profile_switch_m3",
|
|
runnerId: "runner_hwlab_profile_switch_m3",
|
|
jobName: "agentrun-v01-runner-profile-switch-m3",
|
|
namespace: "agentrun-v01",
|
|
status: "completed",
|
|
runStatus: "completed",
|
|
commandState: "completed",
|
|
terminalStatus: "completed",
|
|
completed: true,
|
|
messageId: "msg_profile_switch_m3",
|
|
reply: "AGENTRUN_HWLAB_MINIMAX_M3_OK",
|
|
lastSeq: 3,
|
|
eventCount: 3,
|
|
sessionRef: { sessionId: minimaxSessionId, conversationId: "cnv_server-test-profile-switch", threadId: "019e8078-db67-7750-a5d9-1a99f3abd445" }
|
|
});
|
|
}
|
|
response.writeHead(404, { "content-type": "application/json" });
|
|
response.end(`${JSON.stringify({ ok: false, failureKind: "schema-invalid", message: `unexpected ${request.method} ${url.pathname}`, traceId: "trc_fake_profile_switch" })}\n`);
|
|
});
|
|
await new Promise((resolve) => agentRunServer.listen(0, "127.0.0.1", resolve));
|
|
|
|
const agentRunPort = agentRunServer.address().port;
|
|
const server = createCloudApiServer({
|
|
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_CODE_AGENT_AGENTRUN_SOURCE_COMMIT: "0123456789abcdef0123456789abcdef01234567",
|
|
HWLAB_CODE_AGENT_DEFAULT_PROVIDER_PROFILE: "deepseek",
|
|
HWLAB_ENVIRONMENT: "v02",
|
|
HWLAB_GITOPS_PROFILE: "v02"
|
|
},
|
|
accessController: {
|
|
required: false,
|
|
async authenticate() {
|
|
return { ok: true, actor: TEST_AGENT_ACTOR, session: TEST_AUTH_SESSION };
|
|
},
|
|
async recordAgentSessionOwner(input) {
|
|
const record = testAgentSessionRecord(input);
|
|
ownerSessions.set(record.id, record);
|
|
return record;
|
|
},
|
|
async getAgentSession(sessionId) {
|
|
return ownerSessions.get(sessionId) ?? null;
|
|
}
|
|
}
|
|
});
|
|
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
|
|
try {
|
|
const { port } = server.address();
|
|
const traceId = "trc_server-test-agentrun-profile-switch";
|
|
const submit = await fetch(`http://127.0.0.1:${port}/v1/agent/chat`, {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json", "x-trace-id": traceId, cookie: "hwlab_session=test-stub-session" },
|
|
body: JSON.stringify({
|
|
conversationId: "cnv_server-test-profile-switch",
|
|
sessionId: hwlabSessionId,
|
|
ownerUserId: "usr_agent_owner",
|
|
ownerRole: "user",
|
|
threadId: "019e8078-db67-7750-a5d9-1a99f3abd445",
|
|
providerProfile: "minimax-m3",
|
|
message: "Switch this HWLAB session to MiniMax-M3 through AgentRun"
|
|
})
|
|
});
|
|
assert.equal(submit.status, 202);
|
|
|
|
const payload = await pollAgentResult(port, traceId);
|
|
assert.equal(payload.status, "completed");
|
|
assert.equal(payload.sessionId, hwlabSessionId);
|
|
assert.equal(payload.agentRun.backendProfile, "minimax-m3");
|
|
assert.equal(payload.agentRun.runId, "run_hwlab_profile_switch_m3");
|
|
assert.equal(payload.agentRun.sessionId, minimaxSessionId);
|
|
assert.equal(calls.some((call) => call.method === "GET" && call.path === "/api/v1/runs/run_existing_deepseek"), false);
|
|
assert.equal(calls.filter((call) => call.method === "POST" && call.path === "/api/v1/runs").length, 1);
|
|
const stored = ownerSessions.get(hwlabSessionId);
|
|
assert.equal(stored.session.providerProfile, "minimax-m3");
|
|
assert.equal(stored.session.agentRun.backendProfile, "minimax-m3");
|
|
assert.equal(stored.session.agentRun.sessionId, minimaxSessionId);
|
|
assert.equal(calls.some((call) => call.path.endsWith("/runner-jobs")), false);
|
|
} 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 AgentRun adapter starts a fresh AgentRun session after a failed stale continuation when provider changes", async () => {
|
|
const calls = [];
|
|
const hwlabSessionId = "ses_server-test-stale-continuation-switch";
|
|
const conversationId = "cnv_server-test-stale-continuation-switch";
|
|
const staleThreadId = "thread_stale_bad_model";
|
|
const ownerSessions = new Map([[hwlabSessionId, testAgentSessionRecord({
|
|
sessionId: hwlabSessionId,
|
|
conversationId,
|
|
threadId: staleThreadId,
|
|
traceId: "trc_stale_bad_model",
|
|
status: "thread-resume-failed",
|
|
session: {
|
|
providerProfile: "codex-api",
|
|
codeAgentProviderProfile: "codex-api",
|
|
continuation: {
|
|
requiresFreshSession: true,
|
|
reasonCode: "provider-stream-disconnected",
|
|
staleThreadId,
|
|
traceId: "trc_stale_bad_model",
|
|
valuesRedacted: true
|
|
},
|
|
agentRun: {
|
|
adapter: "agentrun-v01",
|
|
backendProfile: "codex-api",
|
|
runId: "run_stale_bad_model",
|
|
commandId: "cmd_stale_bad_model",
|
|
sessionId: "ses_agentrun_stale_bad_model",
|
|
threadId: staleThreadId,
|
|
terminalStatus: "failed",
|
|
reuseEligible: false
|
|
}
|
|
}
|
|
})]]);
|
|
|
|
const agentRunServer = createHttpServer(async (request, response) => {
|
|
const url = new URL(request.url || "/", "http://127.0.0.1");
|
|
const chunks = [];
|
|
for await (const chunk of request) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
const body = chunks.length ? JSON.parse(Buffer.concat(chunks).toString("utf8")) : null;
|
|
calls.push({ method: request.method, path: url.pathname, body });
|
|
const send = (data, status = 200) => {
|
|
response.writeHead(status, { "content-type": "application/json" });
|
|
response.end(`${JSON.stringify({ ok: status < 400, data, traceId: "trc_fake_stale_continuation_switch" })}\n`);
|
|
};
|
|
if (request.method === "GET" && url.pathname === "/api/v1/runs/run_stale_bad_model") {
|
|
return send({ message: "stale run must not be reused after provider switch" }, 500);
|
|
}
|
|
if (request.method === "POST" && url.pathname === "/api/v1/sessions") {
|
|
assert.match(body.sessionId, /-reset-/u);
|
|
assert.equal(body.backendProfile, "dsflash-go");
|
|
return send({ sessionId: body.sessionId, backendProfile: body.backendProfile });
|
|
}
|
|
if (request.method === "POST" && url.pathname === "/api/v1/runs") {
|
|
assert.equal(body.backendProfile, "dsflash-go");
|
|
assert.match(body.sessionRef?.sessionId, /-reset-/u);
|
|
assert.equal(body.sessionRef?.threadId, undefined);
|
|
assert.equal(body.sessionRef?.metadata?.hwlabSessionId, hwlabSessionId);
|
|
assert.equal(body.sessionRef?.metadata?.agentRunSessionProfile, "dsflash-go");
|
|
return send({ id: "run_stale_recovery_dsflash", status: "pending", backendProfile: "dsflash-go", sessionRef: body.sessionRef, resourceBundleRef: body.resourceBundleRef });
|
|
}
|
|
if (request.method === "POST" && url.pathname === "/api/v1/runs/run_stale_recovery_dsflash/commands") {
|
|
assert.equal(body.payload.providerProfile, "dsflash-go");
|
|
assert.match(body.payload.sessionId, /-reset-/u);
|
|
assert.equal(body.payload.hwlabSessionId, hwlabSessionId);
|
|
assert.equal(body.payload.threadId, null);
|
|
assert.equal(body.dispatch?.kind, "kubernetes-job");
|
|
assert.equal(body.dispatch?.input?.commandId, undefined);
|
|
return send({
|
|
id: "cmd_stale_recovery_dsflash",
|
|
runId: "run_stale_recovery_dsflash",
|
|
state: "pending",
|
|
type: "turn",
|
|
seq: 1,
|
|
dispatchIntent: {
|
|
id: "dispatch_stale_recovery_dsflash",
|
|
state: "pending",
|
|
runnerJobId: "rjob_stale_recovery_dsflash",
|
|
attemptCount: 0,
|
|
durable: true
|
|
}
|
|
});
|
|
}
|
|
if (request.method === "GET" && url.pathname === "/api/v1/runs/run_stale_recovery_dsflash/commands") {
|
|
return send({ items: [
|
|
{ id: "cmd_stale_recovery_dsflash", runId: "run_stale_recovery_dsflash", state: "completed", type: "turn", seq: 1, idempotencyKey: "trc_server-test-stale-continuation-switch", payload: { traceId: "trc_server-test-stale-continuation-switch", conversationId, hwlabSessionId, threadId: "thread_recovered_dsflash", providerProfile: "dsflash-go" } }
|
|
] });
|
|
}
|
|
if (request.method === "GET" && url.pathname === "/api/v1/runs/run_stale_recovery_dsflash/events") {
|
|
return send({ items: [
|
|
{ id: "evt_stale_recovery_1", runId: "run_stale_recovery_dsflash", seq: 1, type: "backend_status", payload: { phase: "runner-job-created", commandId: "cmd_stale_recovery_dsflash" }, createdAt: "2026-06-30T00:00:00.000Z" },
|
|
{ id: "evt_stale_recovery_2", runId: "run_stale_recovery_dsflash", seq: 2, type: "assistant_message", payload: { commandId: "cmd_stale_recovery_dsflash", text: "AGENTRUN_STALE_RECOVERY_OK", final: true, replyAuthority: true }, createdAt: "2026-06-30T00:00:01.000Z" },
|
|
{ id: "evt_stale_recovery_3", runId: "run_stale_recovery_dsflash", seq: 3, type: "terminal_status", payload: { commandId: "cmd_stale_recovery_dsflash", terminalStatus: "completed" }, createdAt: "2026-06-30T00:00:02.000Z" }
|
|
] });
|
|
}
|
|
if (request.method === "GET" && url.pathname === "/api/v1/runs/run_stale_recovery_dsflash/commands/cmd_stale_recovery_dsflash/result") {
|
|
return send({
|
|
runId: "run_stale_recovery_dsflash",
|
|
commandId: "cmd_stale_recovery_dsflash",
|
|
attemptId: "attempt_stale_recovery_dsflash",
|
|
runnerId: "runner_stale_recovery_dsflash",
|
|
jobName: "agentrun-v01-runner-stale-recovery-dsflash",
|
|
namespace: "agentrun-v01",
|
|
status: "completed",
|
|
runStatus: "completed",
|
|
commandState: "completed",
|
|
terminalStatus: "completed",
|
|
completed: true,
|
|
reply: "AGENTRUN_STALE_RECOVERY_OK",
|
|
lastSeq: 3,
|
|
eventCount: 3,
|
|
sessionRef: { sessionId: calls.find((call) => call.method === "POST" && call.path === "/api/v1/runs")?.body?.sessionRef?.sessionId, conversationId, threadId: "thread_recovered_dsflash" }
|
|
});
|
|
}
|
|
response.writeHead(404, { "content-type": "application/json" });
|
|
response.end(`${JSON.stringify({ ok: false, failureKind: "schema-invalid", message: `unexpected ${request.method} ${url.pathname}`, traceId: "trc_fake_stale_continuation_switch" })}\n`);
|
|
});
|
|
await new Promise((resolve) => agentRunServer.listen(0, "127.0.0.1", resolve));
|
|
|
|
const agentRunPort = agentRunServer.address().port;
|
|
const server = createCloudApiServer({
|
|
traceStore: createCodeAgentTraceStore(),
|
|
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: "JD01",
|
|
HWLAB_CODE_AGENT_AGENTRUN_SOURCE_COMMIT: "0123456789abcdef0123456789abcdef01234567",
|
|
HWLAB_CODE_AGENT_DEFAULT_PROVIDER_PROFILE: "codex-api",
|
|
HWLAB_ENVIRONMENT: "v03",
|
|
HWLAB_GITOPS_PROFILE: "v03"
|
|
},
|
|
accessController: {
|
|
required: false,
|
|
async authenticate() {
|
|
return { ok: true, actor: TEST_AGENT_ACTOR, session: TEST_AUTH_SESSION };
|
|
},
|
|
async recordAgentSessionOwner(input) {
|
|
const previous = ownerSessions.get(input.sessionId) ?? null;
|
|
const record = testAgentSessionRecord({
|
|
...(previous ?? {}),
|
|
...input,
|
|
threadId: input.threadId ?? previous?.threadId ?? null,
|
|
session: { ...(previous?.session ?? {}), ...(input.session ?? {}) }
|
|
});
|
|
ownerSessions.set(record.id, record);
|
|
return record;
|
|
},
|
|
async getAgentSession(sessionId) {
|
|
return ownerSessions.get(sessionId) ?? null;
|
|
}
|
|
}
|
|
});
|
|
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
|
|
try {
|
|
const { port } = server.address();
|
|
const traceId = "trc_server-test-stale-continuation-switch";
|
|
const submit = await fetch(`http://127.0.0.1:${port}/v1/agent/chat`, {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json", "x-trace-id": traceId, cookie: "hwlab_session=test-stub-session" },
|
|
body: JSON.stringify({
|
|
conversationId,
|
|
sessionId: hwlabSessionId,
|
|
ownerUserId: "usr_agent_owner",
|
|
ownerRole: "user",
|
|
threadId: staleThreadId,
|
|
providerProfile: "dsflash-go",
|
|
message: "Continue after the first model failed"
|
|
})
|
|
});
|
|
assert.equal(submit.status, 202);
|
|
|
|
const payload = await pollAgentResult(port, traceId);
|
|
assert.equal(payload.status, "completed");
|
|
assert.equal(payload.finalResponse?.text ?? payload.reply?.content, "AGENTRUN_STALE_RECOVERY_OK");
|
|
assert.equal(payload.agentRun.backendProfile, "dsflash-go");
|
|
assert.match(payload.agentRun.sessionId, /-reset-/u);
|
|
assert.equal(calls.some((call) => call.method === "GET" && call.path === "/api/v1/runs/run_stale_bad_model"), false);
|
|
assert.equal(calls.filter((call) => call.method === "POST" && call.path === "/api/v1/runs").length, 1);
|
|
const commandCall = calls.find((call) => call.method === "POST" && call.path === "/api/v1/runs/run_stale_recovery_dsflash/commands");
|
|
assert.equal(commandCall.body.payload.threadId, null);
|
|
const stored = ownerSessions.get(hwlabSessionId);
|
|
assert.equal(stored.status, "completed");
|
|
assert.equal(stored.threadId, "thread_recovered_dsflash");
|
|
assert.equal(stored.session.providerProfile, "dsflash-go");
|
|
assert.equal(stored.session.continuation.requiresFreshSession, false);
|
|
assert.equal(calls.some((call) => call.path.endsWith("/runner-jobs")), false);
|
|
} 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 AgentRun adapter inherits session provider profile when a recovered Workbench page omits it", async () => {
|
|
const calls = [];
|
|
const hwlabSessionId = "ses_server-test-profile-inherit";
|
|
const agentRunSessionId = "ses_agentrun_server_test_profile_inherit";
|
|
const conversationId = "cnv_server-test-profile-inherit";
|
|
const threadId = "019e8078-db67-7750-a5d9-1a99f3abd446";
|
|
const ownerSessions = new Map([[hwlabSessionId, testAgentSessionRecord({
|
|
sessionId: hwlabSessionId,
|
|
conversationId,
|
|
threadId,
|
|
traceId: "trc_existing_dsflash_go",
|
|
status: "active",
|
|
session: {
|
|
providerProfile: "dsflash-go",
|
|
codeAgentProviderProfile: "dsflash-go",
|
|
agentRun: {
|
|
adapter: "agentrun-v01",
|
|
backendProfile: "dsflash-go",
|
|
runId: "run_existing_dsflash_go",
|
|
commandId: "cmd_existing_dsflash_go",
|
|
sessionId: agentRunSessionId,
|
|
terminalStatus: "completed",
|
|
reuseEligible: false
|
|
}
|
|
}
|
|
})]]);
|
|
|
|
const agentRunServer = createHttpServer(async (request, response) => {
|
|
const url = new URL(request.url || "/", "http://127.0.0.1");
|
|
const chunks = [];
|
|
for await (const chunk of request) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
const body = chunks.length ? JSON.parse(Buffer.concat(chunks).toString("utf8")) : null;
|
|
calls.push({ method: request.method, path: url.pathname, body });
|
|
const send = (data) => {
|
|
response.writeHead(200, { "content-type": "application/json" });
|
|
response.end(`${JSON.stringify({ ok: true, data, traceId: "trc_fake_profile_inherit" })}\n`);
|
|
};
|
|
if (request.method === "POST" && url.pathname === "/api/v1/runs") {
|
|
assert.equal(body.backendProfile, "dsflash-go");
|
|
assert.equal(body.sessionRef.sessionId, agentRunSessionId);
|
|
assert.equal(body.sessionRef.metadata.hwlabSessionId, hwlabSessionId);
|
|
assert.equal(body.sessionRef.metadata.agentRunSessionProfile, "dsflash-go");
|
|
return send({ id: "run_hwlab_profile_inherit", status: "pending", backendProfile: "dsflash-go", sessionRef: body.sessionRef, resourceBundleRef: body.resourceBundleRef });
|
|
}
|
|
if (request.method === "POST" && url.pathname === "/api/v1/runs/run_hwlab_profile_inherit/commands") {
|
|
assert.equal(body.payload.providerProfile, "dsflash-go");
|
|
assert.equal(body.payload.sessionId, agentRunSessionId);
|
|
assert.equal(body.payload.hwlabSessionId, hwlabSessionId);
|
|
assert.equal(body.payload.threadId, null);
|
|
assert.equal(body.dispatch?.kind, "kubernetes-job");
|
|
assert.equal(body.dispatch?.input?.commandId, undefined);
|
|
return send({
|
|
id: "cmd_hwlab_profile_inherit",
|
|
runId: "run_hwlab_profile_inherit",
|
|
state: "pending",
|
|
type: "turn",
|
|
seq: 1,
|
|
dispatchIntent: {
|
|
id: "dispatch_hwlab_profile_inherit",
|
|
state: "pending",
|
|
runnerJobId: "rjob_hwlab_profile_inherit",
|
|
attemptCount: 0,
|
|
durable: true
|
|
}
|
|
});
|
|
}
|
|
if (request.method === "GET" && url.pathname === "/api/v1/runs/run_hwlab_profile_inherit/commands") {
|
|
return send({ items: [
|
|
{ id: "cmd_hwlab_profile_inherit", runId: "run_hwlab_profile_inherit", state: "completed", type: "turn", seq: 1, idempotencyKey: "trc_server-test-agentrun-profile-inherit", payload: { traceId: "trc_server-test-agentrun-profile-inherit", conversationId, hwlabSessionId, threadId, providerProfile: "dsflash-go" } }
|
|
] });
|
|
}
|
|
if (request.method === "GET" && url.pathname === "/api/v1/runs/run_hwlab_profile_inherit/events") {
|
|
return send({ items: [
|
|
{ id: "evt_profile_inherit_1", runId: "run_hwlab_profile_inherit", seq: 1, type: "backend_status", payload: { phase: "runner-job-created", commandId: "cmd_hwlab_profile_inherit", attemptId: "attempt_hwlab_profile_inherit", jobName: "agentrun-v01-runner-profile-inherit", namespace: "agentrun-v01" }, createdAt: "2026-06-02T00:00:00.000Z" },
|
|
{ id: "evt_profile_inherit_2", runId: "run_hwlab_profile_inherit", seq: 2, type: "assistant_message", payload: { commandId: "cmd_hwlab_profile_inherit", text: "AGENTRUN_DSFLASH_GO_OK" }, createdAt: "2026-06-02T00:00:01.000Z" },
|
|
{ id: "evt_profile_inherit_3", runId: "run_hwlab_profile_inherit", seq: 3, type: "terminal_status", payload: { commandId: "cmd_hwlab_profile_inherit", terminalStatus: "completed" }, createdAt: "2026-06-02T00:00:02.000Z" }
|
|
] });
|
|
}
|
|
if (request.method === "GET" && url.pathname === "/api/v1/runs/run_hwlab_profile_inherit/commands/cmd_hwlab_profile_inherit/result") {
|
|
return send({
|
|
runId: "run_hwlab_profile_inherit",
|
|
commandId: "cmd_hwlab_profile_inherit",
|
|
attemptId: "attempt_hwlab_profile_inherit",
|
|
runnerId: "runner_hwlab_profile_inherit",
|
|
jobName: "agentrun-v01-runner-profile-inherit",
|
|
namespace: "agentrun-v01",
|
|
status: "completed",
|
|
runStatus: "completed",
|
|
commandState: "completed",
|
|
terminalStatus: "completed",
|
|
completed: true,
|
|
messageId: "msg_profile_inherit",
|
|
reply: "AGENTRUN_DSFLASH_GO_OK",
|
|
lastSeq: 3,
|
|
eventCount: 3,
|
|
sessionRef: { sessionId: agentRunSessionId, conversationId, threadId }
|
|
});
|
|
}
|
|
response.writeHead(404, { "content-type": "application/json" });
|
|
response.end(`${JSON.stringify({ ok: false, failureKind: "schema-invalid", message: `unexpected ${request.method} ${url.pathname}`, traceId: "trc_fake_profile_inherit" })}\n`);
|
|
});
|
|
await new Promise((resolve) => agentRunServer.listen(0, "127.0.0.1", resolve));
|
|
|
|
const agentRunPort = agentRunServer.address().port;
|
|
const server = createCloudApiServer({
|
|
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_CODE_AGENT_AGENTRUN_SOURCE_COMMIT: "0123456789abcdef0123456789abcdef01234567",
|
|
HWLAB_CODE_AGENT_DEFAULT_PROVIDER_PROFILE: "codex",
|
|
HWLAB_ENVIRONMENT: "v02",
|
|
HWLAB_GITOPS_PROFILE: "v02"
|
|
},
|
|
accessController: {
|
|
required: false,
|
|
async authenticate() {
|
|
return { ok: true, actor: TEST_AGENT_ACTOR, session: TEST_AUTH_SESSION };
|
|
},
|
|
async recordAgentSessionOwner(input) {
|
|
const previous = ownerSessions.get(input.sessionId)?.session ?? {};
|
|
const record = testAgentSessionRecord({ ...input, session: { ...previous, ...(input.session ?? {}) } });
|
|
ownerSessions.set(record.id, record);
|
|
return record;
|
|
},
|
|
async getAgentSession(sessionId) {
|
|
return ownerSessions.get(sessionId) ?? null;
|
|
}
|
|
}
|
|
});
|
|
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
|
|
try {
|
|
const { port } = server.address();
|
|
const traceId = "trc_server-test-agentrun-profile-inherit";
|
|
const submit = await fetch(`http://127.0.0.1:${port}/v1/agent/chat`, {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json", "x-trace-id": traceId, cookie: "hwlab_session=test-stub-session" },
|
|
body: JSON.stringify({
|
|
conversationId,
|
|
sessionId: hwlabSessionId,
|
|
ownerUserId: "usr_agent_owner",
|
|
ownerRole: "user",
|
|
message: "Continue with the Workbench session provider profile"
|
|
})
|
|
});
|
|
assert.equal(submit.status, 202);
|
|
|
|
const payload = await pollAgentResult(port, traceId);
|
|
assert.equal(payload.status, "completed");
|
|
assert.equal(payload.agentRun.backendProfile, "dsflash-go");
|
|
assert.equal(calls.filter((call) => call.method === "POST" && call.path === "/api/v1/runs").length, 1);
|
|
const stored = ownerSessions.get(hwlabSessionId);
|
|
assert.equal(stored.session.providerProfile, "dsflash-go");
|
|
assert.equal(stored.session.agentRun.backendProfile, "dsflash-go");
|
|
assert.equal(calls.some((call) => call.path.endsWith("/runner-jobs")), false);
|
|
} 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 AgentRun adapter rejects non-internal manager URLs by default", async () => {
|
|
const server = createCloudApiServer({
|
|
env: {
|
|
HWLAB_CODE_AGENT_ADAPTER: "agentrun-v01",
|
|
HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID: "D601",
|
|
AGENTRUN_MGR_URL: "http://74.48.78.17:8080"
|
|
}
|
|
});
|
|
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
|
|
try {
|
|
const { port } = server.address();
|
|
const manualSession = await createManualAgentSession(port, {
|
|
conversationId: "cnv_server-test-agentrun-public-url"
|
|
});
|
|
const response = await fetch(`http://127.0.0.1:${port}/v1/agent/chat`, {
|
|
method: "POST",
|
|
headers: { "content-type": "application/json", "x-trace-id": "trc_server-test-agentrun-public-url", cookie: manualSession.cookie },
|
|
body: JSON.stringify({ conversationId: manualSession.conversationId, sessionId: manualSession.sessionId, message: "must reject public manager url" })
|
|
});
|
|
assert.equal(response.status, 202);
|
|
const body = await response.json();
|
|
assert.equal(body.accepted, true);
|
|
const result = await pollAgentResult(port, "trc_server-test-agentrun-public-url");
|
|
assert.equal(result.status, "failed");
|
|
assert.match(JSON.stringify(result), /internal k3s Service DNS/u);
|
|
} finally {
|
|
await new Promise((resolve, reject) => server.close((error) => (error ? reject(error) : resolve())));
|
|
}
|
|
});
|
|
|
|
test("cloud api /v1/agent/chat records authenticated owner on agent session", async () => {
|
|
const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-owner-"));
|
|
const codexHome = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-owner-codex-home-"));
|
|
const ownerRecords = [];
|
|
const ownerSessions = new Map([["ses_server-test-owner", testAgentSessionRecord({
|
|
sessionId: "ses_server-test-owner",
|
|
conversationId: "cnv_server-test-owner",
|
|
status: "idle"
|
|
})]]);
|
|
const server = createCloudApiServer({
|
|
env: {
|
|
PATH: process.env.PATH,
|
|
CODEX_HOME: codexHome,
|
|
HWLAB_CODE_AGENT_CODEX_WORKSPACE: workspace,
|
|
HWLAB_CODE_AGENT_CODEX_SANDBOX: "danger-full-access",
|
|
HWLAB_CODE_AGENT_PROVIDER: "codex-stdio",
|
|
HWLAB_CODE_AGENT_MODEL: "gpt-test",
|
|
OPENAI_API_KEY: "test-openai-key-material",
|
|
HWLAB_CODE_AGENT_OPENAI_BASE_URL: "http://127.0.0.1:65535/v1/responses"
|
|
},
|
|
accessController: {
|
|
required: true,
|
|
async authenticate() {
|
|
return { ok: true, actor: TEST_AGENT_ACTOR, session: TEST_AUTH_SESSION };
|
|
},
|
|
async recordAgentSessionOwner(input) {
|
|
const record = testAgentSessionRecord(input);
|
|
ownerRecords.push(input);
|
|
ownerSessions.set(record.id, record);
|
|
return record;
|
|
},
|
|
async getAgentSession(sessionId) {
|
|
return ownerSessions.get(sessionId) ?? null;
|
|
}
|
|
},
|
|
codexStdioManager: {
|
|
describe() { return { ...codexStdioReadyFixture({ workspace, codexHome }), sandbox: "danger-full-access" }; },
|
|
async probe() { return { ...codexStdioReadyFixture({ workspace, codexHome }), sandbox: "danger-full-access" }; },
|
|
async chat(params = {}) {
|
|
return {
|
|
...codexStdioChatFixture({ workspace, codexHome, params }),
|
|
sandbox: "danger-full-access"
|
|
};
|
|
},
|
|
cancel() {},
|
|
reapIdle() {}
|
|
}
|
|
});
|
|
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
|
|
try {
|
|
const { port } = server.address();
|
|
const payload = await postAgent(port, {
|
|
conversationId: "cnv_server-test-owner",
|
|
sessionId: "ses_server-test-owner",
|
|
traceId: "trc_server-test-owner",
|
|
message: "owner binding smoke"
|
|
});
|
|
validateCodeAgentChatSchema(payload);
|
|
assert.equal(payload.ownerUserId, "usr_agent_owner");
|
|
assert.equal(ownerRecords.length, 2);
|
|
assert.equal(ownerRecords[0].status, "running");
|
|
const terminalOwnerRecord = ownerRecords.at(-1);
|
|
assert.equal(terminalOwnerRecord.ownerUserId, "usr_agent_owner");
|
|
assert.equal(terminalOwnerRecord.sessionId, "ses_server_stdio_pwd");
|
|
assert.equal(terminalOwnerRecord.conversationId, "cnv_server-test-owner");
|
|
assert.equal(terminalOwnerRecord.traceId, "trc_server-test-owner");
|
|
assert.equal(terminalOwnerRecord.status, "completed");
|
|
assert.equal(terminalOwnerRecord.session.messageCount, 2);
|
|
assert.equal(terminalOwnerRecord.session.messages.length, 2);
|
|
assert.equal(terminalOwnerRecord.session.messages[0].role, "user");
|
|
assert.equal(terminalOwnerRecord.session.messages[0].text, "owner binding smoke");
|
|
assert.equal(terminalOwnerRecord.session.messages[0].status, "sent");
|
|
assert.equal(terminalOwnerRecord.session.messages[1].role, "agent");
|
|
assert.equal(terminalOwnerRecord.session.messages[1].status, "completed");
|
|
assert.equal(terminalOwnerRecord.session.messages[1].traceId, "trc_server-test-owner");
|
|
assert.equal(terminalOwnerRecord.session.firstUserMessagePreview, "owner binding smoke");
|
|
assert.equal(terminalOwnerRecord.session.valuesRedacted, true);
|
|
assert.equal(JSON.stringify(ownerRecords).includes("test-openai-key-material"), false);
|
|
} finally {
|
|
await new Promise((resolve, reject) => {
|
|
server.close((error) => (error ? reject(error) : resolve()));
|
|
});
|
|
await rm(workspace, { recursive: true, force: true });
|
|
await rm(codexHome, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("cloud api /v1/agent/chat/inspect maps conversation session and thread details to trace evidence", async () => {
|
|
const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-inspect-"));
|
|
const codexHome = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-inspect-codex-home-"));
|
|
const server = createCloudApiServer({
|
|
env: {
|
|
PATH: process.env.PATH,
|
|
CODEX_HOME: codexHome,
|
|
HWLAB_CODE_AGENT_CODEX_WORKSPACE: workspace,
|
|
HWLAB_CODE_AGENT_CODEX_SANDBOX: "danger-full-access",
|
|
HWLAB_CODE_AGENT_PROVIDER: "codex-stdio",
|
|
HWLAB_CODE_AGENT_MODEL: "gpt-test",
|
|
OPENAI_API_KEY: "test-openai-key-material",
|
|
HWLAB_CODE_AGENT_OPENAI_BASE_URL: "http://127.0.0.1:65535/v1/responses"
|
|
},
|
|
codexStdioManager: {
|
|
describe() {
|
|
return {
|
|
...codexStdioReadyFixture({ workspace, codexHome }),
|
|
sandbox: "danger-full-access",
|
|
recentSessions: [{
|
|
sessionId: "ses_server_stdio_pwd",
|
|
conversationId: "cnv_server-test-inspect",
|
|
threadId: "019e6c72-2373-75e3-9856-eff286db7163",
|
|
status: "idle",
|
|
currentTraceId: null,
|
|
lastTraceId: "trc_server-test-inspect",
|
|
workspace,
|
|
sandbox: "danger-full-access",
|
|
secretMaterialStored: false,
|
|
valuesRedacted: true
|
|
}]
|
|
};
|
|
},
|
|
async probe() {
|
|
return {
|
|
...codexStdioReadyFixture({ workspace, codexHome }),
|
|
sandbox: "danger-full-access"
|
|
};
|
|
},
|
|
async chat(params = {}) {
|
|
const base = codexStdioChatFixture({ workspace, codexHome, params });
|
|
return {
|
|
...base,
|
|
sandbox: "danger-full-access",
|
|
session: {
|
|
...base.session,
|
|
threadId: "019e6c72-2373-75e3-9856-eff286db7163",
|
|
sandbox: "danger-full-access"
|
|
},
|
|
sessionReuse: {
|
|
...base.sessionReuse,
|
|
threadId: "019e6c72-2373-75e3-9856-eff286db7163"
|
|
}
|
|
};
|
|
},
|
|
get(sessionId) {
|
|
if (sessionId !== "ses_server_stdio_pwd") return null;
|
|
return {
|
|
sessionId,
|
|
conversationId: "cnv_server-test-inspect",
|
|
threadId: "019e6c72-2373-75e3-9856-eff286db7163",
|
|
status: "idle",
|
|
lastTraceId: "trc_server-test-inspect",
|
|
workspace,
|
|
sandbox: "danger-full-access",
|
|
secretMaterialStored: false,
|
|
valuesRedacted: true
|
|
};
|
|
},
|
|
cancel() {},
|
|
reapIdle() {}
|
|
}
|
|
});
|
|
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
|
|
try {
|
|
const { port } = server.address();
|
|
const traceId = "trc_server-test-inspect";
|
|
const manualSession = await createManualAgentSession(port, {
|
|
conversationId: "cnv_server-test-inspect"
|
|
});
|
|
const submit = await fetch(`http://127.0.0.1:${port}/v1/agent/chat`, {
|
|
method: "POST",
|
|
headers: {
|
|
"content-type": "application/json",
|
|
cookie: manualSession.cookie,
|
|
"x-trace-id": traceId,
|
|
"prefer": "respond-async",
|
|
"x-hwlab-short-connection": "1"
|
|
},
|
|
body: JSON.stringify({
|
|
conversationId: manualSession.conversationId,
|
|
sessionId: manualSession.sessionId,
|
|
message: "inspect mapping smoke"
|
|
})
|
|
});
|
|
assert.equal(submit.status, 202);
|
|
const payload = await pollAgentResult(port, traceId);
|
|
assert.equal(payload.status, "completed");
|
|
|
|
const inspect = await fetch(`http://127.0.0.1:${port}/v1/agent/chat/inspect?conversationId=cnv_server-test-inspect&sessionId=${manualSession.sessionId}&threadId=019e6c72-2373-75e3-9856-eff286db7163`);
|
|
assert.equal(inspect.status, 200);
|
|
const body = await inspect.json();
|
|
assert.equal(body.ok, true);
|
|
assert.equal(body.status, "found");
|
|
assert.equal(body.latestTraceId, traceId);
|
|
assert.equal(body.traceUrl, `/v1/agent/traces/${traceId}`);
|
|
assert.equal(body.resultUrl, `/v1/agent/chat/result/${traceId}`);
|
|
assert.equal(body.query.sessionId, manualSession.sessionId);
|
|
assert.equal(body.query.threadId, "019e6c72-2373-75e3-9856-eff286db7163");
|
|
assert.equal(body.session.sessionId, "ses_server_stdio_pwd");
|
|
assert.equal(body.session.threadId, "019e6c72-2373-75e3-9856-eff286db7163");
|
|
assert.equal(body.conversationFacts.conversationId, "cnv_server-test-inspect");
|
|
assert.equal(body.runnerTrace.traceId, traceId);
|
|
assert.equal(body.valuesRedacted, true);
|
|
assert.equal(body.secretMaterialStored, false);
|
|
assert.equal(JSON.stringify(body).includes("test-openai-key-material"), false);
|
|
} finally {
|
|
await new Promise((resolve, reject) => {
|
|
server.close((error) => (error ? reject(error) : resolve()));
|
|
});
|
|
await rm(workspace, { recursive: true, force: true });
|
|
await rm(codexHome, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("cloud api /v1/agent/chat/inspect returns not_found for empty conversation facts", async () => {
|
|
const server = createCloudApiServer();
|
|
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/chat/inspect?conversationId=cnv_server-test-inspect-missing&sessionId=ses_server_test_missing&threadId=019e6c72-2373-75e3-9856-eff286db7163&traceId=trc_server_test_missing`);
|
|
assert.equal(response.status, 404);
|
|
const body = await response.json();
|
|
assert.equal(body.ok, false);
|
|
assert.equal(body.status, "not_found");
|
|
assert.equal(body.latestTraceId, null);
|
|
assert.deepEqual(body.traceIds, []);
|
|
assert.equal(body.traceUrl, null);
|
|
assert.equal(body.resultUrl, null);
|
|
assert.equal(body.conversationFacts.turnCount, 0);
|
|
} finally {
|
|
await new Promise((resolve, reject) => {
|
|
server.close((error) => (error ? reject(error) : resolve()));
|
|
});
|
|
}
|
|
});
|
|
|
|
test("cloud api trace reports expired projection without AgentRun read-through when live trace store has expired (#1519)", async () => {
|
|
const ownerSessions = new Map();
|
|
const agentRunCalls = [];
|
|
const commandFinalText = "历史 trace 已过期,但 final response 来自 AgentRun command result。";
|
|
ownerSessions.set("ses_issue842_expired", testAgentSessionRecord({
|
|
sessionId: "ses_issue842_expired",
|
|
projectId: "prj_v02_code_agent",
|
|
conversationId: "cnv_issue842_expired",
|
|
threadId: "thread-issue-842-expired",
|
|
traceId: "trc_issue842_expired",
|
|
status: "active",
|
|
session: {
|
|
finalResponse: {
|
|
text: "旧 session snapshot final 不应作为历史 trace 权威。",
|
|
textChars: 30,
|
|
role: "assistant",
|
|
status: "completed",
|
|
traceId: "trc_issue842_expired",
|
|
source: "code-agent-result",
|
|
valuesPrinted: false
|
|
},
|
|
traceSummary: {
|
|
traceId: "trc_issue842_expired",
|
|
source: "stale-derived-session-evidence",
|
|
sourceEventCount: 26,
|
|
terminalStatus: "completed",
|
|
noiseEventCount: 4,
|
|
omittedNoiseCount: 4,
|
|
valuesPrinted: false
|
|
},
|
|
agentRun: {
|
|
adapter: "agentrun-v01",
|
|
runId: "run_issue842_expired",
|
|
commandId: "cmd_issue842_expired",
|
|
runnerId: "runner_issue842_expired",
|
|
jobName: "agentrun-v01-runner-issue842-expired",
|
|
namespace: "agentrun-v01",
|
|
terminalStatus: "completed",
|
|
valuesPrinted: false
|
|
},
|
|
valuesRedacted: true
|
|
}
|
|
}));
|
|
const agentRunServer = createHttpServer(async (request, response) => {
|
|
const url = new URL(request.url || "/", "http://127.0.0.1");
|
|
agentRunCalls.push({ method: request.method, path: url.pathname, search: url.search });
|
|
const send = (data) => {
|
|
response.writeHead(200, { "content-type": "application/json" });
|
|
response.end(`${JSON.stringify({ ok: true, data, traceId: "trc_fake_issue842_expired" })}\n`);
|
|
};
|
|
if (request.method === "GET" && url.pathname === "/api/v1/runs/run_issue842_expired/commands") {
|
|
return send({ items: [
|
|
{ id: "cmd_issue842_expired", runId: "run_issue842_expired", state: "completed", type: "turn", seq: 1, idempotencyKey: "trc_issue842_expired", payload: { traceId: "trc_issue842_expired", conversationId: "cnv_issue842_expired", hwlabSessionId: "ses_issue842_expired", threadId: "thread-issue-842-expired", providerProfile: "deepseek" } }
|
|
] });
|
|
}
|
|
if (request.method === "GET" && url.pathname === "/api/v1/runs/run_issue842_expired/events") {
|
|
return send({ items: [] });
|
|
}
|
|
if (request.method === "GET" && url.pathname === "/api/v1/runs/run_issue842_expired/commands/cmd_issue842_expired/result") {
|
|
return send({
|
|
runId: "run_issue842_expired",
|
|
commandId: "cmd_issue842_expired",
|
|
attemptId: "attempt_issue842_expired",
|
|
runnerId: "runner_issue842_expired",
|
|
jobName: "agentrun-v01-runner-issue842-expired",
|
|
namespace: "agentrun-v01",
|
|
status: "completed",
|
|
runStatus: "completed",
|
|
commandState: "completed",
|
|
terminalStatus: "completed",
|
|
completed: true,
|
|
reply: commandFinalText,
|
|
lastSeq: 26,
|
|
eventCount: 26,
|
|
sessionRef: { sessionId: "ses_agentrun_issue842_expired", conversationId: "cnv_issue842_expired", threadId: "thread-issue-842-expired" }
|
|
});
|
|
}
|
|
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();
|
|
const server = createCloudApiServer({
|
|
traceStore,
|
|
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 };
|
|
},
|
|
async getAgentSessionByTraceId(traceId) {
|
|
return [...ownerSessions.values()].find((session) => session.lastTraceId === traceId) ?? null;
|
|
}
|
|
}
|
|
});
|
|
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/trc_issue842_expired`, {
|
|
headers: { cookie: "hwlab_session=test-stub-session" }
|
|
});
|
|
assert.equal(response.status, 200);
|
|
const body = await response.json();
|
|
assert.equal(body.ok, false);
|
|
assert.equal(body.status, "missing");
|
|
assert.equal(body.traceStatus, "missing");
|
|
assert.equal(body.eventCount, 0);
|
|
assert.equal(body.projectionStatus, "unknown");
|
|
assert.equal(body.retention.liveTraceStore, "missing");
|
|
assert.equal(JSON.stringify(body).includes("旧 session snapshot final"), false);
|
|
assert.deepEqual(agentRunCalls, []);
|
|
assert.equal(JSON.stringify(body).includes("password"), false);
|
|
} 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 trace does not replay an earlier AgentRun command from GET after same-run lastSeq advances (#1519)", async () => {
|
|
const calls = [];
|
|
const firstTraceId = "trc_issue955_first_trace";
|
|
const codeAgentChatResults = new Map();
|
|
const runId = "run_issue955_replay";
|
|
const firstCommandId = "cmd_issue955_first";
|
|
const secondCommandId = "cmd_issue955_second";
|
|
const firstFinalText = "目前只有一个 HWPOD 可用:\n\n- d601-f103-v2 (board: D601-F103-V2 / STM32F103)\n\nAPI 返回 count=1, availableCount=1。";
|
|
const firstEvents = [
|
|
{ id: "evt_issue955_first_setup", runId, seq: 1, type: "backend_status", payload: { phase: "runner-job-created", commandId: firstCommandId, jobName: "agentrun-v01-runner-issue955-first", namespace: "agentrun-v01" }, createdAt: "2026-06-06T01:43:25.000Z" },
|
|
{ id: "evt_issue955_first_tool", runId, seq: 18, type: "tool_call", payload: { method: "item/completed", type: "commandExecution", toolName: "commandExecution", itemId: "call_issue955_hwpod_list", command: "/bin/sh -lc 'hwpod list --available'", status: "completed", exitCode: 0, outputSummary: '{"count":1,"availableCount":1,"ids":["d601-f103-v2"]}', commandId: firstCommandId }, createdAt: "2026-06-06T01:43:42.000Z" },
|
|
{ id: "evt_issue955_first_assistant", runId, seq: 27, type: "assistant_message", payload: { commandId: firstCommandId, itemId: "msg_item_0", text: firstFinalText }, createdAt: "2026-06-06T01:43:55.000Z" },
|
|
{ id: "evt_issue955_first_terminal", runId, seq: 35, type: "terminal_status", payload: { commandId: firstCommandId, terminalStatus: "completed" }, createdAt: "2026-06-06T01:43:56.000Z" }
|
|
];
|
|
const secondEvents = [
|
|
{ id: "evt_issue955_second_setup", runId, seq: 47, type: "backend_status", payload: { phase: "runner-claim-waiting-for-stale-lease" }, createdAt: "2026-06-06T01:45:56.000Z" },
|
|
{ id: "evt_issue955_second_assistant", runId, seq: 58, type: "assistant_message", payload: { commandId: secondCommandId, itemId: "msg_item_0", text: "编译成功!来总结一下结果。" }, createdAt: "2026-06-06T01:46:24.000Z" },
|
|
{ id: "evt_issue955_second_terminal", runId, seq: 66, type: "terminal_status", payload: { commandId: secondCommandId, terminalStatus: "completed" }, createdAt: "2026-06-06T01:46:28.000Z" }
|
|
];
|
|
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, afterSeq: url.searchParams.get("afterSeq") });
|
|
const send = (data) => {
|
|
response.writeHead(200, { "content-type": "application/json" });
|
|
response.end(`${JSON.stringify({ ok: true, data, traceId: "trc_fake_issue955_replay" })}\n`);
|
|
};
|
|
if (request.method === "GET" && url.pathname === `/api/v1/runs/${runId}/commands`) {
|
|
return send({ items: [
|
|
{ id: firstCommandId, runId, state: "completed", idempotencyKey: firstTraceId, createdAt: "2026-06-06T01:43:30.040Z", updatedAt: "2026-06-06T01:43:56.000Z", payload: { traceId: firstTraceId, conversationId: "cnv_issue955_replay", hwlabSessionId: "ses_issue955_replay", threadId: "thread-issue955-replay", providerProfile: "deepseek" } },
|
|
{ id: secondCommandId, runId, state: "completed", idempotencyKey: "trc_issue955_second_trace", createdAt: "2026-06-06T01:46:11.000Z", updatedAt: "2026-06-06T01:46:28.000Z", payload: { traceId: "trc_issue955_second_trace", conversationId: "cnv_issue955_replay", hwlabSessionId: "ses_issue955_replay", threadId: "thread-issue955-replay", providerProfile: "deepseek" } }
|
|
] });
|
|
}
|
|
if (request.method === "GET" && url.pathname === `/api/v1/runs/${runId}/commands/${firstCommandId}/result`) {
|
|
return send({ runId, commandId: firstCommandId, status: "completed", runStatus: "completed", commandState: "completed", terminalStatus: "completed", completed: true, reply: firstFinalText, lastSeq: 35, eventCount: 35, sessionRef: { sessionId: "ses_agentrun_deepseek_issue955_replay", conversationId: "cnv_issue955_replay", threadId: "thread-issue955-replay" } });
|
|
}
|
|
if (request.method === "GET" && url.pathname === `/api/v1/runs/${runId}/events`) {
|
|
return send({ items: [...firstEvents, ...secondEvents] });
|
|
}
|
|
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;
|
|
codeAgentChatResults.set(firstTraceId, {
|
|
accepted: true,
|
|
status: "completed",
|
|
shortConnection: true,
|
|
traceId: firstTraceId,
|
|
conversationId: "cnv_issue955_replay",
|
|
sessionId: "ses_issue955_replay",
|
|
threadId: "thread-issue955-replay",
|
|
ownerUserId: TEST_AGENT_ACTOR.id,
|
|
ownerRole: TEST_AGENT_ACTOR.role,
|
|
reply: { role: "assistant", content: firstFinalText },
|
|
finalResponse: { text: firstFinalText, textChars: firstFinalText.length, role: "assistant", status: "completed", traceId: firstTraceId, valuesPrinted: false },
|
|
traceSummary: { traceId: firstTraceId, source: "code-agent-derived-session-evidence", sourceEventCount: 41, terminalStatus: "completed", agentRun: { runId, commandId: firstCommandId, lastSeq: 35, valuesPrinted: false }, valuesPrinted: false },
|
|
agentRun: { adapter: "agentrun-v01", managerUrl: `http://127.0.0.1:${agentRunPort}`, runId, commandId: firstCommandId, lastSeq: 66, terminalStatus: "completed", valuesPrinted: false },
|
|
valuesPrinted: false
|
|
});
|
|
const traceStore = createCodeAgentTraceStore();
|
|
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/${firstTraceId}`, {
|
|
headers: { cookie: "hwlab_session=test-stub-session" }
|
|
});
|
|
assert.equal(response.status, 200);
|
|
const body = await response.json();
|
|
const text = JSON.stringify(body);
|
|
assert.equal(body.traceId, firstTraceId);
|
|
assert.equal(body.status, "expired");
|
|
assert.equal(body.projectionStatus, "caught-up");
|
|
assert.deepEqual(calls, []);
|
|
assert.equal(body.eventCount, 0);
|
|
assert.deepEqual(body.events, []);
|
|
assert.equal(body.finalResponse.text, firstFinalText);
|
|
assert.match(text, /d601-f103-v2/u);
|
|
assert.doesNotMatch(text, /编译成功/u);
|
|
} 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 running trace reports current projection without AgentRun event refresh (#1519)", async () => {
|
|
const calls = [];
|
|
const traceId = "trc_issue1000_running_trace";
|
|
const runId = "run_issue1000_running_trace";
|
|
const commandId = "cmd_issue1000_running_trace";
|
|
const codeAgentChatResults = new Map();
|
|
const events = [
|
|
{ id: "evt_issue1000_created", runId, seq: 1, type: "backend_status", payload: { phase: "runner-job-created", commandId, jobName: "agentrun-v01-runner-issue1000", namespace: "agentrun-v01" }, createdAt: "2026-06-06T08:34:30.000Z" },
|
|
{ id: "evt_issue1000_turn", runId, seq: 2, type: "backend_status", payload: { phase: "turn/started", commandId }, createdAt: "2026-06-06T08:34:31.000Z" },
|
|
{ id: "evt_issue1000_tool", runId, seq: 3, type: "tool_call", payload: { method: "item/completed", type: "commandExecution", toolName: "commandExecution", itemId: "call_issue1000_hwpod", command: "hwpod list --available", status: "completed", exitCode: 0, outputSummary: "available=1", commandId }, createdAt: "2026-06-06T08:34:32.000Z" },
|
|
{ id: "evt_issue1000_output", runId, seq: 4, type: "assistant_message", payload: { commandId, itemId: "msg_issue1000_running", text: "正在查看 HWPOD。" }, createdAt: "2026-06-06T08:34:33.000Z" }
|
|
];
|
|
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 });
|
|
const send = (data) => {
|
|
response.writeHead(200, { "content-type": "application/json" });
|
|
response.end(`${JSON.stringify({ ok: true, data, traceId: "trc_fake_issue1000_running" })}\n`);
|
|
};
|
|
if (request.method === "GET" && url.pathname === `/api/v1/runs/${runId}/events`) return send({ items: events });
|
|
if (request.method === "GET" && url.pathname === `/api/v1/runs/${runId}/commands/${commandId}/result`) {
|
|
return send({ runId, commandId, status: "running", runStatus: "running", commandState: "running", terminalStatus: null, completed: false, reply: null, lastSeq: 3, eventCount: 3 });
|
|
}
|
|
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;
|
|
codeAgentChatResults.set(traceId, {
|
|
accepted: true,
|
|
status: "running",
|
|
shortConnection: true,
|
|
traceId,
|
|
conversationId: "cnv_issue1000_running_trace",
|
|
sessionId: "ses_issue1000_running_trace",
|
|
threadId: "thread-issue1000-running-trace",
|
|
ownerUserId: TEST_AGENT_ACTOR.id,
|
|
ownerRole: TEST_AGENT_ACTOR.role,
|
|
agentRun: { adapter: "agentrun-v01", managerUrl: `http://127.0.0.1:${agentRunPort}`, runId, commandId, traceId, providerTrace: { traceId }, lastSeq: 0, status: "running", commandState: "running", 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/traces/${traceId}`, {
|
|
headers: { cookie: "hwlab_session=test-stub-session" }
|
|
});
|
|
assert.equal(response.status, 200);
|
|
const body = await response.json();
|
|
assert.equal(body.status, "missing");
|
|
assert.equal(body.projectionStatus, "projecting");
|
|
assert.equal(body.eventCount, 0);
|
|
assert.deepEqual(body.events, []);
|
|
assert.deepEqual(calls, []);
|
|
assert.equal(calls.some((call) => call.path === `/api/v1/runs/${runId}/commands/${commandId}/result`), false);
|
|
} 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 does not backfill terminal AgentRun projection on GET (#1585)", async () => {
|
|
const calls = [];
|
|
const traceId = "trc_issue1422_turn_status_fast_path";
|
|
const runId = "run_issue1422_turn_status_fast_path";
|
|
const commandId = "cmd_issue1422_turn_status_fast_path";
|
|
const events = [
|
|
{ id: "evt_issue1422_created", runId, seq: 1, type: "backend_status", payload: { phase: "runner-job-created", commandId }, createdAt: "2026-06-18T04:30:00.000Z" },
|
|
{ id: "evt_issue1422_terminal", runId, seq: 2, type: "terminal_status", payload: { commandId, terminalStatus: "completed" }, createdAt: "2026-06-18T04:30:01.000Z" }
|
|
];
|
|
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 });
|
|
const send = (data) => {
|
|
response.writeHead(200, { "content-type": "application/json" });
|
|
response.end(`${JSON.stringify({ ok: true, data, traceId: "trc_fake_issue1422_turn" })}\n`);
|
|
};
|
|
if (request.method === "GET" && url.pathname === `/api/v1/runs/${runId}/events`) return send({ items: events });
|
|
if (request.method === "GET" && url.pathname === `/api/v1/runs/${runId}/commands/${commandId}/result`) return send({
|
|
runId,
|
|
commandId,
|
|
status: "completed",
|
|
runStatus: "claimed",
|
|
commandState: "completed",
|
|
terminalStatus: "completed",
|
|
completed: true,
|
|
reply: { content: "issue 1555 read-side backfill completed" },
|
|
lastSeq: 2,
|
|
eventCount: 2
|
|
});
|
|
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 codeAgentChatResults = new Map([[traceId, {
|
|
accepted: true,
|
|
status: "running",
|
|
shortConnection: true,
|
|
traceId,
|
|
conversationId: "cnv_issue1422_turn_status",
|
|
sessionId: "ses_issue1422_turn_status",
|
|
threadId: "thread-issue1422-turn-status",
|
|
ownerUserId: TEST_AGENT_ACTOR.id,
|
|
ownerRole: TEST_AGENT_ACTOR.role,
|
|
agentRun: { adapter: "agentrun-v01", managerUrl: `http://127.0.0.1:${agentRunPort}`, runId, commandId, traceId, providerTrace: { traceId }, lastSeq: 0, status: "running", commandState: "running", 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, "running");
|
|
assert.equal(body.terminal, false);
|
|
assert.equal(body.projectionStatus, "projecting");
|
|
assert.equal(body.agentRun.commandState, "running");
|
|
assert.equal(body.agentRun.terminalStatus ?? null, null);
|
|
assert.equal(body.finalResponse, null);
|
|
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 reuses request session lookup without loading persisted AgentRun result (#1519)", async () => {
|
|
const calls = [];
|
|
const traceId = "trc_issue1422_turn_status_session_cache";
|
|
const runId = "run_issue1422_turn_status_session_cache";
|
|
const commandId = "cmd_issue1422_turn_status_session_cache";
|
|
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 });
|
|
const send = (data) => {
|
|
response.writeHead(200, { "content-type": "application/json" });
|
|
response.end(`${JSON.stringify({ ok: true, data, traceId: "trc_fake_issue1422_session_cache" })}\n`);
|
|
};
|
|
if (request.method === "GET" && url.pathname === `/api/v1/runs/${runId}/events`) {
|
|
return send({ items: [
|
|
{ id: "evt_issue1422_session_cache_running", runId, seq: 1, type: "backend_status", payload: { phase: "runner-job-created", commandId }, createdAt: "2026-06-18T05:20:00.000Z" }
|
|
] });
|
|
}
|
|
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;
|
|
let sessionLookupCount = 0;
|
|
const session = testAgentSessionRecord({
|
|
sessionId: "ses_issue1422_turn_status_session_cache",
|
|
projectId: "prj_hwpod_workbench",
|
|
conversationId: "cnv_issue1422_turn_status_session_cache",
|
|
threadId: "thread-issue1422-turn-status-session-cache",
|
|
lastTraceId: traceId,
|
|
status: "running",
|
|
session: {
|
|
agentRun: {
|
|
adapter: "agentrun-v01",
|
|
managerUrl: `http://127.0.0.1:${agentRunPort}`,
|
|
runId,
|
|
commandId,
|
|
traceId,
|
|
providerTrace: { traceId, valuesPrinted: false },
|
|
lastSeq: 0,
|
|
status: "running",
|
|
commandState: "running",
|
|
backendProfile: "deepseek",
|
|
valuesPrinted: false
|
|
},
|
|
valuesRedacted: true
|
|
}
|
|
});
|
|
const server = createCloudApiServer({
|
|
traceStore: createCodeAgentTraceStore(),
|
|
codeAgentChatResults: new Map(),
|
|
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 };
|
|
},
|
|
async getAgentSessionByTraceId(requestTraceId) {
|
|
sessionLookupCount += 1;
|
|
return requestTraceId === traceId ? session : null;
|
|
}
|
|
}
|
|
});
|
|
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}?projectId=prj_hwpod_workbench`, {
|
|
headers: { cookie: "hwlab_session=test-stub-session" }
|
|
});
|
|
assert.equal(response.status, 404);
|
|
const body = await response.json();
|
|
assert.equal(body.status, "unknown");
|
|
assert.equal(body.projectionStatus, "unknown");
|
|
assert.equal(sessionLookupCount, 1);
|
|
assert.equal(calls.filter((call) => call.path === `/api/v1/runs/${runId}/events`).length, 0);
|
|
} 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 resumes AgentRun projection from durable state after restart", async () => {
|
|
const calls = [];
|
|
const durableEvents = [];
|
|
const projectionStates = new Map();
|
|
const ownerWrites = [];
|
|
const traceId = "trc_projection_resume_restart";
|
|
const runId = "run_projection_resume_restart";
|
|
const commandId = "cmd_projection_resume_restart";
|
|
const sessionId = "ses_projection_resume_restart";
|
|
const conversationId = "cnv_projection_resume_restart";
|
|
const finalText = "OK from resumed AgentRun projection.";
|
|
|
|
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, afterSeq: url.searchParams.get("afterSeq") });
|
|
const send = (body, status = 200) => {
|
|
response.writeHead(status, { "content-type": "application/json" });
|
|
response.end(`${JSON.stringify(body)}\n`);
|
|
};
|
|
if (request.method === "GET" && url.pathname === `/api/v1/runs/${runId}/events`) {
|
|
assert.equal(url.searchParams.get("afterSeq"), "21");
|
|
return send({ items: [
|
|
{ id: "evt_projection_resume_22", runId, seq: 22, type: "assistant_message", payload: { commandId, text: finalText }, createdAt: "2026-06-18T16:31:11.000Z" },
|
|
{ id: "evt_projection_resume_23", runId, seq: 23, type: "terminal_status", payload: { commandId, terminalStatus: "completed" }, createdAt: "2026-06-18T16:31:12.000Z" }
|
|
] });
|
|
}
|
|
if (request.method === "GET" && url.pathname === `/api/v1/runs/${runId}/commands/${commandId}/result`) {
|
|
return send({
|
|
runId,
|
|
commandId,
|
|
status: "completed",
|
|
runStatus: "completed",
|
|
commandState: "completed",
|
|
terminalStatus: "completed",
|
|
completed: true,
|
|
reply: finalText,
|
|
lastSeq: 23,
|
|
eventCount: 23,
|
|
sessionRef: { sessionId, conversationId, threadId: "thread-projection-resume-restart" }
|
|
});
|
|
}
|
|
return send({ ok: false, message: `unexpected ${request.method} ${url.pathname}` }, 404);
|
|
});
|
|
await new Promise((resolve) => agentRunServer.listen(0, "127.0.0.1", resolve));
|
|
const agentRunPort = agentRunServer.address().port;
|
|
|
|
const session = testAgentSessionRecord({
|
|
sessionId,
|
|
conversationId,
|
|
projectId: "prj_hwpod_workbench",
|
|
status: "running",
|
|
traceId,
|
|
updatedAt: "2026-06-18T16:31:04.000Z",
|
|
session: {
|
|
traceResults: {
|
|
[traceId]: {
|
|
traceId,
|
|
status: "running",
|
|
sessionId,
|
|
conversationId,
|
|
updatedAt: "2026-06-18T16:31:04.000Z",
|
|
agentRun: {
|
|
adapter: "agentrun-v01",
|
|
managerUrl: `http://127.0.0.1:${agentRunPort}`,
|
|
runId,
|
|
commandId,
|
|
traceId,
|
|
lastSeq: 0,
|
|
status: "running",
|
|
commandState: "running",
|
|
backendProfile: "codex",
|
|
valuesPrinted: false
|
|
},
|
|
valuesRedacted: true
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
durableEvents.push({
|
|
traceId,
|
|
seq: 27,
|
|
source: "agentrun",
|
|
sourceSeq: 21,
|
|
type: "backend",
|
|
status: "running",
|
|
label: "agentrun:backend:mcpServer/startupStatus/updated",
|
|
commandId,
|
|
runId,
|
|
createdAt: "2026-06-18T16:31:04.200Z",
|
|
valuesPrinted: false
|
|
});
|
|
projectionStates.set(traceId, {
|
|
traceId,
|
|
sessionId,
|
|
conversationId,
|
|
threadId: "thread-projection-resume-restart",
|
|
ownerUserId: TEST_AGENT_ACTOR.id,
|
|
ownerRole: TEST_AGENT_ACTOR.role,
|
|
runId,
|
|
commandId,
|
|
sourceRunId: runId,
|
|
sourceCommandId: commandId,
|
|
managerUrl: `http://127.0.0.1:${agentRunPort}`,
|
|
backendProfile: "codex",
|
|
lastSourceSeq: 21,
|
|
lastAgentRunSeq: 21,
|
|
lastProjectedSeq: 27,
|
|
sourceLatestSeq: 21,
|
|
upstreamLatestSeq: 21,
|
|
projectionStatus: "projecting",
|
|
projectionHealth: "healthy",
|
|
resultSyncState: "not_started",
|
|
createdAt: "2026-06-18T16:31:04.000Z",
|
|
updatedAt: "2026-06-18T16:31:04.000Z",
|
|
valuesPrinted: false
|
|
});
|
|
|
|
const runtimeStore = {
|
|
async queryAgentTraceEvents(params = {}) {
|
|
return { events: durableEvents.filter((event) => event.traceId === params.traceId), count: durableEvents.length };
|
|
},
|
|
async writeAgentTraceEvent(params = {}) {
|
|
const event = params.event ?? params.traceEvent ?? params;
|
|
durableEvents.push(event);
|
|
return { written: true, traceEvent: event };
|
|
},
|
|
async getWorkbenchProjectionState(params = {}) {
|
|
const projectionState = projectionStates.get(params.traceId) ?? null;
|
|
return { projectionState, found: Boolean(projectionState) };
|
|
},
|
|
async queryWorkbenchProjectionStates() {
|
|
return { states: [...projectionStates.values()], count: projectionStates.size };
|
|
},
|
|
async writeWorkbenchProjectionState(params = {}) {
|
|
const projectionState = params.projectionState ?? params.state ?? params;
|
|
projectionStates.set(projectionState.traceId, projectionState);
|
|
return { written: true, projectionState };
|
|
}
|
|
};
|
|
const accessController = {
|
|
required: false,
|
|
store: {
|
|
async listAgentSessionsForUser() { return [session]; },
|
|
async getAgentSessionByTraceId(requestTraceId) { return requestTraceId === traceId ? session : null; }
|
|
},
|
|
async authenticate() { return { ok: true, actor: TEST_AGENT_ACTOR, session: TEST_AUTH_SESSION }; },
|
|
async recordAgentSessionOwner(input = {}) {
|
|
ownerWrites.push(input);
|
|
return { ...session, status: input.status ?? session.status, session: input.session ?? session.session };
|
|
}
|
|
};
|
|
|
|
const server = createCloudApiServer({
|
|
accessController,
|
|
runtimeStore,
|
|
codeAgentChatResults: new Map(),
|
|
env: {
|
|
HWLAB_CODE_AGENT_ADAPTER: "agentrun-v01",
|
|
HWLAB_CODE_AGENT_AGENTRUN_ALLOW_NON_K3S_URL: "1",
|
|
HWLAB_CODE_AGENT_AGENTRUN_PROJECTION_RESUME_ENABLED: "1",
|
|
HWLAB_CODE_AGENT_AGENTRUN_PROJECTION_RESUME_INITIAL_DELAY_MS: "1",
|
|
HWLAB_CODE_AGENT_AGENTRUN_PROJECTION_RESUME_INTERVAL_MS: "0",
|
|
HWLAB_CODE_AGENT_AGENTRUN_PROJECTION_RESUME_MIN_AGE_MS: "0",
|
|
HWLAB_CODE_AGENT_AGENTRUN_PROJECTION_RESUME_BATCH_SIZE: "10"
|
|
}
|
|
});
|
|
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
|
|
try {
|
|
for (let index = 0; index < 80 && projectionStates.get(traceId)?.resultSyncState !== "synced"; index += 1) await delay(10);
|
|
assert.ok(calls.some((call) => call.path === `/api/v1/runs/${runId}/events` && call.afterSeq === "21"));
|
|
assert.equal(calls.some((call) => call.path === `/api/v1/runs/${runId}/commands/${commandId}/result`), true);
|
|
assert.equal(projectionStates.get(traceId)?.lastSourceSeq, 23);
|
|
assert.equal(projectionStates.get(traceId)?.resultSyncState, "synced");
|
|
assert.equal(ownerWrites.some((write) => write.status === "completed"), true);
|
|
assert.ok(durableEvents.some((event) => event.sourceSeq === 22));
|
|
} 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 projection resume retries evicted AgentRun session with stored prompt", async () => {
|
|
const calls = [];
|
|
const durableEvents = [];
|
|
const projectionStates = new Map();
|
|
const traceId = "trc_projection_resume_evicted_retry";
|
|
const runId = "run_projection_resume_evicted";
|
|
const commandId = "cmd_projection_resume_evicted";
|
|
const retryRunId = "run_projection_resume_evicted_retry";
|
|
const retryCommandId = "cmd_projection_resume_evicted_retry";
|
|
const sessionId = "ses_projection_resume_evicted";
|
|
const conversationId = "cnv_projection_resume_evicted";
|
|
const promptText = "resume projection evicted session with original prompt";
|
|
|
|
const agentRunServer = createHttpServer(async (request, response) => {
|
|
const url = new URL(request.url || "/", "http://127.0.0.1");
|
|
const chunks = [];
|
|
for await (const chunk of request) chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
const body = chunks.length ? JSON.parse(Buffer.concat(chunks).toString("utf8")) : null;
|
|
calls.push({ method: request.method, path: url.pathname, search: url.search, afterSeq: url.searchParams.get("afterSeq"), body });
|
|
const send = (data, status = 200) => {
|
|
response.writeHead(status, { "content-type": "application/json" });
|
|
response.end(`${JSON.stringify({ ok: status < 400, data })}\n`);
|
|
};
|
|
if (request.method === "GET" && url.pathname === `/api/v1/runs/${runId}/events`) {
|
|
return send({ items: [
|
|
{ id: "evt_projection_evicted_1", runId, seq: 1, type: "error", payload: { commandId, failureKind: "session-store-evicted", message: "codex app-server thread/resume reported no rollout found for PVC-backed session; session storage was likely evicted" }, createdAt: "2026-06-19T12:01:00.000Z" },
|
|
{ id: "evt_projection_evicted_2", runId, seq: 2, type: "terminal_status", payload: { commandId, terminalStatus: "failed", failureKind: "session-store-evicted" }, createdAt: "2026-06-19T12:01:01.000Z" }
|
|
] });
|
|
}
|
|
if (request.method === "GET" && url.pathname === `/api/v1/runs/${runId}/commands/${commandId}/result`) {
|
|
return send({
|
|
runId,
|
|
commandId,
|
|
status: "failed",
|
|
runStatus: "failed",
|
|
commandState: "failed",
|
|
terminalStatus: "failed",
|
|
failureKind: "session-store-evicted",
|
|
failureMessage: "codex app-server thread/resume reported no rollout found for PVC-backed session; session storage was likely evicted",
|
|
completed: false,
|
|
lastSeq: 2,
|
|
eventCount: 2,
|
|
sessionRef: { sessionId: "ses_agentrun_projection_resume_evicted", conversationId, threadId: "thread_projection_evicted" }
|
|
});
|
|
}
|
|
if (request.method === "POST" && url.pathname === "/api/v1/sessions") {
|
|
assert.match(body.sessionId, /-reset-/u);
|
|
return send({ sessionId: body.sessionId, backendProfile: body.backendProfile });
|
|
}
|
|
if (request.method === "POST" && url.pathname === "/api/v1/runs") {
|
|
assert.match(body.sessionRef?.sessionId, /-reset-/u);
|
|
assert.equal(body.sessionRef?.threadId, undefined);
|
|
return send({ id: retryRunId, status: "pending", backendProfile: "dsflash-go", sessionRef: body.sessionRef, resourceBundleRef: body.resourceBundleRef });
|
|
}
|
|
if (request.method === "POST" && url.pathname === `/api/v1/runs/${retryRunId}/commands`) {
|
|
assert.equal(body.type, "turn");
|
|
assert.equal(body.payload.prompt, promptText);
|
|
assert.equal(body.payload.message, promptText);
|
|
assert.equal(body.payload.threadId, null);
|
|
assert.match(body.payload.sessionId, /-reset-/u);
|
|
assert.equal(body.payload.providerProfile, "dsflash-go");
|
|
assert.equal(body.dispatch?.kind, "kubernetes-job");
|
|
assert.equal(body.dispatch?.input?.commandId, undefined);
|
|
return send({
|
|
id: retryCommandId,
|
|
runId: retryRunId,
|
|
state: "queued",
|
|
dispatchIntent: {
|
|
id: "dispatch_projection_resume_evicted_retry",
|
|
state: "pending",
|
|
runnerJobId: "rjob_projection_resume_evicted_retry",
|
|
attemptCount: 0,
|
|
durable: true
|
|
}
|
|
});
|
|
}
|
|
return send({ message: `unexpected ${request.method} ${url.pathname}` }, 500);
|
|
});
|
|
await new Promise((resolve) => agentRunServer.listen(0, "127.0.0.1", resolve));
|
|
const agentRunPort = agentRunServer.address().port;
|
|
|
|
projectionStates.set(traceId, {
|
|
traceId,
|
|
sessionId,
|
|
conversationId,
|
|
threadId: "thread_projection_evicted",
|
|
ownerUserId: TEST_AGENT_ACTOR.id,
|
|
ownerRole: TEST_AGENT_ACTOR.role,
|
|
runId,
|
|
commandId,
|
|
sourceRunId: runId,
|
|
sourceCommandId: commandId,
|
|
managerUrl: `http://127.0.0.1:${agentRunPort}`,
|
|
backendProfile: "dsflash-go",
|
|
providerId: "JD01",
|
|
lastSourceSeq: 0,
|
|
lastAgentRunSeq: 0,
|
|
lastProjectedSeq: 0,
|
|
sourceLatestSeq: 0,
|
|
upstreamLatestSeq: 0,
|
|
projectionStatus: "projecting",
|
|
projectionHealth: "healthy",
|
|
resultSyncState: "not_started",
|
|
retryParams: {
|
|
message: promptText,
|
|
prompt: promptText,
|
|
text: promptText,
|
|
sessionId,
|
|
conversationId,
|
|
threadId: "thread_projection_evicted",
|
|
ownerUserId: TEST_AGENT_ACTOR.id,
|
|
ownerRole: TEST_AGENT_ACTOR.role,
|
|
providerProfile: "dsflash-go",
|
|
codeAgentProviderProfile: "dsflash-go",
|
|
backendProfile: "dsflash-go",
|
|
valuesPrinted: false
|
|
},
|
|
createdAt: "2026-06-19T12:00:00.000Z",
|
|
updatedAt: "2026-06-19T12:00:00.000Z",
|
|
valuesPrinted: false
|
|
});
|
|
|
|
const runtimeStore = {
|
|
async queryAgentTraceEvents(params = {}) {
|
|
return { events: durableEvents.filter((event) => event.traceId === params.traceId), count: durableEvents.length };
|
|
},
|
|
async writeAgentTraceEvent(params = {}) {
|
|
const event = params.event ?? params.traceEvent ?? params;
|
|
durableEvents.push(event);
|
|
return { written: true, traceEvent: event };
|
|
},
|
|
async getWorkbenchProjectionState(params = {}) {
|
|
const projectionState = projectionStates.get(params.traceId) ?? null;
|
|
return { projectionState, found: Boolean(projectionState) };
|
|
},
|
|
async queryWorkbenchProjectionStates() {
|
|
return { states: [...projectionStates.values()], count: projectionStates.size };
|
|
},
|
|
async writeWorkbenchProjectionState(params = {}) {
|
|
const projectionState = params.projectionState ?? params.state ?? params;
|
|
projectionStates.set(projectionState.traceId, projectionState);
|
|
return { written: true, projectionState };
|
|
}
|
|
};
|
|
const accessController = {
|
|
required: false,
|
|
store: {
|
|
async listAgentSessionsForUser() { return []; },
|
|
async getAgentSessionByTraceId() { return null; }
|
|
},
|
|
async authenticate() { return { ok: true, actor: TEST_AGENT_ACTOR, session: TEST_AUTH_SESSION }; }
|
|
};
|
|
|
|
const server = createCloudApiServer({
|
|
accessController,
|
|
runtimeStore,
|
|
codeAgentChatResults: new Map(),
|
|
env: {
|
|
HWLAB_CODE_AGENT_ADAPTER: "agentrun-v01",
|
|
HWLAB_CODE_AGENT_AGENTRUN_ALLOW_NON_K3S_URL: "1",
|
|
HWLAB_CODE_AGENT_AGENTRUN_SOURCE_COMMIT: "a".repeat(40),
|
|
HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID: "JD01",
|
|
HWLAB_RUNTIME_NAMESPACE: "hwlab-v03",
|
|
HWLAB_RUNTIME_LANE: "v03",
|
|
HWLAB_RUNTIME_API_URL: "http://hwlab-cloud-api.hwlab-v03.svc.cluster.local:6667",
|
|
HWLAB_RUNTIME_WEB_URL: "http://hwlab-cloud-web.hwlab-v03.svc.cluster.local:8080",
|
|
HWLAB_RUNTIME_ENDPOINT_LOCKED: "1",
|
|
HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME: "1",
|
|
HWLAB_CODE_AGENT_AGENTRUN_BACKEND_PROFILE: "dsflash-go",
|
|
HWLAB_CODE_AGENT_AGENTRUN_PROJECTION_RESUME_ENABLED: "1",
|
|
HWLAB_CODE_AGENT_AGENTRUN_PROJECTION_RESUME_INITIAL_DELAY_MS: "1",
|
|
HWLAB_CODE_AGENT_AGENTRUN_PROJECTION_RESUME_INTERVAL_MS: "0",
|
|
HWLAB_CODE_AGENT_AGENTRUN_PROJECTION_RESUME_MIN_AGE_MS: "0",
|
|
HWLAB_CODE_AGENT_AGENTRUN_PROJECTION_RESUME_BATCH_SIZE: "10"
|
|
}
|
|
});
|
|
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
|
|
try {
|
|
for (let index = 0; index < 100 && projectionStates.get(traceId)?.runId !== retryRunId; index += 1) await delay(10);
|
|
assert.equal(calls.some((call) => call.path === `/api/v1/runs/${runId}/commands/${commandId}/result`), true);
|
|
assert.equal(calls.some((call) => call.path === `/api/v1/runs/${retryRunId}/commands`), true);
|
|
const state = projectionStates.get(traceId);
|
|
assert.equal(state.runId, retryRunId);
|
|
assert.equal(state.commandId, retryCommandId);
|
|
assert.equal(state.sourceRunId, retryRunId);
|
|
assert.equal(state.sourceCommandId, retryCommandId);
|
|
assert.equal(state.projectionStatus, "projecting");
|
|
assert.equal(state.retryParams.message, promptText);
|
|
assert.equal(state.retryParams.providerProfile, "dsflash-go");
|
|
assert.equal(calls.some((call) => call.path.endsWith("/runner-jobs")), false);
|
|
} 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()));
|
|
});
|
|
}
|
|
});
|