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); 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((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 completed,Keil 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())); }); } });