Files
pikasTech-HWLAB/internal/cloud/server-agent-chat.test.ts
T
2026-06-08 11:25:54 +08:00

2912 lines
151 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 { validateCodeAgentChatSchema } from "./code-agent-chat.ts";
import { createCodexStdioSessionManager } from "./codex-stdio-session.ts";
import { createCodeAgentTraceStore } from "./code-agent-trace-store.ts";
import { submitAgentRunChatTurn } 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("AgentRun adapter filters resource tools and credentials through access capabilities", async () => {
const calls = [];
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_access_tools" })}\n`);
};
if (request.method === "POST" && url.pathname === "/api/v1/sessions") return send({ ok: true });
if (request.method === "POST" && url.pathname === "/api/v1/runs") return send({ id: "run_access_tools", status: "pending", backendProfile: "deepseek", sessionRef: body.sessionRef, resourceBundleRef: body.resourceBundleRef });
if (request.method === "POST" && url.pathname === "/api/v1/runs/run_access_tools/commands") return send({ id: "cmd_access_tools", runId: "run_access_tools", state: "pending", type: "turn", seq: 1 });
if (request.method === "POST" && url.pathname === "/api/v1/runs/run_access_tools/runner-jobs") return send({
action: "create-kubernetes-job",
runId: "run_access_tools",
commandId: body.commandId,
attemptId: "attempt_access_tools",
runnerId: "runner_access_tools",
namespace: "agentrun-v01",
jobName: "agentrun-v01-runner-access-tools"
});
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));
try {
const { port } = agentRunServer.address();
const traceStore = createCodeAgentTraceStore();
const result = await submitAgentRunChatTurn({
traceId: "trc_access_tool_filter",
traceStore,
params: {
message: "tool filter smoke",
ownerUserId: "usr_tool_limited",
projectId: "prj_hwpod_workbench",
conversationId: "cnv_access_tools",
sessionId: "ses_access_tools"
},
options: {
env: {
HWLAB_CODE_AGENT_ADAPTER: "agentrun-v01",
AGENTRUN_MGR_URL: `http://127.0.0.1:${port}`,
HWLAB_CODE_AGENT_AGENTRUN_ALLOW_NON_K3S_URL: "1",
HWLAB_CODE_AGENT_AGENTRUN_SOURCE_COMMIT: "0123456789abcdef0123456789abcdef01234567",
HWLAB_CODE_AGENT_DEFAULT_PROVIDER_PROFILE: "deepseek",
HWLAB_RUNTIME_API_URL: "http://hwlab-cloud-api.hwlab-v02.svc.cluster.local:6667",
HWLAB_RUNTIME_WEB_URL: "http://hwlab-cloud-web.hwlab-v02.svc.cluster.local:8080",
HWLAB_RUNTIME_NAMESPACE: "hwlab-v02",
HWLAB_RUNTIME_LANE: "v02",
HWLAB_RUNTIME_ENDPOINT_LOCKED: "1",
HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME: "1",
UNIDESK_MAIN_SERVER_IP: "http://74.48.78.17:18081"
},
accessController: {
async codeAgentToolCapabilitiesForOwner(ownerUserId) {
assert.equal(ownerUserId, "usr_tool_limited");
return {
contractVersion: "admin-access-v1",
tools: {
hwpod: { allowed: false },
unidesk_ssh: { allowed: false },
trans_cmd: { allowed: false },
github_pr: { allowed: false }
}
};
},
store: {
async findActiveDefaultApiKeyForUser(userId) {
assert.equal(userId, "usr_tool_limited");
return { displaySecret: "hwl_live_owner_secret" };
}
}
}
}
});
assert.equal(result.status, "running");
const createRun = calls.find((call) => call.method === "POST" && call.path === "/api/v1/runs");
assert.equal(createRun.body.resourceBundleRef.kind, "gitbundle");
assert.deepEqual(createRun.body.resourceBundleRef.bundles, [
{ name: "hwlab-tools", subpath: "tools", target_path: "tools" },
{ name: "hwlab-agent-skills", subpath: "skills", target_path: ".agents/skills" }
]);
assert.equal(Object.hasOwn(createRun.body.resourceBundleRef, "toolAliases"), false);
assert.equal(Object.hasOwn(createRun.body.resourceBundleRef, "skillRefs"), false);
assert.equal(Object.hasOwn(createRun.body.resourceBundleRef, "workspaceFiles"), false);
assert.deepEqual(createRun.body.executionPolicy.secretScope.toolCredentials, []);
const runnerJob = calls.find((call) => call.method === "POST" && call.path === "/api/v1/runs/run_access_tools/runner-jobs");
assert.equal(runnerJob.body.transientEnv.some((entry) => entry.name === "HWLAB_API_KEY"), false);
} finally {
await new Promise((resolve, reject) => agentRunServer.close((error) => (error ? reject(error) : resolve())));
}
});
test("cloud api /v1/agent/chat parse and params errors use structured blocker envelope", async () => {
const server = createCloudApiServer({
env: {
HWLAB_CODE_AGENT_MODEL: "gpt-test"
}
});
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
try {
const { port } = server.address();
const parse = await postAgentRaw(port, "{", { "x-trace-id": "trc_parse_error" });
assert.equal(parse.status, 400);
assert.equal(parse.body.status, "failed");
assert.equal(parse.body.error.code, "parse_error");
assert.equal(parse.body.error.layer, "api");
assert.equal(parse.body.error.retryable, true);
assert.equal(parse.body.error.traceId, "trc_parse_error");
assert.equal(parse.body.error.route, "/v1/agent/chat");
assert.match(parse.body.error.userMessage, /JSON/u);
assert.equal(Object.hasOwn(parse.body, "reply"), false);
assert.equal(JSON.stringify(parse.body).includes("sk-"), false);
const invalid = await postAgentRaw(port, JSON.stringify([]), { "x-trace-id": "trc_invalid_params" });
assert.equal(invalid.status, 400);
assert.equal(invalid.body.error.code, "invalid_params");
assert.equal(invalid.body.error.layer, "api");
assert.equal(invalid.body.error.retryable, true);
assert.equal(invalid.body.error.blocker.code, "invalid_params");
} finally {
await new Promise((resolve, reject) => {
server.close((error) => (error ? reject(error) : resolve()));
});
}
});
test("cloud api /v1/agent/chat supports short submit and result polling", async () => {
const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-short-"));
const codexHome = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-short-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"
};
},
async probe() {
return {
...codexStdioReadyFixture({ workspace, codexHome }),
sandbox: "danger-full-access"
};
},
async chat(params = {}) {
return {
...codexStdioChatFixture({ workspace, codexHome, params }),
sandbox: "danger-full-access",
session: {
...codexStdioChatFixture({ workspace, codexHome, params }).session,
sandbox: "danger-full-access"
}
};
},
cancel() {},
reapIdle() {}
}
});
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
try {
const { port } = server.address();
const traceId = "trc_server-test-short-submit";
const manualSession = await createManualAgentSession(port, {
conversationId: "cnv_server-test-short-submit"
});
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: "用pwd列出你当前的工作目录"
})
});
assert.equal(submit.status, 202);
const accepted = await submit.json();
assert.equal(accepted.accepted, true);
assert.equal(accepted.shortConnection, true);
assert.equal(accepted.traceId, traceId);
assert.equal(accepted.resultUrl, `/v1/agent/chat/result/${traceId}`);
const payload = await pollAgentResult(port, traceId);
validateCodeAgentChatSchema(payload);
assert.equal(payload.status, "completed");
assert.equal(payload.traceId, traceId);
assert.equal(payload.sandbox, "danger-full-access");
assert.match(payload.reply.content, new RegExp(workspace.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")));
} 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 delegates v0.2 turns to AgentRun v0.1 over adapter", async () => {
const calls = [];
const ownerSessions = new Map();
const conversationMessages = [];
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, body });
const send = (data) => {
response.writeHead(200, { "content-type": "application/json" });
response.end(`${JSON.stringify({ ok: true, data, traceId: "trc_fake_agentrun" })}\n`);
};
if (request.method === "GET" && url.pathname === "/api/v1/runs/run_hwlab_adapter") {
return send({
id: "run_hwlab_adapter",
status: "claimed",
terminalStatus: null,
claimedBy: "runner_hwlab_adapter",
leaseExpiresAt: "2999-01-01T00:00:00.000Z",
backendProfile: "deepseek",
sessionRef: {
sessionId: "ses_agentrun_deepseek_server_test_agentrun",
conversationId: "cnv_server-test-agentrun",
threadId: "019e8078-db67-7750-a5d9-1a99f3abd445",
metadata: {}
}
});
}
if (request.method === "POST" && url.pathname === "/api/v1/runs") {
assert.equal(body.tenantId, "hwlab");
assert.equal(body.projectId, "pikasTech/HWLAB");
assert.equal(body.backendProfile, "deepseek");
assert.equal(body.resourceBundleRef.repoUrl, "http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git");
assert.equal(body.resourceBundleRef.commitId, "0123456789abcdef0123456789abcdef01234567");
assert.equal(body.resourceBundleRef.kind, "gitbundle");
assert.deepEqual(body.resourceBundleRef.bundles, [
{ name: "hwlab-tools", subpath: "tools", target_path: "tools" },
{ name: "hwlab-agent-skills", subpath: "skills", target_path: ".agents/skills" }
]);
assert.deepEqual(body.resourceBundleRef.promptRefs, [
{ name: "hwlab-v02-runtime", path: "internal/agent/prompts/hwlab-v02-runtime.md", inject: "thread-start", required: true }
]);
assert.equal(Object.hasOwn(body.resourceBundleRef, "toolAliases"), false);
assert.equal(Object.hasOwn(body.resourceBundleRef, "skillRefs"), false);
assert.equal(Object.hasOwn(body.resourceBundleRef, "workspaceFiles"), false);
const toolCredentials = body.executionPolicy.secretScope.toolCredentials;
assert.equal(toolCredentials.some((item) => item.tool === "github" && item.projection.envName === "GH_TOKEN" && item.secretRef.name === "agentrun-v01-tool-github-pr"), true);
assert.equal(toolCredentials.some((item) => item.tool === "unidesk-ssh" && item.projection.envName === "UNIDESK_SSH_CLIENT_TOKEN" && item.secretRef.name === "agentrun-v01-tool-unidesk-ssh"), true);
assert.equal(body.sessionRef.threadId, "019e8078-db67-7750-a5d9-1a99f3abd445");
assert.equal(body.sessionRef.sessionId, "ses_agentrun_deepseek_server_test_agentrun");
assert.equal(body.sessionRef.metadata.hwlabProjectId, "prj_hwpod_workbench");
assert.equal(body.sessionRef.metadata.hwlabSessionId, "ses_server-test-agentrun");
assert.equal(body.sessionRef.metadata.agentRunSessionProfile, "deepseek");
assert.equal(body.sessionRef.metadata.agentRunSessionPolicy, "backend-profile-scoped");
assert.equal(body.sessionRef.metadata.threadContinuityPolicy, "hwlab-agentrun-v01-reuse-runner-thread");
assert.equal(body.sessionRef.metadata.sessionPolicy, "hwlab-agentrun-v01-session-runner-reuse");
return send({ id: "run_hwlab_adapter", status: "pending", backendProfile: "deepseek", sessionRef: body.sessionRef, resourceBundleRef: body.resourceBundleRef });
}
if (request.method === "POST" && url.pathname === "/api/v1/runs/run_hwlab_adapter/commands") {
if (body.type === "steer") {
assert.equal(body.payload.targetTraceId, "trc_server-test-agentrun-adapter");
assert.equal(body.payload.targetCommandId, "cmd_hwlab_adapter");
assert.equal(body.payload.traceId, "trc_steer_server_test");
assert.match(body.payload.prompt, /STEER_MARK/u);
assert.equal(body.payload.sessionId, "ses_agentrun_deepseek_server_test_agentrun");
assert.equal(body.payload.hwlabSessionId, "ses_server-test-agentrun");
assert.equal(body.payload.threadId, "019e8078-db67-7750-a5d9-1a99f3abd445");
assert.equal(body.idempotencyKey, "trc_steer_server_test");
return send({ id: "cmd_hwlab_adapter_steer", runId: "run_hwlab_adapter", state: "pending", type: "steer", seq: 7 });
}
assert.equal(body.type, "turn");
assert.match(body.payload.prompt, /AgentRun adapter/u);
assert.equal(body.payload.projectId, "prj_hwpod_workbench");
assert.equal(body.payload.sessionId, "ses_agentrun_deepseek_server_test_agentrun");
assert.equal(body.payload.hwlabSessionId, "ses_server-test-agentrun");
assert.equal(body.payload.threadId, "019e8078-db67-7750-a5d9-1a99f3abd445");
assert.equal(body.payload.threadContinuityPolicy, "hwlab-agentrun-v01-reuse-runner-thread");
assert.equal(body.payload.sessionPolicy, "hwlab-agentrun-v01-session-runner-reuse");
const secondTurn = /第二轮/u.test(body.payload.message ?? body.payload.prompt);
if (secondTurn) {
assert.equal(body.payload.prompt, "AgentRun adapter smoke 第二轮:总结我们刚才的对话内容");
assert.equal(body.payload.message, "AgentRun adapter smoke 第二轮:总结我们刚才的对话内容");
assert.equal(Object.hasOwn(body.payload, "originalPrompt"), false);
assert.equal(Object.hasOwn(body.payload, "conversationContext"), false);
assert.equal(Object.hasOwn(body.payload, "messages"), false);
assert.equal(body.payload.prompt.includes("HWLAB 同一会话的已脱敏历史上下文"), false);
assert.equal(body.payload.prompt.includes("hwl_live_test_secret"), false);
assert.equal(body.payload.prompt.includes("sk-test-hwpod-secret"), false);
} else {
assert.equal(Object.hasOwn(body.payload, "conversationContext"), false);
}
return send({ id: secondTurn ? "cmd_hwlab_adapter_second" : "cmd_hwlab_adapter", runId: "run_hwlab_adapter", state: "pending", type: "turn", seq: secondTurn ? 2 : 1 });
}
if (request.method === "GET" && url.pathname === "/api/v1/runs/run_hwlab_adapter/commands") {
return send({ items: [
{ id: "cmd_hwlab_adapter", runId: "run_hwlab_adapter", state: "completed", type: "turn", seq: 1, idempotencyKey: "trc_server-test-agentrun-adapter", payload: { traceId: "trc_server-test-agentrun-adapter", conversationId: "cnv_server-test-agentrun", hwlabSessionId: "ses_server-test-agentrun", threadId: "019e8078-db67-7750-a5d9-1a99f3abd445", providerProfile: "deepseek" } },
{ id: "cmd_hwlab_adapter_steer", runId: "run_hwlab_adapter", state: "completed", type: "steer", seq: 7, idempotencyKey: "trc_steer_server_test", payload: { traceId: "trc_steer_server_test", targetTraceId: "trc_server-test-agentrun-adapter", targetCommandId: "cmd_hwlab_adapter", conversationId: "cnv_server-test-agentrun", hwlabSessionId: "ses_server-test-agentrun", threadId: "019e8078-db67-7750-a5d9-1a99f3abd445", providerProfile: "deepseek" } },
{ id: "cmd_hwlab_adapter_second", runId: "run_hwlab_adapter", state: "completed", type: "turn", seq: 2, idempotencyKey: "trc_server-test-agentrun-adapter-second", payload: { traceId: "trc_server-test-agentrun-adapter-second", conversationId: "cnv_server-test-agentrun", hwlabSessionId: "ses_server-test-agentrun", threadId: "019e8078-db67-7750-a5d9-1a99f3abd445", providerProfile: "deepseek" } }
] });
}
if (request.method === "POST" && url.pathname === "/api/v1/runs/run_hwlab_adapter/runner-jobs") {
const secondRunnerJob = body.commandId === "cmd_hwlab_adapter_second";
assert.ok(body.commandId === "cmd_hwlab_adapter" || secondRunnerJob);
assert.deepEqual(body.transientEnv.map((entry) => entry.name), [
"HWLAB_RUNTIME_API_URL",
"HWLAB_RUNTIME_WEB_URL",
"HWLAB_RUNTIME_NAMESPACE",
"HWLAB_RUNTIME_LANE",
"HWLAB_RUNTIME_ENDPOINT_LOCKED",
"HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME",
"UNIDESK_MAIN_SERVER_IP",
"HWLAB_CODE_AGENT_PROVIDER_PROFILE",
"HWLAB_CODE_AGENT_PARENT_TRACE_ID"
]);
const transientEnv = Object.fromEntries(body.transientEnv.map((entry) => [entry.name, entry.value]));
assert.equal(transientEnv.HWLAB_RUNTIME_API_URL, "http://hwlab-cloud-api.hwlab-v02.svc.cluster.local:6667");
assert.equal(transientEnv.HWLAB_RUNTIME_WEB_URL, "http://hwlab-cloud-web.hwlab-v02.svc.cluster.local:8080");
assert.equal(transientEnv.HWLAB_RUNTIME_NAMESPACE, "hwlab-v02");
assert.equal(transientEnv.HWLAB_RUNTIME_LANE, "v02");
assert.equal(transientEnv.HWLAB_RUNTIME_ENDPOINT_LOCKED, "1");
assert.equal(transientEnv.HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME, "1");
assert.equal(transientEnv.UNIDESK_MAIN_SERVER_IP, "http://74.48.78.17:18081");
assert.equal(transientEnv.HWLAB_CODE_AGENT_PROVIDER_PROFILE, "deepseek");
assert.equal(transientEnv.HWLAB_CODE_AGENT_PARENT_TRACE_ID, secondRunnerJob ? "trc_server-test-agentrun-adapter-second" : "trc_server-test-agentrun-adapter");
assert.ok(body.transientEnv.length >= 9);
assert.equal(Object.hasOwn(transientEnv, "UNIDESK_SSH_CLIENT_TOKEN"), false);
assert.equal(Object.hasOwn(transientEnv, "GH_TOKEN"), false);
return send({
action: "create-kubernetes-job",
runId: "run_hwlab_adapter",
commandId: body.commandId,
attemptId: secondRunnerJob ? "attempt_hwlab_adapter_second" : "attempt_hwlab_adapter",
runnerId: secondRunnerJob ? "runner_hwlab_adapter_second" : "runner_hwlab_adapter",
namespace: "agentrun-v01",
jobName: secondRunnerJob ? "agentrun-v01-runner-hwlab-adapter-second" : "agentrun-v01-runner-hwlab-adapter",
jobIdentity: { namespace: "agentrun-v01", name: secondRunnerJob ? "agentrun-v01-runner-hwlab-adapter-second" : "agentrun-v01-runner-hwlab-adapter" },
runner: { attemptId: secondRunnerJob ? "attempt_hwlab_adapter_second" : "attempt_hwlab_adapter", runnerId: secondRunnerJob ? "runner_hwlab_adapter_second" : "runner_hwlab_adapter" }
});
}
if (request.method === "GET" && url.pathname === "/api/v1/runs/run_hwlab_adapter/events") {
const afterSeq = Number.parseInt(url.searchParams.get("afterSeq") ?? "0", 10);
const second = afterSeq >= 5 || url.searchParams.get("afterSeq") === "3" || calls.some((call) => call.path === "/api/v1/runs/run_hwlab_adapter/commands/cmd_hwlab_adapter_second/result");
if (second) return send({ items: [
{ id: "evt_old_tail", runId: "run_hwlab_adapter", seq: 6, type: "assistant_message", payload: { commandId: "cmd_hwlab_adapter", text: "旧 command 尾部不应进入第二轮。" }, createdAt: "2026-06-01T00:00:02.500Z" },
{ id: "evt_7", runId: "run_hwlab_adapter", seq: 7, type: "backend_status", payload: { phase: "turn-started", commandId: "cmd_hwlab_adapter_second", attemptId: "attempt_hwlab_adapter_second", runnerId: "runner_hwlab_adapter_second", jobName: "agentrun-v01-runner-hwlab-adapter-second", namespace: "agentrun-v01" }, createdAt: "2026-06-01T00:00:03.000Z" },
{ id: "evt_7_prompt", runId: "run_hwlab_adapter", seq: 8, type: "backend_status", payload: { phase: "initial-prompt-assembly", commandId: "cmd_hwlab_adapter_second", initialPromptInjected: false, reason: "thread-resume", initialPrompt: { available: true, bytes: 512, sha256: "prompt-sha", promptRefCount: 1, skillCount: 3, valuesPrinted: false } }, createdAt: "2026-06-01T00:00:03.500Z" },
{ id: "evt_8", runId: "run_hwlab_adapter", seq: 8, type: "assistant_message", payload: { commandId: "cmd_hwlab_adapter_second", text: "AgentRun adapter 复用已有 runner 完成第二轮。" }, createdAt: "2026-06-01T00:00:04.000Z" },
{ id: "evt_9", runId: "run_hwlab_adapter", seq: 9, type: "terminal_status", payload: { commandId: "cmd_hwlab_adapter_second", terminalStatus: "completed" }, createdAt: "2026-06-01T00:00:05.000Z" }
] });
return send({ items: [
{ id: "evt_1", runId: "run_hwlab_adapter", seq: 1, type: "backend_status", payload: { phase: "runner-job-created", commandId: "cmd_hwlab_adapter", attemptId: "attempt_hwlab_adapter", jobName: "agentrun-v01-runner-hwlab-adapter", namespace: "agentrun-v01" }, createdAt: "2026-06-01T00:00:00.000Z" },
{ id: "evt_bundle", runId: "run_hwlab_adapter", seq: 2, type: "backend_status", payload: { phase: "resource-bundle-materialized", commandId: "cmd_hwlab_adapter", kind: "gitbundle", commitId: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", bundles: { count: 2, names: ["hwlab-tools", "hwlab-agent-skills"], targetPaths: ["tools", ".agents/skills"], valuesPrinted: false }, tools: { count: 5, names: ["hwpod", "hwpod-ctl", "hwpod-compiler", "unidesk-ssh", "hwlab-code-agent"], valuesPrinted: false }, promptRefs: { count: 1, names: ["hwlab-v02-runtime"], valuesPrinted: false }, skillDirs: { count: 4, names: ["hwpod-cli", "hwpod-ctl", "hwlab-agent-runtime", "hwlab-code-agent"], valuesPrinted: false }, initialPrompt: { available: true, bytes: 512, sha256: "prompt-sha", promptRefCount: 1, skillCount: 4, skillNames: ["hwpod-cli", "hwpod-ctl", "hwlab-agent-runtime", "hwlab-code-agent"], valuesPrinted: false } }, createdAt: "2026-06-01T00:00:00.250Z" },
{ id: "evt_tool", runId: "run_hwlab_adapter", seq: 2, type: "tool_call", payload: { method: "item/completed", type: "commandExecution", toolName: "commandExecution", itemId: "call_agentrun_tool", command: "/bin/sh -lc 'hwpod inspect --dry-run'", status: "completed", exitCode: 0, durationMs: 708, outputSummary: '{"ok":true,"action":"hwpod-cli.plan"}', summary: { outputBytes: 42, outputTruncated: false }, commandId: "cmd_hwlab_adapter", runnerId: "runner_hwlab_adapter", attemptId: "attempt_hwlab_adapter" }, createdAt: "2026-06-01T00:00:00.500Z" },
{ id: "evt_noise", runId: "run_hwlab_adapter", seq: 3, type: "backend_status", payload: { phase: "thread/status/changed", commandId: "cmd_hwlab_adapter" }, createdAt: "2026-06-01T00:00:00.750Z" },
{ id: "evt_prompt", runId: "run_hwlab_adapter", seq: 4, type: "backend_status", payload: { phase: "initial-prompt-assembly", commandId: "cmd_hwlab_adapter", initialPromptInjected: true, reason: "thread-start", initialPrompt: { available: true, bytes: 512, sha256: "prompt-sha", promptRefCount: 1, skillCount: 4, valuesPrinted: false } }, createdAt: "2026-06-01T00:00:00.875Z" },
{ id: "evt_2", runId: "run_hwlab_adapter", seq: 4, type: "assistant_message", payload: { commandId: "cmd_hwlab_adapter", text: "AgentRun adapter 已接管 HWLAB Code Agent。" }, createdAt: "2026-06-01T00:00:01.000Z" },
{ id: "evt_3", runId: "run_hwlab_adapter", seq: 5, type: "terminal_status", payload: { commandId: "cmd_hwlab_adapter", terminalStatus: "completed" }, createdAt: "2026-06-01T00:00:02.000Z" }
] });
}
if (request.method === "GET" && url.pathname === "/api/v1/runs/run_hwlab_adapter/commands/cmd_hwlab_adapter/result") {
return send({
runId: "run_hwlab_adapter",
commandId: "cmd_hwlab_adapter",
attemptId: "attempt_hwlab_adapter",
runnerId: "runner_hwlab_adapter",
jobName: "agentrun-v01-runner-hwlab-adapter",
namespace: "agentrun-v01",
status: "completed",
runStatus: "completed",
commandState: "completed",
terminalStatus: "completed",
completed: true,
reply: "AgentRun adapter 已接管 HWLAB Code Agent。",
lastSeq: 5,
eventCount: 5,
sessionRef: { sessionId: "ses_agentrun_deepseek_server_test_agentrun", conversationId: "cnv_server-test-agentrun", threadId: "019e8078-db67-7750-a5d9-1a99f3abd445" }
});
}
if (request.method === "GET" && url.pathname === "/api/v1/runs/run_hwlab_adapter/commands/cmd_hwlab_adapter_second/result") {
return send({
runId: "run_hwlab_adapter",
commandId: "cmd_hwlab_adapter_second",
attemptId: "attempt_hwlab_adapter_second",
runnerId: "runner_hwlab_adapter_second",
jobName: "agentrun-v01-runner-hwlab-adapter-second",
namespace: "agentrun-v01",
status: "completed",
runStatus: "claimed",
commandState: "completed",
terminalStatus: "completed",
completed: true,
reply: "AgentRun adapter 复用已有 runner 完成第二轮。",
lastSeq: 9,
eventCount: 9,
sessionRef: { sessionId: "ses_agentrun_deepseek_server_test_agentrun", conversationId: "cnv_server-test-agentrun", 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_agentrun" })}\n`);
});
await new Promise((resolve) => agentRunServer.listen(0, "127.0.0.1", resolve));
const agentRunPort = agentRunServer.address().port;
const traceStore = createCodeAgentTraceStore();
let deferOwnerRecord = false;
let releaseOwnerRecord: (() => void) | null = null;
let deferredOwnerRecordStarted = false;
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_SOURCE_COMMIT: "0123456789abcdef0123456789abcdef01234567",
HWLAB_CODE_AGENT_DEFAULT_PROVIDER_PROFILE: "deepseek",
HWLAB_CODE_AGENT_DEEPSEEK_MODEL: "deepseek-chat",
HWLAB_ENVIRONMENT: "v02",
HWLAB_GITOPS_PROFILE: "v02",
UNIDESK_MAIN_SERVER_IP: "http://74.48.78.17:18081"
},
accessController: {
required: false,
async authenticate() {
return { ok: true, actor: TEST_AGENT_ACTOR, session: TEST_AUTH_SESSION };
},
async recordAgentSessionOwner(input) {
if (deferOwnerRecord && input.traceId === "trc_server-test-agentrun-adapter" && input.status === "running") {
deferredOwnerRecordStarted = true;
await new Promise<void>((resolve) => { releaseOwnerRecord = resolve; });
}
const record = testAgentSessionRecord(input);
ownerSessions.set(record.id, record);
return record;
},
async getAgentSession(sessionId) {
return ownerSessions.get(sessionId) ?? null;
},
async visibleConversationForActor(actor, conversationId, projectId) {
assert.equal(actor.id, "usr_agent_owner");
assert.equal(actor.role, "user");
assert.equal(conversationId, "cnv_server-test-agentrun");
assert.equal(projectId, "pikasTech/HWLAB");
return { conversationId, messages: conversationMessages };
}
}
});
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
try {
const { port } = server.address();
const traceId = "trc_server-test-agentrun-adapter";
ownerSessions.set("ses_server-test-agentrun", testAgentSessionRecord({
sessionId: "ses_server-test-agentrun",
conversationId: "cnv_server-test-agentrun",
projectId: "pikasTech/HWLAB",
status: "idle",
threadId: "019e8078-db67-7750-a5d9-1a99f3abd445"
}));
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-agentrun",
projectId: "prj_hwpod_workbench",
sessionId: "ses_server-test-agentrun",
ownerUserId: "usr_agent_owner",
ownerRole: "user",
threadId: "019e8078-db67-7750-a5d9-1a99f3abd445",
retryOf: "trc_previous-agentrun-web",
message: "AgentRun adapter smoke"
})
});
assert.equal(submit.status, 202);
const accepted = await submit.json();
assert.equal(accepted.shortConnection, true);
const payload = await pollAgentResult(port, traceId);
validateCodeAgentChatSchema(payload);
assert.equal(payload.status, "completed");
assert.equal(payload.provider, "deepseek");
assert.equal(payload.backend, "agentrun-v01/deepseek");
assert.equal(payload.infrastructureBackend, "agentrun-v01/deepseek");
assert.equal(payload.capabilityLevel, "agentrun-v01-shared-code-agent-session");
assert.equal(payload.sessionMode, "agentrun-v01-durable-session");
assert.equal(payload.implementationType, "agentrun-v01-shared-execution-infra");
assert.equal(payload.runner.kind, "agentrun-v01-shared-runner");
assert.equal(payload.runner.provider, "deepseek");
assert.equal(payload.runner.codexStdio, false);
assert.equal(payload.runner.delegatedToAgentRun, true);
assert.equal(payload.providerTrace.protocol, "agentrun-v01-jsonrpc");
assert.equal(payload.providerTrace.command, "agentrun.v01.command.turn");
assert.equal(payload.providerTrace.runnerKind, "agentrun-v01-shared-runner");
assert.equal(payload.providerTrace.terminalStatus, "completed");
assert.equal(payload.longLivedSessionGate.status, "pass");
assert.equal(payload.longLivedSessionGate.provider, "deepseek");
assert.equal(payload.longLivedSessionGate.codexStdio, false);
assert.equal(payload.longLivedSessionGate.delegatedToAgentRun, true);
assert.equal(payload.agentRun.runId, "run_hwlab_adapter");
assert.equal(payload.agentRun.commandId, "cmd_hwlab_adapter");
assert.equal(payload.agentRun.jobName, "agentrun-v01-runner-hwlab-adapter");
const serializedPayload = JSON.stringify(payload);
assert.equal(serializedPayload.includes("repo-owned-codex"), false);
assert.equal(serializedPayload.includes("codex-app-server-stdio"), false);
assert.equal(serializedPayload.includes("\"provider\":\"codex-stdio\""), false);
assert.equal(serializedPayload.includes("\"codexStdio\":true"), false);
assert.match(payload.reply.content, /AgentRun adapter/u);
assert.ok(payload.runnerTrace.events.some((event) => event.label === "agentrun:backend:runner-job-created"));
const bundleEvent = payload.runnerTrace.events.find((event) => event.label === "agentrun:backend:resource-bundle-materialized");
assert.equal(bundleEvent?.details?.kind, "gitbundle");
assert.deepEqual(bundleEvent?.details?.bundles?.names, ["hwlab-tools", "hwlab-agent-skills"]);
assert.deepEqual(bundleEvent?.details?.tools?.names, ["hwpod", "hwpod-ctl", "hwpod-compiler", "unidesk-ssh", "hwlab-code-agent"]);
assert.deepEqual(bundleEvent?.details?.promptRefs?.names, ["hwlab-v02-runtime"]);
assert.deepEqual(bundleEvent?.details?.skillDirs?.names, ["hwpod-cli", "hwpod-ctl", "hwlab-agent-runtime", "hwlab-code-agent"]);
const promptEvent = payload.runnerTrace.events.find((event) => event.label === "agentrun:backend:initial-prompt-assembly");
assert.equal(promptEvent?.details?.initialPromptInjected, true);
assert.equal(promptEvent?.details?.reason, "thread-start");
assert.equal(promptEvent?.details?.initialPrompt?.promptRefCount, 1);
assert.equal(promptEvent?.details?.initialPrompt?.skillCount, 4);
assert.ok(calls.some((call) => call.path === "/api/v1/runs/run_hwlab_adapter/runner-jobs"));
const inspectByTrace = await fetch(`http://127.0.0.1:${port}/v1/agent/chat/inspect?traceId=${traceId}`);
assert.equal(inspectByTrace.status, 200);
const inspectByTraceBody = await inspectByTrace.json();
assert.equal(inspectByTraceBody.ok, true);
assert.equal(inspectByTraceBody.latestTraceId, traceId);
assert.equal(inspectByTraceBody.session.sessionId, "ses_server-test-agentrun");
assert.equal(inspectByTraceBody.session.conversationId, "cnv_server-test-agentrun");
assert.equal(inspectByTraceBody.session.threadId, "019e8078-db67-7750-a5d9-1a99f3abd445");
assert.equal(inspectByTraceBody.conversationFacts.conversationId, "cnv_server-test-agentrun");
assert.equal(inspectByTraceBody.conversationFacts.threadId, "019e8078-db67-7750-a5d9-1a99f3abd445");
assert.equal(inspectByTraceBody.runnerTrace.traceId, traceId);
assert.equal(inspectByTraceBody.valuesRedacted, true);
assert.equal(JSON.stringify(inspectByTraceBody).includes("hwl_live_test_secret"), false);
const trace = await fetch(`http://127.0.0.1:${port}/v1/agent/chat/trace/${traceId}`);
assert.equal(trace.status, 200);
const traceBody = await trace.json();
assert.ok(traceBody.events.some((event) => event.runId === "run_hwlab_adapter"));
assert.ok(traceBody.events.some((event) => event.label === "agentrun:assistant:message"));
assert.equal(traceBody.events.some((event) => event.details?.initialPromptInjected === true), true);
const commandTraceEvent = traceBody.events.find((event) => event.label === "item/commandExecution:completed");
assert.equal(commandTraceEvent.toolName, "commandExecution");
assert.equal(commandTraceEvent.createdAt, "2026-06-01T00:00:00.500Z");
assert.match(commandTraceEvent.command, /hwpod inspect --dry-run/u);
assert.match(commandTraceEvent.stdoutSummary, /hwpod-cli\.plan/u);
conversationMessages.push(
{
id: "msg_693_user",
role: "user",
text: "看看 HWPOD 可用性?",
status: "sent",
traceId,
conversationId: "cnv_server-test-agentrun",
sessionId: "ses_server-test-agentrun",
threadId: "019e8078-db67-7750-a5d9-1a99f3abd445",
createdAt: "2026-06-02T00:00:00.000Z"
},
{
id: "msg_693_agent",
role: "agent",
text: "v0.2 HWPOD 可用性快照:可用;apiKey=hwl_live_test_secret sk-test-hwpod-secret-000000",
status: "completed",
traceId,
conversationId: "cnv_server-test-agentrun",
sessionId: "ses_server-test-agentrun",
threadId: "019e8078-db67-7750-a5d9-1a99f3abd445",
createdAt: "2026-06-02T00:00:01.000Z"
}
);
deferOwnerRecord = true;
const steerRequest = fetch(`http://127.0.0.1:${port}/v1/agent/chat/steer`, {
method: "POST",
headers: { "content-type": "application/json", "x-trace-id": traceId, cookie: "hwlab_session=test-stub-session" },
body: JSON.stringify({
traceId,
steerTraceId: "trc_steer_server_test",
conversationId: "cnv_server-test-agentrun",
projectId: "prj_hwpod_workbench",
sessionId: "ses_server-test-agentrun",
threadId: "019e8078-db67-7750-a5d9-1a99f3abd445",
message: "请按 STEER_MARK 调整最终回复"
})
});
const steer = await Promise.race([
steerRequest,
delay(100).then(() => null)
]);
assert.ok(steer, "steer short response must not wait for owner persistence");
assert.equal(steer.status, 202);
for (let index = 0; index < 20 && !deferredOwnerRecordStarted; index += 1) await delay(10);
assert.equal(deferredOwnerRecordStarted, true);
releaseOwnerRecord?.();
deferOwnerRecord = false;
const steerBody = await steer.json();
assert.equal(steerBody.accepted, true);
assert.equal(steerBody.route, "/v1/agent/chat/steer");
assert.equal(steerBody.traceId, traceId);
assert.equal(steerBody.steerTraceId, "trc_steer_server_test");
assert.equal(steerBody.agentRun.runId, "run_hwlab_adapter");
assert.equal(steerBody.agentRun.targetCommandId, "cmd_hwlab_adapter");
assert.equal(steerBody.agentRun.steerCommandId, "cmd_hwlab_adapter_steer");
assert.ok(steerBody.runnerTrace.events.some((event) => event.label === "agentrun:steer:command-created"));
const secondTraceId = "trc_server-test-agentrun-adapter-second";
const second = await fetch(`http://127.0.0.1:${port}/v1/agent/chat`, {
method: "POST",
headers: { "content-type": "application/json", "x-trace-id": secondTraceId, cookie: "hwlab_session=test-stub-session" },
body: JSON.stringify({
conversationId: "cnv_server-test-agentrun",
projectId: "prj_hwpod_workbench",
sessionId: "ses_server-test-agentrun",
ownerUserId: "usr_agent_owner",
ownerRole: "user",
threadId: "019e8078-db67-7750-a5d9-1a99f3abd445",
message: "AgentRun adapter smoke 第二轮:总结我们刚才的对话内容"
})
});
assert.equal(second.status, 202);
const secondPayload = await pollAgentResult(port, secondTraceId);
validateCodeAgentChatSchema(secondPayload);
assert.equal(secondPayload.status, "completed");
assert.equal(secondPayload.provider, "deepseek");
assert.equal(secondPayload.backend, "agentrun-v01/deepseek");
assert.equal(secondPayload.runner.kind, "agentrun-v01-shared-runner");
assert.equal(secondPayload.runner.codexStdio, false);
assert.equal(secondPayload.agentRun.runId, "run_hwlab_adapter");
assert.equal(secondPayload.agentRun.commandId, "cmd_hwlab_adapter_second");
assert.equal(secondPayload.providerTrace.commandId, "cmd_hwlab_adapter_second");
assert.equal(secondPayload.providerTrace.traceId, secondTraceId);
assert.equal(secondPayload.agentRun.providerTrace.commandId, "cmd_hwlab_adapter_second");
assert.equal(secondPayload.agentRun.providerTrace.traceId, secondTraceId);
assert.equal(secondPayload.agentRun.jobName, "agentrun-v01-runner-hwlab-adapter-second");
assert.equal(secondPayload.sessionReuse.reused, true);
assert.equal(secondPayload.agentRun.runnerJobCount, 1);
assert.match(secondPayload.reply.content, /复用已有 runner/u);
assert.ok(secondPayload.runnerTrace.events.some((event) => event.label === "agentrun:run:reused"));
assert.ok(secondPayload.runnerTrace.events.some((event) => event.label === "agentrun:runner-job:ensured"));
assert.equal(secondPayload.runnerTrace.events.some((event) => String(event.text ?? event.message ?? "").includes("旧 command 尾部")), false);
assert.equal(calls.filter((call) => call.method === "POST" && call.path === "/api/v1/runs").length, 1);
assert.equal(calls.filter((call) => call.method === "POST" && call.path === "/api/v1/runs/run_hwlab_adapter/runner-jobs").length, 2);
assert.equal(calls.filter((call) => call.method === "POST" && call.path === "/api/v1/runs/run_hwlab_adapter/commands").length, 3);
} 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 reports persistent thread resume when a completed run needs a new runner", async () => {
const calls = [];
const hwlabSessionId = "ses_server-test-thread-resume";
const agentRunSessionId = "ses_agentrun_deepseek_server_test_thread_resume";
const conversationId = "cnv_server-test-thread-resume";
const threadId = "019e90e0-9535-7130-a894-47ef4e127206";
const ownerSessions = new Map([[hwlabSessionId, testAgentSessionRecord({
sessionId: hwlabSessionId,
conversationId,
threadId,
traceId: "trc_previous_thread_resume",
status: "idle",
session: {
agentRun: {
adapter: "agentrun-v01",
backendProfile: "deepseek",
managerUrl: "http://127.0.0.1:1",
runId: "run_issue812_previous",
commandId: "cmd_issue812_previous",
jobName: "agentrun-v01-runner-issue812-previous",
namespace: "agentrun-v01",
sessionId: agentRunSessionId,
conversationId,
threadId,
terminalStatus: "completed",
runStatus: "completed",
reuseEligible: true,
reused: 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_issue812_resume" })}\n`);
};
if (request.method === "GET" && url.pathname === "/api/v1/runs/run_issue812_previous") {
return send({
id: "run_issue812_previous",
status: "completed",
terminalStatus: "completed",
backendProfile: "deepseek",
sessionRef: { sessionId: agentRunSessionId, conversationId, threadId, metadata: {} }
});
}
if (request.method === "POST" && url.pathname === "/api/v1/sessions") {
assert.equal(body.sessionId, agentRunSessionId);
assert.equal(body.backendProfile, "deepseek");
return send({ sessionId: body.sessionId, backendProfile: body.backendProfile });
}
if (request.method === "POST" && url.pathname === "/api/v1/runs") {
assert.equal(body.sessionRef.sessionId, agentRunSessionId);
assert.equal(body.sessionRef.conversationId, conversationId);
assert.equal(body.sessionRef.threadId, threadId);
assert.equal(body.sessionRef.metadata.hwlabSessionId, hwlabSessionId);
assert.equal(body.sessionRef.metadata.threadContinuityPolicy, "hwlab-agentrun-v01-reuse-runner-thread");
return send({ id: "run_issue812_resume", status: "pending", backendProfile: "deepseek", sessionRef: body.sessionRef, resourceBundleRef: body.resourceBundleRef });
}
if (request.method === "POST" && url.pathname === "/api/v1/runs/run_issue812_resume/commands") {
assert.equal(body.type, "turn");
assert.equal(body.payload.sessionId, agentRunSessionId);
assert.equal(body.payload.hwlabSessionId, hwlabSessionId);
assert.equal(body.payload.threadId, threadId);
assert.match(body.payload.prompt, /ISSUE812_RESUME/u);
return send({ id: "cmd_issue812_resume", runId: "run_issue812_resume", state: "pending", type: "turn", seq: 1 });
}
if (request.method === "GET" && url.pathname === "/api/v1/runs/run_issue812_resume/commands") {
return send({ items: [
{ id: "cmd_issue812_resume", runId: "run_issue812_resume", state: "completed", type: "turn", seq: 1, idempotencyKey: "trc_server-test-issue812-resume", payload: { traceId: "trc_server-test-issue812-resume", conversationId, hwlabSessionId, threadId, providerProfile: "deepseek" } }
] });
}
if (request.method === "POST" && url.pathname === "/api/v1/runs/run_issue812_resume/runner-jobs") {
assert.equal(body.commandId, "cmd_issue812_resume");
return send({
action: "create-kubernetes-job",
runId: "run_issue812_resume",
commandId: "cmd_issue812_resume",
attemptId: "attempt_issue812_resume",
runnerId: "runner_issue812_resume",
namespace: "agentrun-v01",
jobName: "agentrun-v01-runner-issue812-resume",
jobIdentity: { namespace: "agentrun-v01", name: "agentrun-v01-runner-issue812-resume" },
runner: { attemptId: "attempt_issue812_resume", runnerId: "runner_issue812_resume" }
});
}
if (request.method === "GET" && url.pathname === "/api/v1/runs/run_issue812_resume/events") {
return send({ items: [
{ id: "evt_issue812_1", runId: "run_issue812_resume", seq: 1, type: "backend_status", payload: { phase: "runner-job-created", commandId: "cmd_issue812_resume", attemptId: "attempt_issue812_resume", jobName: "agentrun-v01-runner-issue812-resume", namespace: "agentrun-v01" }, createdAt: "2026-06-04T00:00:00.000Z" },
{ id: "evt_issue812_2", runId: "run_issue812_resume", seq: 2, type: "backend_status", payload: { phase: "initial-prompt-assembly", commandId: "cmd_issue812_resume", initialPromptInjected: false, reason: "thread-resume" }, createdAt: "2026-06-04T00:00:00.500Z" },
{ id: "evt_issue812_3", runId: "run_issue812_resume", seq: 3, type: "assistant_message", payload: { commandId: "cmd_issue812_resume", text: "ISSUE812_RESUME_OK" }, createdAt: "2026-06-04T00:00:01.000Z" },
{ id: "evt_issue812_4", runId: "run_issue812_resume", seq: 4, type: "terminal_status", payload: { commandId: "cmd_issue812_resume", terminalStatus: "completed" }, createdAt: "2026-06-04T00:00:02.000Z" }
] });
}
if (request.method === "GET" && url.pathname === "/api/v1/runs/run_issue812_resume/commands/cmd_issue812_resume/result") {
return send({
runId: "run_issue812_resume",
commandId: "cmd_issue812_resume",
attemptId: "attempt_issue812_resume",
runnerId: "runner_issue812_resume",
jobName: "agentrun-v01-runner-issue812-resume",
namespace: "agentrun-v01",
status: "completed",
runStatus: "completed",
commandState: "completed",
terminalStatus: "completed",
completed: true,
reply: "ISSUE812_RESUME_OK",
lastSeq: 4,
eventCount: 4,
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_issue812_resume" })}\n`);
});
await new Promise((resolve) => agentRunServer.listen(0, "127.0.0.1", resolve));
const agentRunPort = agentRunServer.address().port;
for (const record of ownerSessions.values()) {
record.session.agentRun.managerUrl = `http://127.0.0.1:${agentRunPort}`;
}
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_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-issue812-resume";
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,
message: "ISSUE812_RESUME after completed AgentRun run"
})
});
assert.equal(submit.status, 202);
const payload = await pollAgentResult(port, traceId);
validateCodeAgentChatSchema(payload);
assert.equal(payload.status, "completed");
assert.equal(payload.sessionId, hwlabSessionId);
assert.equal(payload.threadId, threadId);
assert.equal(payload.agentRun.runId, "run_issue812_resume");
assert.equal(payload.agentRun.reused, false);
assert.equal(payload.agentRun.runnerReused, false);
assert.equal(payload.agentRun.threadReused, true);
assert.equal(payload.agentRun.persistentResume, true);
assert.equal(payload.sessionReuse.threadId, threadId);
assert.equal(payload.sessionReuse.reused, true);
assert.equal(payload.sessionReuse.status, "thread-resumed");
assert.equal(payload.sessionReuse.runnerReused, false);
assert.equal(payload.sessionReuse.threadReused, true);
assert.equal(payload.sessionReuse.persistentResume, true);
assert.equal(payload.reply.content, "ISSUE812_RESUME_OK");
assert.equal(calls.some((call) => call.method === "GET" && call.path === "/api/v1/runs/run_issue812_previous"), true);
assert.equal(calls.filter((call) => call.method === "POST" && call.path === "/api/v1/runs").length, 1);
assert.equal(calls.filter((call) => call.method === "POST" && call.path === "/api/v1/runs/run_issue812_resume/runner-jobs").length, 1);
} 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 exposes invalid tool-call attribution in result payload", async () => {
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;
const send = (data) => {
response.writeHead(200, { "content-type": "application/json" });
response.end(`${JSON.stringify({ ok: true, data, traceId: "trc_fake_agentrun_invalid_tool" })}\n`);
};
if (request.method === "POST" && url.pathname === "/api/v1/runs") {
return send({ id: "run_invalid_tool", status: "pending", backendProfile: "deepseek", sessionRef: body.sessionRef, resourceBundleRef: body.resourceBundleRef });
}
if (request.method === "POST" && url.pathname === "/api/v1/runs/run_invalid_tool/commands") {
return send({ id: "cmd_invalid_tool", runId: "run_invalid_tool", state: "pending", type: "turn", seq: 1 });
}
if (request.method === "GET" && url.pathname === "/api/v1/runs/run_invalid_tool/commands") {
return send({ items: [
{ id: "cmd_invalid_tool", runId: "run_invalid_tool", state: "failed", type: "turn", seq: 1, idempotencyKey: "trc_server-test-agentrun-invalid-tool", payload: { traceId: "trc_server-test-agentrun-invalid-tool", conversationId: "cnv_invalid_tool", hwlabSessionId: "ses_server-test-invalid-tool", threadId: "thread_invalid_tool", providerProfile: "deepseek" } }
] });
}
if (request.method === "POST" && url.pathname === "/api/v1/runs/run_invalid_tool/runner-jobs") {
return send({
action: "create-kubernetes-job",
runId: "run_invalid_tool",
commandId: "cmd_invalid_tool",
attemptId: "attempt_invalid_tool",
runnerId: "runner_invalid_tool",
namespace: "agentrun-v01",
jobName: "agentrun-v01-runner-invalid-tool",
runner: { attemptId: "attempt_invalid_tool", runnerId: "runner_invalid_tool" }
});
}
if (request.method === "GET" && url.pathname === "/api/v1/runs/run_invalid_tool/events") {
return send({ items: [
{ id: "evt_invalid_tool", runId: "run_invalid_tool", seq: 1, type: "error", payload: { commandId: "cmd_invalid_tool", failureKind: "provider-invalid-tool-call", message: "invalid params, invalid function arguments json string, tool_call_id: call_function_selftest_2 (2013)" }, createdAt: "2026-06-02T00:00:00.000Z" },
{ id: "evt_invalid_tool_terminal", runId: "run_invalid_tool", seq: 2, type: "terminal_status", payload: { commandId: "cmd_invalid_tool", terminalStatus: "failed", failureKind: "provider-invalid-tool-call" }, createdAt: "2026-06-02T00:00:01.000Z" }
] });
}
if (request.method === "GET" && url.pathname === "/api/v1/runs/run_invalid_tool/commands/cmd_invalid_tool/result") {
return send({
runId: "run_invalid_tool",
commandId: "cmd_invalid_tool",
attemptId: "attempt_invalid_tool",
runnerId: "runner_invalid_tool",
jobName: "agentrun-v01-runner-invalid-tool",
namespace: "agentrun-v01",
status: "failed",
runStatus: "failed",
commandState: "failed",
terminalStatus: "failed",
failureKind: "provider-invalid-tool-call",
failureMessage: "invalid params, invalid function arguments json string, tool_call_id: call_function_selftest_2 (2013)",
completed: false,
lastSeq: 2,
eventCount: 2,
sessionRef: { sessionId: "ses_agentrun_invalid_tool", conversationId: "cnv_invalid_tool", threadId: "thread_invalid_tool" }
});
}
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_agentrun_invalid_tool" })}\n`);
});
await new Promise((resolve) => agentRunServer.listen(0, "127.0.0.1", resolve));
const agentRunPort = agentRunServer.address().port;
const ownerSessions = new Map([["ses_server-test-invalid-tool", testAgentSessionRecord({
sessionId: "ses_server-test-invalid-tool",
conversationId: "cnv_invalid_tool",
status: "idle"
})]]);
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_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-invalid-tool";
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_invalid_tool", sessionId: "ses_server-test-invalid-tool", message: "invalid tool-call attribution" })
});
assert.equal(submit.status, 202);
const payload = await pollAgentResult(port, traceId);
validateCodeAgentChatSchema(payload);
assert.equal(payload.status, "failed");
assert.equal(payload.providerTrace.failureKind, "provider-invalid-tool-call");
assert.equal(payload.error.code, "provider-invalid-tool-call");
assert.equal(payload.error.category, "provider_invalid_tool_call");
assert.match(payload.error.userMessage, /无效 tool-call arguments JSON/u);
assert.match(payload.error.userMessage, /AgentRun\/provider/u);
assert.match(payload.error.userMessage, /不是 HWPOD/u);
assert.equal(payload.blocker.category, "provider_invalid_tool_call");
assert.match(payload.blocker.summary, /invalid function arguments json string/u);
assert.equal(payload.session.status, "failed");
assert.equal(payload.session.lifecycle.requiresNewSession, true);
assert.equal(payload.sessionReuse.threadId, "thread_invalid_tool");
assert.equal(payload.agentRun.reuseEligible, false);
assert.equal(payload.reuseEligible, false);
const serializedPayload = JSON.stringify(payload);
assert.equal(serializedPayload.includes("repo-owned-codex"), false);
assert.equal(serializedPayload.includes("codex-app-server-stdio"), false);
assert.equal(serializedPayload.includes("\"provider\":\"codex-stdio\""), 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 maps minimax-m3 provider profile to AgentRun backend", async () => {
const calls = [];
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_agentrun_minimax_m3" })}\n`);
};
if (request.method === "POST" && url.pathname === "/api/v1/runs") {
assert.equal(body.backendProfile, "minimax-m3");
assert.equal(body.sessionRef.sessionId, "ses_agentrun_minimax_m3_server_test_minimax_m3");
assert.equal(body.sessionRef.metadata.hwlabSessionId, "ses_server-test-minimax-m3");
assert.equal(body.sessionRef.metadata.agentRunSessionProfile, "minimax-m3");
assert.equal(body.sessionRef.metadata.agentRunSessionPolicy, "backend-profile-scoped");
assert.equal(body.executionPolicy.secretScope.providerCredentials[0].profile, "minimax-m3");
assert.equal(body.executionPolicy.secretScope.providerCredentials[0].secretRef.name, "agentrun-v01-provider-minimax-m3");
assert.equal(body.executionPolicy.secretScope.providerCredentials[0].secretRef.namespace, "agentrun-v01");
return send({ id: "run_hwlab_minimax_m3", status: "pending", backendProfile: "minimax-m3", sessionRef: body.sessionRef, resourceBundleRef: body.resourceBundleRef });
}
if (request.method === "POST" && url.pathname === "/api/v1/runs/run_hwlab_minimax_m3/commands") {
assert.equal(body.payload.providerProfile, "minimax-m3");
assert.equal(body.payload.sessionId, "ses_agentrun_minimax_m3_server_test_minimax_m3");
assert.equal(body.payload.hwlabSessionId, "ses_server-test-minimax-m3");
assert.match(body.payload.prompt, /MiniMax-M3/u);
return send({ id: "cmd_hwlab_minimax_m3", runId: "run_hwlab_minimax_m3", state: "pending", type: "turn", seq: 1 });
}
if (request.method === "GET" && url.pathname === "/api/v1/runs/run_hwlab_minimax_m3/commands") {
return send({ items: [
{ id: "cmd_hwlab_minimax_m3", runId: "run_hwlab_minimax_m3", state: "completed", type: "turn", seq: 1, idempotencyKey: "trc_server-test-agentrun-minimax-m3", payload: { traceId: "trc_server-test-agentrun-minimax-m3", conversationId: "cnv_server-test-minimax-m3", hwlabSessionId: "ses_server-test-minimax-m3", threadId: null, providerProfile: "minimax-m3" } }
] });
}
if (request.method === "POST" && url.pathname === "/api/v1/runs/run_hwlab_minimax_m3/runner-jobs") {
assert.equal(body.commandId, "cmd_hwlab_minimax_m3");
return send({
action: "create-kubernetes-job",
runId: "run_hwlab_minimax_m3",
commandId: "cmd_hwlab_minimax_m3",
attemptId: "attempt_hwlab_minimax_m3",
runnerId: "runner_hwlab_minimax_m3",
namespace: "agentrun-v01",
jobName: "agentrun-v01-runner-hwlab-minimax-m3",
jobIdentity: { namespace: "agentrun-v01", name: "agentrun-v01-runner-hwlab-minimax-m3" },
runner: { attemptId: "attempt_hwlab_minimax_m3", runnerId: "runner_hwlab_minimax_m3" }
});
}
if (request.method === "GET" && url.pathname === "/api/v1/runs/run_hwlab_minimax_m3/events") {
return send({ items: [
{ id: "evt_minimax_1", runId: "run_hwlab_minimax_m3", seq: 1, type: "backend_status", payload: { phase: "runner-job-created", commandId: "cmd_hwlab_minimax_m3", attemptId: "attempt_hwlab_minimax_m3", jobName: "agentrun-v01-runner-hwlab-minimax-m3", namespace: "agentrun-v01" }, createdAt: "2026-06-02T00:00:00.000Z" },
{ id: "evt_minimax_2", runId: "run_hwlab_minimax_m3", seq: 2, type: "assistant_message", payload: { commandId: "cmd_hwlab_minimax_m3", text: "AGENTRUN_MINIMAX_M3_OK" }, createdAt: "2026-06-02T00:00:01.000Z" },
{ id: "evt_minimax_3", runId: "run_hwlab_minimax_m3", seq: 3, type: "terminal_status", payload: { commandId: "cmd_hwlab_minimax_m3", terminalStatus: "completed" }, createdAt: "2026-06-02T00:00:02.000Z" }
] });
}
if (request.method === "GET" && url.pathname === "/api/v1/runs/run_hwlab_minimax_m3/commands/cmd_hwlab_minimax_m3/result") {
return send({
runId: "run_hwlab_minimax_m3",
commandId: "cmd_hwlab_minimax_m3",
attemptId: "attempt_hwlab_minimax_m3",
runnerId: "runner_hwlab_minimax_m3",
jobName: "agentrun-v01-runner-hwlab-minimax-m3",
namespace: "agentrun-v01",
status: "completed",
runStatus: "completed",
commandState: "completed",
terminalStatus: "completed",
completed: true,
reply: "AGENTRUN_MINIMAX_M3_OK",
lastSeq: 3,
eventCount: 3,
sessionRef: { sessionId: "ses_agentrun_minimax_m3_server_test_minimax_m3", conversationId: "cnv_server-test-minimax-m3", threadId: null }
});
}
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_agentrun_minimax_m3" })}\n`);
});
await new Promise((resolve) => agentRunServer.listen(0, "127.0.0.1", resolve));
const agentRunPort = agentRunServer.address().port;
const minimaxOwnerSessions = new Map([["ses_server-test-minimax-m3", testAgentSessionRecord({
sessionId: "ses_server-test-minimax-m3",
conversationId: "cnv_server-test-minimax-m3",
status: "idle"
})]]);
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_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);
minimaxOwnerSessions.set(record.id, record);
return record;
},
async getAgentSession(sessionId) {
return minimaxOwnerSessions.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-minimax-m3";
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-minimax-m3",
sessionId: "ses_server-test-minimax-m3",
providerProfile: "minimax-m3",
message: "MiniMax-M3 AgentRun adapter smoke"
})
});
assert.equal(submit.status, 202);
const accepted = await submit.json();
assert.equal(accepted.shortConnection, true);
const payload = await pollAgentResult(port, traceId);
validateCodeAgentChatSchema(payload);
assert.equal(payload.status, "completed");
assert.equal(payload.provider, "minimax-m3");
assert.equal(payload.backend, "agentrun-v01/minimax-m3");
assert.equal(payload.infrastructureBackend, "agentrun-v01/minimax-m3");
assert.equal(payload.runner.kind, "agentrun-v01-shared-runner");
assert.equal(payload.runner.provider, "minimax-m3");
assert.equal(payload.runner.codexStdio, false);
assert.equal(payload.agentRun.backendProfile, "minimax-m3");
assert.equal(payload.sessionId, "ses_server-test-minimax-m3");
assert.equal(payload.agentRun.sessionId, "ses_agentrun_minimax_m3_server_test_minimax_m3");
assert.equal(payload.providerTrace.backendProfile, "minimax-m3");
assert.equal(payload.agentRun.jobName, "agentrun-v01-runner-hwlab-minimax-m3");
assert.equal(payload.reply.content, "AGENTRUN_MINIMAX_M3_OK");
assert.equal(calls.filter((call) => call.method === "POST" && call.path === "/api/v1/runs").length, 1);
} 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 scopes AgentRun sessions by backend profile", async () => {
const calls = [];
const hwlabSessionId = "ses_server-test-profile-switch";
const minimaxSessionId = "ses_agentrun_minimax_m3_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_deepseek_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.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);
return send({ id: "cmd_hwlab_profile_switch_m3", runId: "run_hwlab_profile_switch_m3", state: "pending", type: "turn", seq: 1 });
}
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 === "POST" && url.pathname === "/api/v1/runs/run_hwlab_profile_switch_m3/runner-jobs") {
assert.equal(body.commandId, "cmd_hwlab_profile_switch_m3");
return send({
action: "create-kubernetes-job",
runId: "run_hwlab_profile_switch_m3",
commandId: "cmd_hwlab_profile_switch_m3",
attemptId: "attempt_hwlab_profile_switch_m3",
runnerId: "runner_hwlab_profile_switch_m3",
namespace: "agentrun-v01",
jobName: "agentrun-v01-runner-profile-switch-m3",
jobIdentity: { namespace: "agentrun-v01", name: "agentrun-v01-runner-profile-switch-m3" },
runner: { attemptId: "attempt_hwlab_profile_switch_m3", runnerId: "runner_hwlab_profile_switch_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,
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_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);
validateCodeAgentChatSchema(payload);
assert.equal(payload.status, "completed");
assert.equal(payload.sessionId, hwlabSessionId);
assert.equal(payload.infrastructureBackend, "agentrun-v01/minimax-m3");
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(payload.reply.content, "AGENTRUN_HWLAB_MINIMAX_M3_OK");
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.agentRun.backendProfile, "minimax-m3");
assert.equal(stored.session.agentRun.sessionId, minimaxSessionId);
} 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",
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, 500);
const body = await response.json();
assert.equal(body.error.code, "internal_error");
assert.match(body.error.reason, /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, 1);
assert.equal(ownerRecords[0].ownerUserId, "usr_agent_owner");
assert.equal(ownerRecords[0].sessionId, "ses_server_stdio_pwd");
assert.equal(ownerRecords[0].conversationId, "cnv_server-test-owner");
assert.equal(ownerRecords[0].traceId, "trc_server-test-owner");
assert.equal(ownerRecords[0].session.messageCount, 2);
assert.equal(ownerRecords[0].session.messages.length, 2);
assert.equal(ownerRecords[0].session.messages[0].role, "user");
assert.equal(ownerRecords[0].session.messages[0].text, "owner binding smoke");
assert.equal(ownerRecords[0].session.messages[0].status, "sent");
assert.equal(ownerRecords[0].session.messages[1].role, "agent");
assert.equal(ownerRecords[0].session.messages[1].status, "completed");
assert.equal(ownerRecords[0].session.messages[1].traceId, "trc_server-test-owner");
assert.equal(ownerRecords[0].session.firstUserMessagePreview, "owner binding smoke");
assert.equal(ownerRecords[0].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/chat/trace/${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 uses AgentRun command result evidence when live trace store has expired", 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_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/chat/trace/trc_issue842_expired`, {
headers: { cookie: "hwlab_session=test-stub-session" }
});
assert.equal(response.status, 200);
const body = await response.json();
assert.equal(body.ok, true);
assert.equal(body.status, "expired");
assert.equal(body.traceStatus, "expired");
assert.equal(body.eventCount, 0);
assert.equal(body.terminalEvidence.available, true);
assert.equal(body.terminalEvidence.source, "agentrun-command-result");
assert.equal(body.finalResponse.text, commandFinalText);
assert.equal(body.traceSummary.source, "agentrun-command-result");
assert.equal(body.traceSummary.sourceEventCount, 0);
assert.equal(body.agentRun.runId, "run_issue842_expired");
assert.equal(body.agentRun.commandId, "cmd_issue842_expired");
assert.equal(body.retention.liveTraceStore, "expired-or-evicted");
assert.equal(JSON.stringify(body).includes("旧 session snapshot final"), false);
assert.ok(agentRunCalls.some((call) => call.path === "/api/v1/runs/run_issue842_expired/commands"));
assert.ok(agentRunCalls.some((call) => call.path === "/api/v1/runs/run_issue842_expired/commands/cmd_issue842_expired/result"));
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 replays an earlier AgentRun command after same-run lastSeq advances (#955)", 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_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/chat/trace/${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, "completed");
assert.ok(calls.some((call) => call.path === `/api/v1/runs/${runId}/events` && call.afterSeq === "0"));
assert.ok(body.events.some((event) => event.commandId === firstCommandId && event.label === "item/commandExecution:completed"));
assert.ok(body.events.some((event) => event.commandId === firstCommandId && event.label === "agentrun:assistant:message"));
assert.equal(body.events.some((event) => event.commandId === secondCommandId), false);
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 refreshes AgentRun events without waiting for command result (#1000)", 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_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/chat/trace/${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.eventCount, 4);
assert.ok(body.events.some((event) => event.label === "item/commandExecution:completed"));
assert.ok(body.events.some((event) => event.label === "agentrun:assistant:message"));
assert.ok(calls.some((call) => call.path === `/api/v1/runs/${runId}/events`));
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 repairs historical same-session AgentRun trace after lastTraceId advances (#955)", async () => {
const calls = [];
const ownerSessions = new Map();
const runId = "run_issue955_historical";
const firstTraceId = "trc_issue955_historical_first";
const secondTraceId = "trc_issue955_historical_second";
const firstCommandId = "cmd_issue955_historical_first";
const secondCommandId = "cmd_issue955_historical_second";
const firstFinalText = "目前只有一个 HWPOD 可用:\n\n- d601-f103-v2 (board: D601-F103-V2 / STM32F103)\n\nAPI 返回 count=1, availableCount=1。";
const secondFinalText = "编译成功!hwpod build completed。";
const events = [
{ id: "evt_issue955_hist_first_created", runId, seq: 1, type: "command_created", payload: { commandId: firstCommandId }, createdAt: "2026-06-06T01:43:23.000Z" },
{ id: "evt_issue955_hist_first_tool", runId, seq: 25, 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}', commandId: firstCommandId }, createdAt: "2026-06-06T01:43:46.000Z" },
{ id: "evt_issue955_hist_first_message", runId, seq: 34, type: "assistant_message", payload: { commandId: firstCommandId, itemId: "msg_issue955_first", text: firstFinalText }, createdAt: "2026-06-06T01:46:19.000Z" },
{ id: "evt_issue955_hist_first_done", runId, seq: 35, type: "terminal_status", payload: { commandId: firstCommandId, terminalStatus: "completed" }, createdAt: "2026-06-06T01:46:19.000Z" },
{ id: "evt_issue955_hist_second_created", runId, seq: 36, type: "command_created", payload: { commandId: secondCommandId }, createdAt: "2026-06-06T01:46:11.000Z" },
{ id: "evt_issue955_hist_second_message", runId, seq: 58, type: "assistant_message", payload: { commandId: secondCommandId, itemId: "msg_issue955_second", text: secondFinalText }, createdAt: "2026-06-06T01:46:28.000Z" },
{ id: "evt_issue955_hist_second_done", runId, seq: 67, 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, search: url.search });
const send = (data) => {
response.writeHead(200, { "content-type": "application/json" });
response.end(`${JSON.stringify({ ok: true, data, traceId: "trc_fake_issue955_historical" })}\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:46.360Z", payload: { traceId: firstTraceId, conversationId: "cnv_issue955_historical", hwlabSessionId: "ses_issue955_historical", threadId: "thread-issue955-historical", providerProfile: "deepseek" } },
{ id: secondCommandId, runId, state: "completed", idempotencyKey: secondTraceId, createdAt: "2026-06-06T01:46:11.000Z", updatedAt: "2026-06-06T01:46:28.000Z", payload: { traceId: secondTraceId, conversationId: "cnv_issue955_historical", hwlabSessionId: "ses_issue955_historical", threadId: "thread-issue955-historical", 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", conversationId: "cnv_issue955_historical", threadId: "thread-issue955-historical" } });
}
if (request.method === "GET" && url.pathname === `/api/v1/runs/${runId}/commands/${secondCommandId}/result`) {
return send({ runId, commandId: secondCommandId, status: "completed", runStatus: "completed", commandState: "completed", terminalStatus: "completed", completed: true, reply: secondFinalText, lastSeq: 67, eventCount: 67, sessionRef: { sessionId: "ses_agentrun_deepseek_issue955", conversationId: "cnv_issue955_historical", threadId: "thread-issue955-historical" } });
}
if (request.method === "GET" && url.pathname === `/api/v1/runs/${runId}/events`) {
return send({ items: events });
}
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;
ownerSessions.set("ses_issue955_historical", testAgentSessionRecord({
sessionId: "ses_issue955_historical",
projectId: "prj_v02_code_agent",
conversationId: "cnv_issue955_historical",
threadId: "thread-issue955-historical",
lastTraceId: secondTraceId,
status: "active",
session: {
messages: [
{ id: "msg_issue955_user_first", role: "user", text: "看看有几个hwpod可用", traceId: firstTraceId, status: "submitted" },
{ id: "msg_issue955_user_second", role: "user", text: "试一下编译 d601-f103-v2", traceId: secondTraceId, status: "submitted" },
{ id: "msg_issue955_second", role: "agent", text: firstFinalText, traceId: secondTraceId, status: "idle" }
],
agentRun: { adapter: "agentrun-v01", managerUrl: `http://127.0.0.1:${agentRunPort}`, runId, commandId: firstCommandId, backendProfile: "deepseek", terminalStatus: "completed", lastSeq: 35, valuesPrinted: false },
finalResponse: { text: firstFinalText, textChars: firstFinalText.length, role: "assistant", status: "completed", traceId: secondTraceId, valuesPrinted: false },
traceSummary: { traceId: secondTraceId, source: "code-agent-derived-session-evidence", sourceEventCount: 36, terminalStatus: "completed", agentRun: { runId, commandId: firstCommandId, lastSeq: 35, valuesPrinted: false }, valuesPrinted: false },
traceResults: {
[secondTraceId]: {
traceId: secondTraceId,
status: "completed",
conversationId: "cnv_issue955_historical",
sessionId: "ses_issue955_historical",
threadId: "thread-issue955-historical",
finalResponse: { text: firstFinalText, textChars: firstFinalText.length, role: "assistant", status: "completed", traceId: secondTraceId, valuesPrinted: false },
traceSummary: { traceId: secondTraceId, source: "agent-session-trace-repair", sourceEventCount: 4, 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, backendProfile: "deepseek", terminalStatus: "completed", lastSeq: 35, valuesPrinted: false },
valuesRedacted: true
}
},
valuesRedacted: true
}
}));
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_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 || session.session?.messages?.some((message) => message.traceId === traceId)) ?? null;
},
async recordAgentSessionOwner(input) {
const existing = ownerSessions.get(input.sessionId) ?? testAgentSessionRecord(input);
const nextSession = { ...(existing.session ?? {}), ...(input.session ?? {}) };
nextSession.traceResults = { ...(existing.session?.traceResults ?? {}), ...(input.session?.traceResults ?? {}) };
const record = testAgentSessionRecord({ ...existing, ...input, session: nextSession });
ownerSessions.set(record.id, record);
return record;
}
}
});
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/trace/${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, "completed");
assert.equal(body.finalResponse.text, firstFinalText);
assert.equal(body.agentRun.commandId, firstCommandId);
assert.equal(body.eventCount, 4);
assert.equal(body.traceSummary.sourceEventCount, body.eventCount);
assert.ok(body.events.some((event) => event.commandId === firstCommandId));
assert.equal(body.events.some((event) => event.commandId === secondCommandId), false);
assert.match(text, /d601-f103-v2/u);
assert.doesNotMatch(text, /编译成功/u);
assert.ok(calls.some((call) => call.path === `/api/v1/runs/${runId}/commands`));
assert.ok(calls.some((call) => call.path === `/api/v1/runs/${runId}/commands/${firstCommandId}/result`));
assert.equal(calls.some((call) => call.path === `/api/v1/runs/${runId}/commands/${secondCommandId}/result`), false);
assert.equal(ownerSessions.get("ses_issue955_historical").session.traceResults[firstTraceId].finalResponse.text, firstFinalText);
const repairedSession = ownerSessions.get("ses_issue955_historical");
assert.equal(repairedSession.lastTraceId, secondTraceId);
assert.equal(repairedSession.session.traceResults[firstTraceId].agentRun.commandId, firstCommandId);
const secondResultResponse = await fetch(`http://127.0.0.1:${port}/v1/agent/chat/result/${secondTraceId}`, {
headers: { cookie: "hwlab_session=test-stub-session" }
});
assert.equal(secondResultResponse.status, 200);
const secondResult = await secondResultResponse.json();
const secondText = JSON.stringify(secondResult);
assert.equal(secondResult.traceId, secondTraceId);
assert.equal(secondResult.status, "completed");
assert.equal(secondResult.agentRun.commandId, secondCommandId);
assert.equal(secondResult.agentRun.traceId, secondTraceId);
assert.equal(secondResult.terminalEvidence.available, true);
assert.equal(secondResult.terminalEvidence.agentRun.commandId, secondCommandId);
assert.equal(secondResult.terminalEvidence.finalResponse.traceId, secondTraceId);
assert.equal(secondResult.finalResponse.traceId, secondTraceId);
assert.match(secondText, /编译成功/u);
assert.doesNotMatch(secondText, /目前只有一个 HWPOD 可用/u);
assert.equal(secondText.includes('"fallback"'), false);
assert.ok(calls.some((call) => call.path === `/api/v1/runs/${runId}/commands/${secondCommandId}/result`));
assert.equal(ownerSessions.get("ses_issue955_historical").session.traceResults[secondTraceId].agentRun.commandId, secondCommandId);
assert.equal(ownerSessions.get("ses_issue955_historical").session.traceResults[secondTraceId].finalResponse.text, secondFinalText);
} 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 result polling repairs polluted completed AgentRun memory cache (#955)", async () => {
const calls = [];
const runId = "run_issue955_live_cache";
const firstTraceId = "trc_issue955_live_cache_first";
const secondTraceId = "trc_issue955_live_cache_second";
const firstCommandId = "cmd_issue955_live_cache_first";
const secondCommandId = "cmd_issue955_live_cache_second";
const firstFinalText = "目前只有一个 HWPOD 可用:d601-f103-v2。";
const secondFinalText = "编译成功!hwpod build completedKeil MDK USART atk_f103.hex queued。";
const events = [
{ id: "evt_issue955_live_first_message", runId, seq: 10, type: "assistant_message", payload: { commandId: firstCommandId, itemId: "msg_issue955_live_first", text: firstFinalText }, createdAt: "2026-06-06T01:46:19.000Z" },
{ id: "evt_issue955_live_first_done", runId, seq: 11, type: "terminal_status", payload: { commandId: firstCommandId, terminalStatus: "completed" }, createdAt: "2026-06-06T01:46:19.000Z" },
{ id: "evt_issue955_live_second_message", runId, seq: 20, type: "assistant_message", payload: { commandId: secondCommandId, itemId: "msg_issue955_live_second", text: secondFinalText }, createdAt: "2026-06-06T01:46:28.000Z" },
{ id: "evt_issue955_live_second_done", runId, seq: 21, 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, search: url.search });
const send = (data) => {
response.writeHead(200, { "content-type": "application/json" });
response.end(`${JSON.stringify({ ok: true, data, traceId: "trc_fake_issue955_live_cache" })}\n`);
};
if (request.method === "GET" && url.pathname === `/api/v1/runs/${runId}/commands`) {
return send({ items: [
{ id: firstCommandId, runId, state: "completed", idempotencyKey: firstTraceId, payload: { traceId: firstTraceId, conversationId: "cnv_issue955_live_cache", hwlabSessionId: "ses_issue955_live_cache", threadId: "thread-issue955-live-cache" } },
{ id: secondCommandId, runId, state: "completed", idempotencyKey: secondTraceId, payload: { traceId: secondTraceId, conversationId: "cnv_issue955_live_cache", hwlabSessionId: "ses_issue955_live_cache", threadId: "thread-issue955-live-cache" } }
] });
}
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: 11, eventCount: 11 });
}
if (request.method === "GET" && url.pathname === `/api/v1/runs/${runId}/commands/${secondCommandId}/result`) {
return send({ runId, commandId: secondCommandId, status: "completed", runStatus: "completed", commandState: "completed", terminalStatus: "completed", completed: true, reply: secondFinalText, lastSeq: 21, eventCount: 21 });
}
if (request.method === "GET" && url.pathname === `/api/v1/runs/${runId}/events`) {
return send({ items: events });
}
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();
codeAgentChatResults.set(secondTraceId, {
accepted: true,
status: "completed",
shortConnection: true,
traceId: secondTraceId,
conversationId: "cnv_issue955_live_cache",
sessionId: "ses_issue955_live_cache",
threadId: "thread-issue955-live-cache",
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: secondTraceId, valuesPrinted: false },
traceSummary: { traceId: secondTraceId, source: "code-agent-derived-session-evidence", sourceEventCount: 11, terminalStatus: "completed", agentRun: { runId, commandId: firstCommandId, lastSeq: 11, valuesPrinted: false }, valuesPrinted: false },
agentRun: {
adapter: "agentrun-v01",
managerUrl: `http://127.0.0.1:${agentRunPort}`,
runId,
commandId: firstCommandId,
traceId: secondTraceId,
lastSeq: 11,
terminalStatus: "completed",
providerTrace: { traceId: secondTraceId, runId, commandId: firstCommandId, terminalStatus: "completed", valuesPrinted: false },
valuesPrinted: false
},
providerTrace: { traceId: secondTraceId, runId, commandId: firstCommandId, terminalStatus: "completed", 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_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/chat/result/${secondTraceId}`, {
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, secondTraceId);
assert.equal(body.status, "completed");
assert.equal(body.agentRun.commandId, secondCommandId);
assert.equal(body.agentRun.traceId, secondTraceId);
assert.equal(body.terminalEvidence.available, true);
assert.equal(body.terminalEvidence.agentRun.commandId, secondCommandId);
assert.equal(body.terminalEvidence.finalResponse.traceId, secondTraceId);
assert.equal(body.reply.content, secondFinalText);
assert.match(text, /编译成功/u);
assert.match(text, /Keil MDK/u);
assert.doesNotMatch(text, /目前只有一个 HWPOD 可用/u);
assert.equal(text.includes('"fallback"'), false);
assert.ok(calls.some((call) => call.path === `/api/v1/runs/${runId}/commands`));
assert.ok(calls.some((call) => call.path === `/api/v1/runs/${runId}/commands/${secondCommandId}/result`));
assert.equal(calls.some((call) => call.path === `/api/v1/runs/${runId}/commands/${firstCommandId}/result`), false);
assert.equal(codeAgentChatResults.get(secondTraceId).agentRun.commandId, secondCommandId);
} 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 result polling fails closed when AgentRun command registry misses trace (#955)", async () => {
const calls = [];
const runId = "run_issue955_registry_missing";
const traceId = "trc_issue955_registry_missing";
const staleCommandId = "cmd_issue955_registry_stale";
const staleFinalText = "目前只有一个 HWPOD 可用:d601-f103-v2。";
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 });
if (request.method === "GET" && url.pathname === `/api/v1/runs/${runId}/commands`) {
response.writeHead(200, { "content-type": "application/json" });
response.end(`${JSON.stringify({ ok: true, data: { items: [] }, traceId: "trc_fake_issue955_missing" })}\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 codeAgentChatResults = new Map();
codeAgentChatResults.set(traceId, {
accepted: true,
status: "completed",
shortConnection: true,
traceId,
conversationId: "cnv_issue955_registry_missing",
sessionId: "ses_issue955_registry_missing",
threadId: "thread-issue955-registry-missing",
ownerUserId: TEST_AGENT_ACTOR.id,
ownerRole: TEST_AGENT_ACTOR.role,
reply: { role: "assistant", content: staleFinalText },
finalResponse: { text: staleFinalText, textChars: staleFinalText.length, role: "assistant", status: "completed", traceId, valuesPrinted: false },
agentRun: { adapter: "agentrun-v01", managerUrl: `http://127.0.0.1:${agentRunPort}`, runId, commandId: staleCommandId, traceId, terminalStatus: "completed", 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_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/chat/result/${traceId}`, {
headers: { cookie: "hwlab_session=test-stub-session" }
});
assert.equal(response.status, 404);
const body = await response.json();
const text = JSON.stringify(body);
assert.equal(body.error.code, "agentrun_trace_command_not_found");
assert.equal(body.error.traceId, traceId);
assert.equal(body.error.runId, runId);
assert.doesNotMatch(text, /目前只有一个 HWPOD 可用/u);
assert.ok(calls.some((call) => call.path === `/api/v1/runs/${runId}/commands`));
assert.equal(calls.some((call) => call.path.includes("/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 result polling compacts large runnerTrace while preserving providerTrace", async () => {
const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-result-compact-"));
const codexHome = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-result-compact-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",
HWLAB_CODE_AGENT_RESULT_TRACE_EVENT_LIMIT: "32",
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"
};
},
async probe() {
return {
...codexStdioReadyFixture({ workspace, codexHome }),
sandbox: "danger-full-access"
};
},
async chat(params = {}) {
const base = codexStdioChatFixture({ workspace, codexHome, params });
const events = Array.from({ length: 160 }, (_, index) => ({
seq: index + 1,
traceId: params.traceId,
type: "assistant_message",
status: "chunk",
label: index === 159 ? "assistant:completed" : "assistant:chunk",
createdAt: "2026-05-23T00:00:00.000Z",
waitingFor: index === 159 ? null : "turn/completed",
valuesPrinted: false
}));
return {
...base,
sandbox: "danger-full-access",
session: {
...base.session,
sandbox: "danger-full-access"
},
runnerTrace: {
...base.runnerTrace,
events,
eventLabels: events.map((event) => event.label),
eventCount: events.length,
lastEvent: events.at(-1)
}
};
},
cancel() {},
reapIdle() {}
}
});
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
try {
const { port } = server.address();
const traceId = "trc_server-test-result-compact";
const manualSession = await createManualAgentSession(port, {
conversationId: "cnv_server-test-result-compact"
});
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: "生成大量 trace 后返回"
})
});
assert.equal(submit.status, 202);
const payload = await pollAgentResult(port, traceId);
validateCodeAgentChatSchema(payload);
assert.equal(payload.status, "completed");
assert.equal(payload.traceId, traceId);
assert.equal(payload.providerTrace.protocol, "codex-app-server-jsonrpc-stdio");
assert.equal(payload.providerTrace.command, "codex app-server --listen stdio://");
assert.equal(payload.runnerTrace.eventCount, 160);
assert.equal(payload.runnerTrace.eventsCompacted, true);
assert.equal(payload.runnerTrace.events.length, 32);
assert.equal(payload.runnerTrace.events.some((event) => event.label === "trace:compacted"), true);
assert.equal(payload.runnerTrace.lastEvent.label, "assistant:completed");
assert.equal(payload.runnerTrace.eventLabels.includes("trace:compacted"), true);
} 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 answers skills prompt through real Codex stdio and retains trace", async () => {
const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-stdio-skills-"));
const codexHome = path.join(workspace, "codex-home");
const skillsDir = path.join(workspace, "skills");
const fakeCodex = await createFakeCodexCommand();
await mkdir(path.join(skillsDir, "hwlab-test-skill"), { recursive: true });
await prepareFakeCodexHome(codexHome);
await writeFile(path.join(skillsDir, "hwlab-test-skill", "SKILL.md"), [
"---",
"name: hwlab-test-skill",
"description: Test skill mounted for Codex stdio discovery.",
"version: v-test",
"---",
"",
"# HWLAB Test Skill"
].join("\n"));
const server = createCloudApiServer({
env: {
PATH: process.env.PATH,
OPENAI_API_KEY: "test-openai-key-material",
CODEX_HOME: codexHome,
HWLAB_CODE_AGENT_PROVIDER: "codex-stdio",
HWLAB_CODE_AGENT_MODEL: "gpt-test",
HWLAB_CODE_AGENT_CODEX_COMMAND: fakeCodex.command,
HWLAB_CODE_AGENT_CODEX_STDIO_ENABLED: "1",
HWLAB_CODE_AGENT_CODEX_STDIO_SUPERVISOR: "repo-owned",
HWLAB_CODE_AGENT_CODEX_WORKSPACE: workspace,
HWLAB_CODE_AGENT_SKILLS_DIRS: skillsDir,
HWLAB_CODE_AGENT_SKILLS_STRICT: "1",
HWLAB_CODE_AGENT_OPENAI_BASE_URL: "http://127.0.0.1:65535/v1/responses"
},
codexStdioManager: createCodexStdioSessionManager({
idFactory: () => "ses_server_stdio_skills",
createRpcClient: async () => createFakeAppServerClient({
text: "模型泛化回答不应成为 skills 最终回复。"
})
})
});
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_stdio_skills",
traceId: "trc_server_stdio_skills",
message: "列出你可用的skills"
});
validateCodeAgentChatSchema(payload);
assert.equal(payload.status, "completed");
assert.equal(payload.provider, "codex-stdio");
assert.equal(payload.backend, "hwlab-cloud-api/codex-app-server-stdio");
assert.equal(payload.capabilityLevel, "long-lived-codex-stdio-session");
assert.equal(payload.providerTrace.sidecarOnly, undefined);
assert.equal(payload.providerTrace.toolName, "codex-app-server.thread/start+turn/start");
assert.equal(payload.skills.status, "ready");
const discovered = payload.skills.items.find((skill) => skill.name === "hwlab-test-skill");
assert.ok(discovered);
assert.equal(discovered.source, path.join(skillsDir, "hwlab-test-skill", "SKILL.md"));
assert.equal(discovered.manifest.path, path.join(skillsDir, "hwlab-test-skill", "SKILL.md"));
assert.equal(discovered.version, "v-test");
assert.ok(payload.toolCalls.some((toolCall) => toolCall.name === "skills.discover" && toolCall.status === "completed"));
assert.match(payload.reply.content, /hwlab-test-skill/u);
assert.match(payload.reply.content, /真实 skill manifest 清单/u);
assert.doesNotMatch(payload.reply.content, /模型泛化回答/u);
assert.ok(payload.runnerTrace.events.some((event) => event.label === "prompt:sent"));
assert.ok(payload.runnerTrace.events.some((event) => event.label === "tool:codex-app-server.thread/start+turn/start:started"));
assert.equal(JSON.stringify(payload).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(fakeCodex.root, { recursive: true, force: true });
}
});
test("cloud api /v1/agent/chat returns structured skills blocker without local skills manifest", async () => {
const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-stdio-skills-missing-"));
const codexHome = path.join(workspace, "codex-home");
const fakeCodex = await createFakeCodexCommand();
await prepareFakeCodexHome(codexHome);
const server = createCloudApiServer({
env: {
PATH: process.env.PATH,
OPENAI_API_KEY: "test-openai-key-material",
CODEX_HOME: codexHome,
HWLAB_CODE_AGENT_PROVIDER: "codex-stdio",
HWLAB_CODE_AGENT_MODEL: "gpt-test",
HWLAB_CODE_AGENT_CODEX_COMMAND: fakeCodex.command,
HWLAB_CODE_AGENT_CODEX_STDIO_ENABLED: "1",
HWLAB_CODE_AGENT_CODEX_STDIO_SUPERVISOR: "repo-owned",
HWLAB_CODE_AGENT_CODEX_WORKSPACE: workspace,
HWLAB_CODE_AGENT_SKILLS_DIRS: path.join(workspace, "missing-skills"),
HWLAB_CODE_AGENT_SKILLS_STRICT: "1",
HWLAB_CODE_AGENT_OPENAI_BASE_URL: "http://127.0.0.1:65535/v1/responses"
},
codexStdioManager: createCodexStdioSessionManager({
idFactory: () => "ses_server_stdio_skills_missing",
createRpcClient: async () => createFakeAppServerClient({
text: "模型泛化回答:alpha-skill"
})
})
});
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_stdio_skills_missing",
traceId: "trc_server_stdio_skills_missing",
message: "列出你可用的skills"
});
validateCodeAgentChatSchema(payload);
assert.equal(payload.status, "failed");
assert.equal(payload.provider, "codex-stdio");
assert.equal(payload.backend, "hwlab-cloud-api/codex-app-server-stdio");
assert.equal(payload.capabilityLevel, "blocked");
assert.equal(payload.error.code, "skills_unavailable");
assert.equal(payload.skills.status, "blocked");
assert.equal(payload.skills.blockers[0].code, "skills_unavailable");
assert.deepEqual(payload.skills.sourceIssues, ["pikasTech/HWLAB#136", "pikasTech/HWLAB#237"]);
assert.ok(payload.toolCalls.some((toolCall) => toolCall.name === "skills.discover" && toolCall.status === "blocked"));
assert.equal(payload.toolCalls.some((toolCall) => toolCall.name === "codex-app-server.thread/start+turn/start"), false);
assert.equal(payload.session.status, "idle");
assert.equal(Object.hasOwn(payload, "reply"), false);
assert.equal(JSON.stringify(payload).includes("alpha-skill"), false);
assert.equal(JSON.stringify(payload).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(fakeCodex.root, { recursive: true, force: true });
}
});
test("cloud api /v1/agent/chat blocks forbidden file paths without leaking values", async () => {
const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-file-block-"));
const server = createCloudApiServer({
env: {
PATH: process.env.PATH,
HWLAB_CODE_AGENT_WORKSPACE: workspace
}
});
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-security-block",
traceId: "trc_server-test-security-block",
message: "cat ../outside.txt"
});
assert.equal(payload.status, "failed");
assert.match(payload.error.code, /^(codex_cli_binary_missing|stdio_protocol_not_wired)$/u);
assert.deepEqual(payload.toolCalls, []);
assert.equal(payload.capabilityLevel, "blocked");
assert.ok(payload.runnerTrace.events.some((event) => event.label === "codex-stdio:blocked"));
assert.equal(Object.hasOwn(payload, "reply"), false);
assert.equal(JSON.stringify(payload).includes("sk-"), false);
} finally {
await new Promise((resolve, reject) => {
server.close((error) => (error ? reject(error) : resolve()));
});
}
});
test("cloud api /v1/agent/chat/cancel reports unsupported lifecycle degradation", async () => {
const traceStore = createCodeAgentTraceStore();
traceStore.append("trc_cancel_unsupported", {
type: "request",
status: "running",
label: "request:running",
sessionId: "ses_cancel_unsupported",
sessionStatus: "busy",
sessionLifecycleStatus: "busy"
});
const server = createCloudApiServer({
traceStore,
codexStdioManager: {
get() {
return null;
}
},
env: {
PATH: process.env.PATH,
HWLAB_CODE_AGENT_PROVIDER: "codex-stdio"
}
});
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/cancel`, {
method: "POST",
headers: {
"content-type": "application/json",
"x-trace-id": "trc_cancel_unsupported"
},
body: JSON.stringify({
conversationId: "cnv_cancel_unsupported",
sessionId: "ses_cancel_unsupported",
traceId: "trc_cancel_unsupported"
})
});
assert.equal(response.status, 501);
const payload = await response.json();
assert.equal(payload.status, "degraded");
assert.equal(payload.unsupported, true);
assert.equal(payload.degraded, true);
assert.equal(payload.error.code, "cancel_unsupported");
assert.equal(payload.sessionLifecycleStatus, "failed");
assert.equal(payload.sessionSummary.unsupported, true);
assert.match(payload.sessionSummary.userMessage, /unsupported\/degraded/u);
} finally {
await new Promise((resolve, reject) => {
server.close((error) => (error ? reject(error) : resolve()));
});
}
});
test("cloud api /v1/agent/chat reports Codex stdio blocker instead of structured skills fallback", async () => {
const root = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-no-skills-"));
const skillsDir = path.join(root, "missing-skills");
const server = createCloudApiServer({
env: {
PATH: process.env.PATH,
HWLAB_CODE_AGENT_WORKSPACE: root
},
skillsDirs: [skillsDir],
skillsDirsExact: true
});
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-skills-missing"
});
const response = await fetch(`http://127.0.0.1:${port}/v1/agent/chat`, {
method: "POST",
headers: {
"content-type": "application/json",
cookie: manualSession.cookie
},
body: JSON.stringify({
conversationId: manualSession.conversationId,
sessionId: manualSession.sessionId,
message: "列出你可用的skills"
})
});
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.status, "failed");
assert.equal(payload.provider, "codex-stdio");
assert.notEqual(payload.error.code, "skills_unavailable");
assert.equal(payload.capabilityLevel, "blocked");
assert.equal(payload.skills.status, "not_requested");
assert.deepEqual(payload.toolCalls, []);
assert.ok(payload.runnerLimitations.includes("no-controlled-readonly-fallback"));
assert.equal(Object.hasOwn(payload, "reply"), false);
} finally {
await new Promise((resolve, reject) => {
server.close((error) => (error ? reject(error) : resolve()));
});
}
});
test("cloud api /v1/agent/chat does not run unsupported local grep fallback", async () => {
const workspace = await mkdtemp(path.join(os.tmpdir(), "hwlab-agent-tool-blocked-"));
const server = createCloudApiServer({
env: {
PATH: process.env.PATH,
HWLAB_CODE_AGENT_WORKSPACE: workspace
}
});
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-tool-unavailable"
});
const response = await fetch(`http://127.0.0.1:${port}/v1/agent/chat`, {
method: "POST",
headers: {
"content-type": "application/json",
cookie: manualSession.cookie
},
body: JSON.stringify({
conversationId: manualSession.conversationId,
sessionId: manualSession.sessionId,
message: "请用grep搜索 package.json"
})
});
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.status, "failed");
assert.notEqual(payload.error.code, "tool_unavailable");
assert.equal(payload.provider, "codex-stdio");
assert.equal(payload.capabilityLevel, "blocked");
assert.deepEqual(payload.toolCalls, []);
assert.ok(payload.runnerLimitations.includes("no-controlled-readonly-fallback"));
assert.equal(Object.hasOwn(payload, "reply"), false);
} finally {
await new Promise((resolve, reject) => {
server.close((error) => (error ? reject(error) : resolve()));
});
}
});
test("cloud api /v1/agent/chat does not call delayed provider fallback beyond legacy 4500ms UI timeout", async () => {
let providerCalled = false;
const server = createCloudApiServer({
callCodeAgentProvider: async ({ providerPlan, message, traceId }) => {
providerCalled = true;
await delay(4700);
return {
provider: providerPlan.provider,
model: providerPlan.model,
backend: providerPlan.backend,
content: `延迟真实 provider stub: ${message} / ${traceId}`,
usage: null,
providerTrace: {
source: "delayed-test-provider"
}
};
}
});
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-agent-chat-delayed"
});
const startedAt = Date.now();
const response = await fetch(`http://127.0.0.1:${port}/v1/agent/chat`, {
method: "POST",
headers: {
"content-type": "application/json",
cookie: manualSession.cookie,
"x-trace-id": "trc_server-test-agent-chat-delayed"
},
body: JSON.stringify({
conversationId: manualSession.conversationId,
sessionId: manualSession.sessionId,
message: "请用一句话说明当前 HWLAB 工作台可以做什么,稍后回答"
})
});
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(Date.now() - startedAt < 4500, true);
assert.equal(payload.status, "failed");
assert.equal(payload.conversationId, "cnv_server-test-agent-chat-delayed");
assert.equal(payload.traceId, "trc_server-test-agent-chat-delayed");
assert.equal(payload.provider, "codex-stdio");
assert.equal(payload.capabilityLevel, "blocked");
assert.equal(providerCalled, false);
assert.equal(Object.hasOwn(payload, "reply"), false);
} finally {
await new Promise((resolve, reject) => {
server.close((error) => (error ? reject(error) : resolve()));
});
}
});
test("cloud api /v1/agent/chat does not call OpenAI provider 502/503 fallback", async () => {
for (const status of [502, 503]) {
const providerServer = createHttpServer((request, response) => {
request.resume();
response.writeHead(status, { "content-type": "application/json" });
response.end(JSON.stringify({
error: {
message: `upstream ${status}`
}
}));
});
await new Promise((resolve) => providerServer.listen(0, "127.0.0.1", resolve));
const providerPort = providerServer.address().port;
const server = createCloudApiServer({
env: {
OPENAI_API_KEY: "test-openai-key-material",
HWLAB_CODE_AGENT_PROVIDER: "openai",
HWLAB_CODE_AGENT_ALLOW_TEXT_FALLBACK: "true",
HWLAB_CODE_AGENT_MODEL: "gpt-test",
HWLAB_CODE_AGENT_OPENAI_BASE_URL: `http://127.0.0.1:${providerPort}/v1/responses`
}
});
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-agent-chat-provider-${status}`
});
const response = await fetch(`http://127.0.0.1:${port}/v1/agent/chat`, {
method: "POST",
headers: {
"content-type": "application/json",
cookie: manualSession.cookie,
"x-trace-id": `trc_server-test-agent-chat-provider-${status}`
},
body: JSON.stringify({
conversationId: manualSession.conversationId,
sessionId: manualSession.sessionId,
message: `provider ${status}`
})
});
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.status, "failed");
assert.equal(payload.traceId, `trc_server-test-agent-chat-provider-${status}`);
assert.match(payload.error.code, /^(codex_cli_binary_missing|codex_cli_native_dependency_missing|codex_stdio_supervisor_disabled|stdio_protocol_not_wired)$/u);
assert.match(payload.error.layer, /^(runner|api)$/u);
assert.equal(typeof payload.error.retryable, "boolean");
assert.equal(payload.error.blocker.traceId, `trc_server-test-agent-chat-provider-${status}`);
assert.equal(typeof payload.error.blocker.retryable, "boolean");
assert.equal(payload.error.blocker.capabilityLevel, "blocked");
assert.equal(payload.provider, "codex-stdio");
assert.equal(Object.hasOwn(payload, "reply"), false);
} finally {
await new Promise((resolve, reject) => {
server.close((error) => (error ? reject(error) : resolve()));
});
await new Promise((resolve, reject) => {
providerServer.close((error) => (error ? reject(error) : resolve()));
});
}
}
});
test("cloud api /v1/agent/chat does not call empty provider text fallback", async () => {
let providerCalled = false;
const server = createCloudApiServer({
callCodeAgentProvider: async () => {
providerCalled = true;
throw new Error("empty provider fallback must not be used");
}
});
await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));
try {
const { port } = server.address();
const manualSession = await createManualAgentSession(port, {
conversationId: "cnv_empty-provider-text"
});
const response = await fetch(`http://127.0.0.1:${port}/v1/agent/chat`, {
method: "POST",
headers: {
"content-type": "application/json",
cookie: manualSession.cookie
},
body: JSON.stringify({
conversationId: manualSession.conversationId,
sessionId: manualSession.sessionId,
message: "你好"
})
});
assert.equal(response.status, 200);
const payload = await response.json();
assert.equal(payload.conversationId, "cnv_empty-provider-text");
assert.equal(payload.status, "failed");
assert.match(payload.error.code, /^(codex_cli_binary_missing|stdio_protocol_not_wired)$/u);
assert.equal(payload.provider, "codex-stdio");
assert.equal(Object.hasOwn(payload, "reply"), false);
assert.equal(providerCalled, false);
} finally {
await new Promise((resolve, reject) => {
server.close((error) => (error ? reject(error) : resolve()));
});
}
});