Files
pikasTech-HWLAB/tools/hwlab-cli/client.test.ts
T
Codex d39e322bf0 fix(web/cli): do not truncate the terminal final-response body in trace rows
- HWLAB v0.2 Final Response was being truncated to 5000 characters in
  traceAssistantSummaryRow and then again to 1200 characters in the CLI
  compactTraceRenderRow, even when the response was the assistant’s
  terminal (last) message. The same Final Response is what
  /v1/agent/chat/result returns as reply.content, so the trace view
  should show the full text and stay consistent with the CLI
  assistantText path.
- Mark each rendered row with a terminal flag (true only for the
  terminal Final Response) and lift the body cap to
  Number.POSITIVE_INFINITY for terminal rows. The CLI Web-render path
  now passes the full body through when row.terminal is true and keeps
  the 1200 char preview for non-terminal streaming rows.
- Add unit tests in web/hwlab-cloud-web/app-trace.test.ts and
  tools/hwlab-cli/client.test.ts to lock the behavior in for the
  terminal/no-truncate path and the streaming/2200 cap path.

Refs: pikasTech/HWLAB#722
2026-06-03 13:26:19 +08:00

1705 lines
76 KiB
TypeScript

import assert from "node:assert/strict";
import { mkdtemp, readFile, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { test } from "bun:test";
import { runHwlabCli } from "../src/hwlab-cli-lib.ts";
test("hwlab-cli client login uses Cloud Web auth and stores only cookie session", async () => {
const cwd = await mkdtemp(path.join(os.tmpdir(), "hwlab-cli-client-"));
const calls: any[] = [];
const result = await runHwlabCli([
"client",
"auth",
"login",
"--base-url",
"http://web.test",
"--username",
"admin",
"--password-env",
"HWLAB_PASSWORD"
], {
cwd,
env: { HWLAB_PASSWORD: "secret-password" },
fetchImpl: async (url, init) => {
calls.push({ url: String(url), init, body: JSON.parse(String(init?.body ?? "{}")) });
return new Response(JSON.stringify({ authenticated: true, user: { id: "usr_admin", username: "admin", role: "admin" }, expiresAt: "2026-05-30T12:00:00.000Z" }), {
status: 200,
headers: { "content-type": "application/json", "set-cookie": "hwlab_session=session-a; Path=/; HttpOnly" }
});
},
now: () => "2026-05-30T00:00:00.000Z"
});
assert.equal(result.exitCode, 0);
assert.equal(result.payload.action, "client.auth.login");
assert.equal(result.payload.cookieStored, true);
assert.equal(calls[0].url, "http://web.test/auth/login");
assert.deepEqual(calls[0].body, { username: "admin", password: "secret-password" });
assert.equal(JSON.stringify(result.payload).includes("secret-password"), false);
const session = JSON.parse(await readFile(path.join(cwd, ".state/hwlab-cli/session.json"), "utf8"));
assert.equal(session.baseUrl, "http://web.test");
assert.equal(session.cookie, "hwlab_session=session-a");
assert.equal(JSON.stringify(session).includes("secret-password"), false);
});
test("hwlab-cli client device-pods uses saved cookie and same Web API paths", async () => {
const cwd = await mkdtemp(path.join(os.tmpdir(), "hwlab-cli-client-"));
await runHwlabCli(["client", "auth", "login", "--base-url", "http://web.test", "--username", "admin", "--password", "pw"], {
cwd,
fetchImpl: async () => new Response(JSON.stringify({ authenticated: true, user: { username: "admin" } }), { status: 200, headers: { "set-cookie": "hwlab_session=session-a; Path=/" } })
});
const seen: any[] = [];
const result = await runHwlabCli(["client", "device-pods", "status", "device-pod-71-freq", "--base-url", "http://web.test"], {
cwd,
fetchImpl: async (url, init) => {
seen.push({ url: String(url), headers: init?.headers });
return new Response(JSON.stringify({ ok: true, devicePodId: "device-pod-71-freq", contractVersion: "device-pod-authority-v1", profileHash: "sha256:abc" }), { status: 200 });
}
});
assert.equal(result.exitCode, 0);
assert.equal(seen[0].url, "http://web.test/v1/device-pods/device-pod-71-freq/status");
assert.equal(seen[0].headers.cookie, "hwlab_session=session-a");
assert.equal(result.payload.route.path, "/v1/device-pods/device-pod-71-freq/status");
assert.equal(result.payload.body.devicePodId, "device-pod-71-freq");
assert.equal(result.payload.body.fullBodyAvailable, true);
});
test("hwlab-cli client request reports auth username from the selected session", async () => {
const cwd = await mkdtemp(path.join(os.tmpdir(), "hwlab-cli-client-profile-user-"));
const calls: any[] = [];
await runHwlabCli(["client", "auth", "login", "--base-url", "http://web.test", "--profile", "alice", "--username", "alice", "--password", "pw"], {
cwd,
fetchImpl: async () => new Response(JSON.stringify({ authenticated: true, user: { id: "usr_alice", username: "alice", role: "user" } }), { status: 200, headers: { "set-cookie": "hwlab_session=session-alice; Path=/" } })
});
const result = await runHwlabCli(["client", "request", "GET", "/v1/access/status", "--base-url", "http://web.test", "--profile", "alice"], {
cwd,
fetchImpl: async (url, init) => {
calls.push({ url: String(url), init });
return new Response(JSON.stringify({ ok: true, authenticated: true, actor: { username: "alice" } }), { status: 200 });
}
});
assert.equal(result.exitCode, 0);
assert.equal(calls[0].init.headers.cookie, "hwlab_session=session-alice");
assert.equal(result.payload.auth.username, "alice");
assert.equal(result.payload.auth.localSession.user.username, "alice");
});
test("hwlab-cli client auth status exposes local session state and next commands", async () => {
const cwd = await mkdtemp(path.join(os.tmpdir(), "hwlab-cli-client-auth-status-"));
const missing = await runHwlabCli(["client", "auth", "status", "--base-url", "http://web.test"], { cwd });
assert.equal(missing.exitCode, 0);
assert.equal(missing.payload.action, "client.auth.status");
assert.equal(missing.payload.localSession.exists, false);
assert.equal(missing.payload.localSession.usable, false);
assert.equal(missing.payload.localSession.ignoredReason, "state_file_missing");
assert.match(missing.payload.nextCommands[1], /client auth login/u);
await runHwlabCli(["client", "auth", "login", "--base-url", "http://old-web.test", "--username", "admin", "--password", "pw"], {
cwd,
fetchImpl: async () => new Response(JSON.stringify({ authenticated: true, user: { username: "admin" } }), { status: 200, headers: { "set-cookie": "hwlab_session=session-old; Path=/" } })
});
const mismatch = await runHwlabCli(["client", "auth", "status", "--base-url", "http://web.test"], { cwd });
assert.equal(mismatch.exitCode, 0);
assert.equal(mismatch.payload.localSession.exists, true);
assert.equal(mismatch.payload.localSession.usable, false);
assert.equal(mismatch.payload.localSession.ignoredReason, "base_url_mismatch");
assert.equal(JSON.stringify(mismatch.payload).includes("session-old"), false);
assert.match(mismatch.payload.nextCommands[1], /^bun tools\/hwlab-cli\/bin\/hwlab-cli\.ts client auth login/u);
assert.equal(JSON.stringify(mismatch.payload.nextCommands).includes("run-bun.mjs"), false);
});
test("hwlab-cli reuses public v02 session when runtime namespace resolves host endpoint", async () => {
const cwd = await mkdtemp(path.join(os.tmpdir(), "hwlab-cli-client-runtime-scope-"));
await runHwlabCli(["client", "auth", "login", "--base-url", "http://74.48.78.17:19666", "--username", "admin", "--password", "pw"], {
cwd,
fetchImpl: async () => new Response(JSON.stringify({ authenticated: true, user: { username: "admin" } }), { status: 200, headers: { "set-cookie": "hwlab_session=session-v02; Path=/" } })
});
const status = await runHwlabCli(["client", "auth", "status"], {
cwd,
env: {
HWLAB_RUNTIME_NAMESPACE: "hwlab-v02",
HWLAB_RUNTIME_LANE: "v02",
HWLAB_RUNTIME_ENDPOINT_LOCKED: "1",
HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME: "1"
}
});
assert.equal(status.exitCode, 0);
assert.equal(status.payload.baseUrl, "http://74.48.78.17:19666");
assert.equal(status.payload.localSession.usable, true);
assert.equal(status.payload.localSession.ignoredReason, null);
assert.equal(status.payload.localSession.baseUrlMatch, "exact");
assert.equal(status.payload.localSession.baseUrl, "http://74.48.78.17:19666");
});
test("hwlab-cli resolves runtime namespace to service DNS inside kubernetes", async () => {
const result = await runHwlabCli(["client", "auth", "status"], {
env: {
HWLAB_RUNTIME_NAMESPACE: "hwlab-v02",
HWLAB_RUNTIME_LANE: "v02",
KUBERNETES_SERVICE_HOST: "10.43.0.1",
POD_NAMESPACE: "hwlab-v02"
}
});
assert.equal(result.exitCode, 0);
assert.equal(result.payload.baseUrl, "http://hwlab-cloud-web.hwlab-v02.svc.cluster.local:8080");
assert.equal(result.payload.runtimeEndpoint.source, "runtime-namespace");
});
test("hwlab-cli client group help is visible and does not issue HTTP requests", async () => {
let fetchCount = 0;
const fetchImpl = async () => {
fetchCount += 1;
return new Response(JSON.stringify({ ok: false }), { status: 500 });
};
const auth = await runHwlabCli(["client", "auth", "--help", "--base-url", "http://web.test"], { fetchImpl });
assert.equal(auth.exitCode, 0);
assert.equal(auth.payload.action, "client.auth.help");
assert.equal(auth.payload.serviceRuntime, false);
assert.ok(auth.payload.commands.some((command: string) => command.startsWith("login ")));
const agent = await runHwlabCli(["client", "agent", "--help", "--base-url", "http://web.test"], { fetchImpl });
assert.equal(agent.exitCode, 0);
assert.equal(agent.payload.action, "client.agent.help");
assert.equal(agent.payload.serviceRuntime, false);
assert.ok(agent.payload.commands.some((command: string) => command.startsWith("send ")));
const session = await runHwlabCli(["client", "session", "--help", "--base-url", "http://web.test"], { fetchImpl });
assert.equal(session.exitCode, 0);
assert.equal(session.payload.action, "client.session.help");
assert.equal(session.payload.webEquivalent, true);
assert.ok(session.payload.commands.some((command: string) => command.startsWith("create ")));
assert.equal(fetchCount, 0);
});
test("hwlab-cli client session management uses Cloud Web workspace and conversation paths", async () => {
const cwd = await mkdtemp(path.join(os.tmpdir(), "hwlab-cli-session-management-"));
const calls: any[] = [];
const fetchImpl = async (url: string | URL, init: any = {}) => {
const body = init?.body ? JSON.parse(String(init.body)) : null;
calls.push({ url: String(url), method: init?.method ?? "GET", body });
if (String(url).endsWith("/v1/workbench/workspace?projectId=prj_device_pod_workbench")) {
return new Response(JSON.stringify({ ok: true, workspace: { workspaceId: "wsp_cli_session", revision: 4, selectedConversationId: "cnv_existing", selectedAgentSessionId: "ses_existing", workspace: {} } }), { status: 200 });
}
if (String(url).endsWith("/v1/workbench/workspace/wsp_cli_session/select-conversation")) {
const conversationId = body.conversationId || "cnv_created";
const sessionId = body.sessionId || "ses_created";
return new Response(JSON.stringify({
ok: true,
workspace: {
workspaceId: "wsp_cli_session",
revision: 5,
selectedConversationId: conversationId,
selectedAgentSessionId: sessionId,
selectedConversation: { conversationId, sessionId, threadId: body.threadId || "thread-cli", status: "active", messages: [] },
workspace: {}
}
}), { status: 200 });
}
if (String(url).endsWith("/v1/agent/conversations/cnv_existing")) {
return new Response(JSON.stringify({ ok: true, status: "deleted", archivedCount: 1, conversationId: "cnv_existing", workspace: { workspaceId: "wsp_cli_session", revision: 6, selectedConversationId: null, selectedAgentSessionId: null, workspace: {} } }), { status: 200 });
}
return new Response(JSON.stringify({ ok: false, error: { code: "unexpected_test_route" } }), { status: 404 });
};
const created = await runHwlabCli(["client", "session", "create", "--base-url", "http://web.test", "--cookie", "hwlab_session=session-a", "--conversation-id", "cnv_created", "--session-id", "ses_created", "--thread-id", "thread-created"], { cwd, fetchImpl });
const switched = await runHwlabCli(["client", "session", "switch", "cnv_existing", "--base-url", "http://web.test", "--cookie", "hwlab_session=session-a", "--session-id", "ses_existing", "--thread-id", "thread-existing"], { cwd, fetchImpl });
const deleted = await runHwlabCli(["client", "session", "delete", "cnv_existing", "--confirm", "--base-url", "http://web.test", "--cookie", "hwlab_session=session-a"], { cwd, fetchImpl });
assert.equal(created.exitCode, 0);
assert.equal(switched.exitCode, 0);
assert.equal(deleted.exitCode, 0);
assert.equal(calls[1].url, "http://web.test/v1/workbench/workspace/wsp_cli_session/select-conversation");
assert.equal(calls[1].method, "POST");
assert.equal(calls[1].body.create, true);
assert.equal(calls[1].body.updatedByClient, "hwlab-cli");
assert.equal(calls[3].url, "http://web.test/v1/workbench/workspace/wsp_cli_session/select-conversation");
assert.equal(calls[3].body.conversationId, "cnv_existing");
assert.equal(calls[5].url, "http://web.test/v1/agent/conversations/cnv_existing");
assert.equal(calls[5].method, "DELETE");
assert.equal(calls[5].body.workspaceId, "wsp_cli_session");
assert.equal(created.payload.route.path, "/v1/workbench/workspace/wsp_cli_session/select-conversation");
assert.equal(switched.payload.current.conversationId, "cnv_existing");
assert.equal(deleted.payload.body.status, "deleted");
});
test("hwlab-cli rejects manual endpoint override in locked assembled runtime", async () => {
const result = await runHwlabCli(["client", "auth", "status", "--base-url", "http://74.48.78.17:17666"], {
env: {
HWLAB_RUNTIME_WEB_URL: "http://cloud-v02.test",
HWLAB_RUNTIME_NAMESPACE: "hwlab-v02",
HWLAB_RUNTIME_LANE: "v02",
HWLAB_RUNTIME_ENDPOINT_LOCKED: "1",
HWLAB_CODE_AGENT_ASSEMBLED_RUNTIME: "1"
}
});
assert.equal(result.exitCode, 1);
assert.equal(result.payload.error.code, "runtime_endpoint_manual_url_forbidden");
assert.equal(result.payload.error.details.kind, "web");
});
test("hwlab-cli client device-pods events compacts event stream by default", async () => {
const result = await runHwlabCli(["client", "device-pods", "events", "pod-1", "--base-url", "http://web.test", "--cookie", "hwlab_session=session-a"], {
fetchImpl: async () => new Response(JSON.stringify({
ok: true,
status: "ok",
events: [{ eventId: "evt-1", level: "info", scope: "job", intent: "debug.chip-id", summary: "running", refs: { traceId: "trc_1" } }],
lines: ["line-1"]
}), { status: 200 })
});
assert.equal(result.exitCode, 0);
assert.equal(result.payload.body.eventsCount, 1);
assert.equal(result.payload.body.events[0].eventId, "evt-1");
assert.equal(result.payload.body.lines[0], "line-1");
assert.equal(result.payload.body.fullBodyAvailable, true);
});
test("hwlab-cli client runtime routes reports UniDesk pod passthrough route", async () => {
const result = await runHwlabCli(["client", "runtime", "routes", "--base-url", "http://web.test", "--service-id", "hwlab-cloud-api"], {
fetchImpl: async () => new Response(JSON.stringify({
ok: true,
status: "ok",
services: [{
serviceId: "hwlab-cloud-api",
runtime: { pod: { name: "hwlab-cloud-api-abc", namespace: "hwlab-v02", container: "hwlab-cloud-api" } }
}]
}), { status: 200 })
});
assert.equal(result.exitCode, 0);
assert.equal(result.payload.items[0].unideskRoute, "G14:k3s:hwlab-v02:pod:hwlab-cloud-api-abc:hwlab-cloud-api");
assert.match(result.payload.items[0].useWith, /bun scripts\/cli\.ts ssh/u);
assert.equal(result.payload.discovery.ok, true);
assert.equal(result.payload.discovery.source, "cloud-web:/v1/live-builds");
assert.equal(result.payload.discovery.readyRouteCount, 1);
});
test("hwlab-cli client gateway sessions uses direct Cloud API short connection", async () => {
const calls: any[] = [];
const result = await runHwlabCli([
"client",
"gateway",
"sessions",
"--api-base-url",
"http://api.test",
"--no-auth",
"--full"
], {
fetchImpl: async (url, init) => {
calls.push({ url: String(url), init });
return new Response(JSON.stringify({ ok: true, sessions: [{ gatewaySessionId: "gws_D601_F103", inflightCount: 0 }] }), { status: 200 });
}
});
assert.equal(result.exitCode, 0);
assert.equal(calls[0].url, "http://api.test/v1/gateway/sessions");
assert.equal(calls[0].init.headers.cookie, undefined);
assert.equal(result.payload.action, "client.gateway.sessions");
assert.equal(result.payload.baseUrl, "http://api.test");
assert.equal(result.payload.body.sessions[0].gatewaySessionId, "gws_D601_F103");
});
test("hwlab-cli client gateway pressure reports byte/truncation visibility", async () => {
const calls: any[] = [];
const result = await runHwlabCli([
"client",
"gateway",
"pressure",
"--api-base-url",
"http://api.test",
"--gateway-session-id",
"gws_test",
"--large-bytes",
"8192",
"--parallel",
"2",
"--request-timeout-ms",
"10000",
"--timeout-scenario-ms",
"500"
], {
fetchImpl: async (url, init) => {
const callIndex = calls.length;
const body = JSON.parse(String(init?.body ?? "{}"));
calls.push({ url: String(url), init, body });
const dispatch: any = { dispatchStatus: "succeeded", shellExecuted: true, exitCode: 0, stdout: "", stderr: "" };
if (callIndex === 0) dispatch.stdout = "hwlab-gateway-pressure-small-ok\r\n";
else if (callIndex === 1 || callIndex === 2) Object.assign(dispatch, { stdout: "O".repeat(65536), stdoutTruncated: true });
else if (callIndex === 3) Object.assign(dispatch, { stderr: "E".repeat(65536), stderrTruncated: true });
else if (callIndex === 4) Object.assign(dispatch, { dispatchStatus: "timed_out", timedOut: true, exitCode: null });
else dispatch.stdout = `parallel-${callIndex - 4}-ok\r\n`;
return new Response(JSON.stringify({ ok: true, result: { status: "succeeded", dispatch } }), { status: 200 });
}
});
assert.equal(result.exitCode, 0);
assert.equal(result.payload.action, "client.gateway.pressure");
assert.equal(result.payload.status, "succeeded");
assert.equal(result.payload.scenarioCount, 7);
assert.equal(result.payload.failedCount, 0);
assert.equal(calls[0].url, "http://api.test/v1/rpc/hardware.invoke.shell");
assert.equal(calls[0].init.headers.cookie, undefined);
assert.equal(calls[0].body.gatewaySessionId, "gws_test");
assert.match(calls[1].body.input.command, /-EncodedCommand/u);
assert.equal(result.payload.results.find((item: any) => item.name === "large-stdout").stdoutTruncated, true);
assert.equal(result.payload.results.find((item: any) => item.name === "stderr-flood").stderrTruncated, true);
assert.equal(result.payload.results.find((item: any) => item.name === "timeout-kill").reason, "timeout_structured");
assert.match(result.payload.results.find((item: any) => item.name === "large-stdout").stdoutSha256, /^[a-f0-9]{64}$/u);
});
test("hwlab-cli client agent send submits async and returns trace without waiting by default", async () => {
const calls: any[] = [];
const result = await runHwlabCli([
"client",
"agent",
"send",
"--base-url",
"http://web.test",
"--cookie",
"hwlab_session=session-a",
"--message",
"hello",
"--trace-id",
"trc_test",
"--conversation-id",
"cnv_test"
], {
fetchImpl: async (url, init) => {
calls.push({ url: String(url), init, body: init?.body ? JSON.parse(String(init.body)) : null });
if (String(url).endsWith("/v1/workbench/workspace?projectId=prj_device_pod_workbench")) {
return new Response(JSON.stringify({ ok: true, workspace: { workspaceId: "wsp_test", revision: 3, selectedConversationId: "cnv_test", selectedAgentSessionId: "ses_test", selectedConversation: { conversationId: "cnv_test", sessionId: "ses_test", threadId: "thread-test" }, workspace: {} } }), { status: 200 });
}
if (String(url).endsWith("/v1/agent/chat")) {
return new Response(JSON.stringify({ accepted: true, status: "running", traceId: "trc_test", resultUrl: "/v1/agent/chat/result/trc_test" }), { status: 202 });
}
return new Response(JSON.stringify({ status: "completed", traceId: "trc_test", conversationId: "cnv_test", reply: { role: "assistant", content: "hi" } }), { status: 200 });
},
sleep: async () => {}
});
assert.equal(result.exitCode, 0);
assert.equal(calls[0].url, "http://web.test/v1/workbench/workspace?projectId=prj_device_pod_workbench");
assert.equal(calls[1].url, "http://web.test/v1/agent/chat");
assert.equal(calls[1].init.headers.prefer, "respond-async");
assert.equal(calls[1].body.shortConnection, true);
assert.equal(calls[1].body.workspaceId, "wsp_test");
assert.equal(calls[1].body.expectedWorkspaceRevision, 3);
assert.equal(calls[1].body.sessionId, "ses_test");
assert.equal(calls[1].body.threadId, "thread-test");
assert.equal(Object.hasOwn(calls[1].body, "conversationContext"), false);
assert.equal(Object.hasOwn(calls[1].body, "messages"), false);
assert.equal(calls.length, 2);
assert.equal(result.payload.traceId, "trc_test");
assert.equal(result.payload.continuation.threadId, "thread-test");
assert.equal(result.payload.waited, false);
assert.equal(result.payload.waitPolicy.defaultWait, false);
assert.equal(result.payload.waitPolicy.webEquivalent, true);
assert.ok(result.payload.waitPolicy.nextCommands.some((command: string) => command.includes("agent result trc_test")));
});
test("hwlab-cli client agent send requires an explicit manual session", async () => {
const calls: any[] = [];
const result = await runHwlabCli([
"client",
"agent",
"send",
"--base-url",
"http://web.test",
"--cookie",
"hwlab_session=session-a",
"--message",
"hello without session",
"--trace-id",
"trc_no_session"
], {
fetchImpl: async (url, init) => {
calls.push({ url: String(url), init, body: init?.body ? JSON.parse(String(init.body)) : null });
if (String(url).endsWith("/v1/workbench/workspace?projectId=prj_device_pod_workbench")) {
return new Response(JSON.stringify({ ok: true, workspace: { workspaceId: "wsp_no_session", revision: 1, workspace: {} } }), { status: 200 });
}
return new Response(JSON.stringify({ accepted: true }), { status: 202 });
}
});
assert.equal(result.exitCode, 1);
assert.equal(result.payload.error.code, "session_required");
assert.equal(calls.length, 1);
assert.equal(calls[0].url, "http://web.test/v1/workbench/workspace?projectId=prj_device_pod_workbench");
});
test("hwlab-cli client agent session create stores selected workspace", async () => {
const cwd = await mkdtemp(path.join(os.tmpdir(), "hwlab-cli-agent-session-create-"));
const calls: any[] = [];
const result = await runHwlabCli([
"client",
"agent",
"session",
"create",
"--base-url",
"http://web.test",
"--cookie",
"hwlab_session=session-a",
"--provider-profile",
"minimax-m3"
], {
cwd,
fetchImpl: async (url, init) => {
calls.push({ url: String(url), init, body: init?.body ? JSON.parse(String(init.body)) : null });
if (String(url).endsWith("/v1/workbench/workspace?projectId=prj_device_pod_workbench")) {
return new Response(JSON.stringify({ ok: true, workspace: { workspaceId: "wsp_session_create", revision: 2, workspace: {} } }), { status: 200 });
}
if (String(url).endsWith("/v1/agent/sessions")) {
return new Response(JSON.stringify({
ok: true,
status: "created",
session: { sessionId: "ses_manual_create", conversationId: "cnv_manual_create", status: "idle", usable: true, providerProfile: "minimax-m3" },
workspace: { workspaceId: "wsp_session_create", revision: 3, selectedConversationId: "cnv_manual_create", selectedAgentSessionId: "ses_manual_create", workspace: { sessionStatus: "idle", providerProfile: "minimax-m3" } }
}), { status: 201 });
}
return new Response(JSON.stringify({ ok: false, error: { code: "unexpected_test_route" } }), { status: 404 });
}
});
assert.equal(result.exitCode, 0);
assert.equal(calls[1].url, "http://web.test/v1/agent/sessions");
assert.equal(calls[1].body.providerProfile, "minimax-m3");
assert.equal(calls[1].body.workspaceId, "wsp_session_create");
assert.equal(result.payload.sessionId, "ses_manual_create");
assert.equal(result.payload.conversationId, "cnv_manual_create");
assert.equal(result.payload.providerProfile, "minimax-m3");
assert.equal(result.payload.sessionStatus, "idle");
assert.equal(result.payload.session.sessionId, "ses_manual_create");
assert.equal(result.payload.workspace.selectedAgentSessionId, "ses_manual_create");
const session = JSON.parse(await readFile(path.join(cwd, ".state/hwlab-cli/session.json"), "utf8"));
assert.equal(session.workspace.selectedConversationId, "cnv_manual_create");
assert.equal(session.workspace.selectedAgentSessionId, "ses_manual_create");
});
test("hwlab-cli client agent send does not inherit workspace session for explicit new conversation", async () => {
const calls: any[] = [];
const result = await runHwlabCli([
"client",
"agent",
"send",
"--base-url",
"http://web.test",
"--cookie",
"hwlab_session=session-a",
"--message",
"new conversation needs session",
"--trace-id",
"trc_new_conversation",
"--conversation-id",
"cnv_new_conversation"
], {
fetchImpl: async (url, init) => {
calls.push({ url: String(url), init, body: init?.body ? JSON.parse(String(init.body)) : null });
if (String(url).endsWith("/v1/workbench/workspace?projectId=prj_device_pod_workbench")) {
return new Response(JSON.stringify({
ok: true,
workspace: {
workspaceId: "wsp_old_session",
revision: 8,
selectedConversationId: "cnv_old_conversation",
selectedAgentSessionId: "ses_old_session",
threadId: "thread-old",
workspace: { sessionStatus: "idle" }
}
}), { status: 200 });
}
return new Response(JSON.stringify({ accepted: true }), { status: 202 });
}
});
assert.equal(result.exitCode, 1);
assert.equal(result.payload.error.code, "session_required");
assert.equal(result.payload.error.details.conversationId, "cnv_new_conversation");
assert.equal(calls.length, 1);
assert.equal(calls[0].url, "http://web.test/v1/workbench/workspace?projectId=prj_device_pod_workbench");
});
test("hwlab-cli client agent send prefers selected conversation thread over stale workspace thread", async () => {
const calls: any[] = [];
const result = await runHwlabCli([
"client",
"agent",
"send",
"--base-url",
"http://web.test",
"--cookie",
"hwlab_session=session-a",
"--message",
"continue after failed turn",
"--trace-id",
"trc_thread_current",
"--conversation-id",
"cnv_thread_current"
], {
fetchImpl: async (url, init) => {
calls.push({ url: String(url), init, body: init?.body ? JSON.parse(String(init.body)) : null });
if (String(url).endsWith("/v1/workbench/workspace?projectId=prj_device_pod_workbench")) {
return new Response(JSON.stringify({
ok: true,
workspace: {
workspaceId: "wsp_thread_current",
revision: 9,
selectedConversationId: "cnv_thread_current",
selectedAgentSessionId: "ses_thread_current",
selectedConversation: {
conversationId: "cnv_thread_current",
sessionId: "ses_thread_current",
threadId: "thread-current"
},
workspace: {
threadId: "thread-stale"
}
}
}), { status: 200 });
}
if (String(url).endsWith("/v1/agent/chat")) {
return new Response(JSON.stringify({ accepted: true, status: "running", traceId: "trc_thread_current", resultUrl: "/v1/agent/chat/result/trc_thread_current" }), { status: 202 });
}
return new Response(JSON.stringify({ status: "completed", traceId: "trc_thread_current", conversationId: "cnv_thread_current", reply: { role: "assistant", content: "ok" } }), { status: 200 });
},
sleep: async () => {}
});
assert.equal(result.exitCode, 0);
assert.equal(calls[1].url, "http://web.test/v1/agent/chat");
assert.equal(calls[1].body.conversationId, "cnv_thread_current");
assert.equal(calls[1].body.sessionId, "ses_thread_current");
assert.equal(calls[1].body.threadId, "thread-current");
assert.equal(result.payload.continuation.threadId, "thread-current");
assert.equal(result.payload.workspace.threadId, "thread-current");
});
test("hwlab-cli client agent send waits only when --wait is explicit", async () => {
const calls: any[] = [];
const result = await runHwlabCli([
"client",
"agent",
"send",
"--base-url",
"http://web.test",
"--cookie",
"hwlab_session=session-a",
"--message",
"hello",
"--trace-id",
"trc_wait",
"--conversation-id",
"cnv_wait",
"--wait",
"--poll-interval-ms",
"1",
"--timeout-ms",
"1000"
], {
fetchImpl: async (url, init) => {
calls.push({ url: String(url), init, body: init?.body ? JSON.parse(String(init.body)) : null });
if (String(url).endsWith("/v1/workbench/workspace?projectId=prj_device_pod_workbench")) {
return new Response(JSON.stringify({ ok: true, workspace: { workspaceId: "wsp_wait", revision: 3, selectedConversationId: "cnv_wait", selectedAgentSessionId: "ses_wait", selectedConversation: { conversationId: "cnv_wait", sessionId: "ses_wait", threadId: "thread-wait" }, workspace: {} } }), { status: 200 });
}
if (String(url).endsWith("/v1/agent/chat")) {
return new Response(JSON.stringify({ accepted: true, status: "running", traceId: "trc_wait", resultUrl: "/v1/agent/chat/result/trc_wait" }), { status: 202 });
}
if (String(url).endsWith("/v1/workbench/workspace/wsp_wait")) {
return new Response(JSON.stringify({ ok: true, workspace: { workspaceId: "wsp_wait", revision: 5, selectedConversationId: "cnv_wait", selectedAgentSessionId: "ses_wait", selectedConversation: { conversationId: "cnv_wait", sessionId: "ses_wait", threadId: "thread-wait" }, workspace: { sessionStatus: "completed" } } }), { status: 200 });
}
return new Response(JSON.stringify({ status: "completed", traceId: "trc_wait", conversationId: "cnv_wait", reply: { role: "assistant", content: "hi" } }), { status: 200 });
},
sleep: async () => {}
});
assert.equal(result.exitCode, 0);
assert.equal(calls[1].body.threadId, "thread-wait");
assert.equal(calls[2].url, "http://web.test/v1/agent/chat/result/trc_wait");
assert.equal(calls[3].url, "http://web.test/v1/workbench/workspace/wsp_wait");
assert.equal(Object.hasOwn(calls[3].body, "activeTraceId"), true);
assert.equal(calls[3].body.activeTraceId, null);
assert.equal(calls[3].body.threadId, "thread-wait");
assert.equal(result.payload.traceId, "trc_wait");
assert.equal(result.payload.continuation.threadId, "thread-wait");
assert.equal(result.payload.result.body.status, "completed");
assert.equal(result.payload.result.body.assistantText, "hi");
assert.equal(result.payload.result.body.reply.content, "hi");
});
test("hwlab-cli client agent send saves completed workspace response for new conversation", async () => {
const cwd = await mkdtemp(path.join(os.tmpdir(), "hwlab-cli-client-agent-save-workspace-"));
const calls: any[] = [];
const result = await runHwlabCli([
"client",
"agent",
"send",
"--base-url",
"http://web.test",
"--cookie",
"hwlab_session=session-a",
"--message",
"start a fresh conversation",
"--trace-id",
"trc_new_workspace",
"--conversation-id",
"cnv_new_workspace",
"--session-id",
"ses_new_workspace",
"--wait",
"--poll-interval-ms",
"1",
"--timeout-ms",
"1000"
], {
cwd,
fetchImpl: async (url, init) => {
calls.push({ url: String(url), init, body: init?.body ? JSON.parse(String(init.body)) : null });
if (String(url).endsWith("/v1/workbench/workspace?projectId=prj_device_pod_workbench")) {
return new Response(JSON.stringify({
ok: true,
workspace: {
workspaceId: "wsp_new_workspace",
revision: 8,
selectedConversationId: "cnv_old_workspace",
selectedAgentSessionId: "ses_old_workspace",
selectedConversation: {
conversationId: "cnv_old_workspace",
sessionId: "ses_old_workspace",
threadId: "thread-old-workspace"
},
workspace: {
sessionStatus: "idle",
threadId: "thread-old-workspace"
}
}
}), { status: 200 });
}
if (String(url).endsWith("/v1/agent/chat")) {
return new Response(JSON.stringify({ accepted: true, status: "running", traceId: "trc_new_workspace", resultUrl: "/v1/agent/chat/result/trc_new_workspace" }), { status: 202 });
}
if (String(url).endsWith("/v1/agent/chat/result/trc_new_workspace")) {
return new Response(JSON.stringify({ status: "completed", traceId: "trc_new_workspace", conversationId: "cnv_new_workspace", sessionId: "ses_new_workspace", reply: { role: "assistant", content: "new workspace ready" } }), { status: 200 });
}
if (String(url).endsWith("/v1/workbench/workspace/wsp_new_workspace")) {
return new Response(JSON.stringify({
ok: true,
workspace: {
workspaceId: "wsp_new_workspace",
revision: 9,
selectedConversationId: "cnv_new_workspace",
selectedAgentSessionId: "ses_new_workspace",
selectedConversation: {
conversationId: "cnv_new_workspace",
sessionId: "ses_new_workspace",
threadId: "thread-new-workspace"
},
workspace: {
sessionStatus: "idle",
threadId: "thread-new-workspace"
}
}
}), { status: 200 });
}
return new Response(JSON.stringify({ ok: false, error: { code: "unexpected_test_route" } }), { status: 404 });
},
sleep: async () => {}
});
assert.equal(result.exitCode, 0);
assert.equal(calls[1].url, "http://web.test/v1/agent/chat");
assert.equal(calls[1].body.conversationId, "cnv_new_workspace");
assert.equal(calls[1].body.sessionId, "ses_new_workspace");
assert.equal(Object.hasOwn(calls[1].body, "threadId"), false);
assert.equal(calls[3].url, "http://web.test/v1/workbench/workspace/wsp_new_workspace");
assert.equal(result.payload.workspace.selectedConversationId, "cnv_new_workspace");
assert.equal(result.payload.workspace.selectedAgentSessionId, "ses_new_workspace");
assert.equal(result.payload.workspace.threadId, "thread-new-workspace");
assert.equal(result.payload.workspace.selectedConversation.conversationId, "cnv_new_workspace");
const session = JSON.parse(await readFile(path.join(cwd, ".state/hwlab-cli/session.json"), "utf8"));
assert.equal(session.workspace.selectedConversationId, "cnv_new_workspace");
assert.equal(session.workspace.selectedAgentSessionId, "ses_new_workspace");
assert.equal(session.workspace.threadId, "thread-new-workspace");
assert.equal(session.workspace.selectedConversation.conversationId, "cnv_new_workspace");
});
test("hwlab-cli client agent send restores workspace when completed patch omits workspace body", async () => {
const cwd = await mkdtemp(path.join(os.tmpdir(), "hwlab-cli-client-agent-restore-workspace-"));
const calls: any[] = [];
const result = await runHwlabCli([
"client",
"agent",
"send",
"--base-url",
"http://web.test",
"--cookie",
"hwlab_session=session-a",
"--message",
"start a fresh conversation",
"--trace-id",
"trc_restore_workspace",
"--conversation-id",
"cnv_restore_workspace",
"--session-id",
"ses_restore_workspace",
"--wait",
"--poll-interval-ms",
"1",
"--timeout-ms",
"1000"
], {
cwd,
fetchImpl: async (url, init) => {
calls.push({ url: String(url), init, body: init?.body ? JSON.parse(String(init.body)) : null });
if (String(url).endsWith("/v1/workbench/workspace?projectId=prj_device_pod_workbench")) {
const restored = calls.filter((call) => String(call.url).endsWith("/v1/workbench/workspace?projectId=prj_device_pod_workbench")).length > 1;
return new Response(JSON.stringify({
ok: true,
workspace: restored
? {
workspaceId: "wsp_restore_workspace",
revision: 12,
selectedConversationId: "cnv_restore_workspace",
selectedAgentSessionId: "ses_restore_workspace",
selectedConversation: {
conversationId: "cnv_restore_workspace",
sessionId: "ses_restore_workspace",
threadId: "thread-restore-workspace"
},
workspace: {
sessionStatus: "idle",
threadId: "thread-restore-workspace"
}
}
: {
workspaceId: "wsp_restore_workspace",
revision: 11,
selectedConversationId: "cnv_previous_workspace",
selectedAgentSessionId: "ses_previous_workspace",
selectedConversation: {
conversationId: "cnv_previous_workspace",
sessionId: "ses_previous_workspace",
threadId: "thread-previous-workspace"
},
workspace: {
sessionStatus: "idle",
threadId: "thread-previous-workspace"
}
}
}), { status: 200 });
}
if (String(url).endsWith("/v1/agent/chat")) {
return new Response(JSON.stringify({ accepted: true, status: "running", traceId: "trc_restore_workspace", resultUrl: "/v1/agent/chat/result/trc_restore_workspace" }), { status: 202 });
}
if (String(url).endsWith("/v1/agent/chat/result/trc_restore_workspace")) {
return new Response(JSON.stringify({ status: "completed", traceId: "trc_restore_workspace", conversationId: "cnv_restore_workspace", sessionId: "ses_restore_workspace", reply: { role: "assistant", content: "restore workspace ready" } }), { status: 200 });
}
if (String(url).endsWith("/v1/workbench/workspace/wsp_restore_workspace")) {
return new Response(JSON.stringify({ ok: true }), { status: 200 });
}
return new Response(JSON.stringify({ ok: false, error: { code: "unexpected_test_route" } }), { status: 404 });
},
sleep: async () => {}
});
assert.equal(result.exitCode, 0);
assert.equal(calls[1].url, "http://web.test/v1/agent/chat");
assert.equal(calls[1].body.sessionId, "ses_restore_workspace");
assert.equal(Object.hasOwn(calls[1].body, "threadId"), false);
assert.equal(calls[3].url, "http://web.test/v1/workbench/workspace/wsp_restore_workspace");
assert.equal(calls[4].url, "http://web.test/v1/workbench/workspace?projectId=prj_device_pod_workbench");
assert.equal(result.payload.workspace.selectedConversationId, "cnv_restore_workspace");
assert.equal(result.payload.workspace.selectedAgentSessionId, "ses_restore_workspace");
assert.equal(result.payload.workspace.threadId, "thread-restore-workspace");
const session = JSON.parse(await readFile(path.join(cwd, ".state/hwlab-cli/session.json"), "utf8"));
assert.equal(session.workspace.selectedConversationId, "cnv_restore_workspace");
assert.equal(session.workspace.selectedAgentSessionId, "ses_restore_workspace");
assert.equal(session.workspace.threadId, "thread-restore-workspace");
});
test("hwlab-cli client agent send output uses completed result when restore is stale", async () => {
const cwd = await mkdtemp(path.join(os.tmpdir(), "hwlab-cli-client-agent-stale-restore-"));
const calls: any[] = [];
const result = await runHwlabCli([
"client",
"agent",
"send",
"--base-url",
"http://web.test",
"--cookie",
"hwlab_session=session-a",
"--message",
"start a fresh conversation",
"--trace-id",
"trc_stale_restore",
"--conversation-id",
"cnv_stale_restore",
"--session-id",
"ses_stale_restore",
"--wait",
"--poll-interval-ms",
"1",
"--timeout-ms",
"1000"
], {
cwd,
fetchImpl: async (url, init) => {
calls.push({ url: String(url), init, body: init?.body ? JSON.parse(String(init.body)) : null });
if (String(url).endsWith("/v1/workbench/workspace?projectId=prj_device_pod_workbench")) {
return new Response(JSON.stringify({
ok: true,
workspace: {
workspaceId: "wsp_stale_restore",
revision: 21,
selectedConversationId: "cnv_previous_restore",
selectedAgentSessionId: "ses_previous_restore",
selectedConversation: {
conversationId: "cnv_previous_restore",
sessionId: "ses_previous_restore",
threadId: "thread-previous-restore"
},
workspace: {
sessionStatus: "idle",
threadId: "thread-previous-restore"
}
}
}), { status: 200 });
}
if (String(url).endsWith("/v1/agent/chat")) {
return new Response(JSON.stringify({ accepted: true, status: "running", traceId: "trc_stale_restore", resultUrl: "/v1/agent/chat/result/trc_stale_restore" }), { status: 202 });
}
if (String(url).endsWith("/v1/agent/chat/result/trc_stale_restore")) {
return new Response(JSON.stringify({ status: "completed", traceId: "trc_stale_restore", conversationId: "cnv_stale_restore", sessionId: "ses_stale_restore", reply: { role: "assistant", content: "stale restore ready" } }), { status: 200 });
}
if (String(url).endsWith("/v1/workbench/workspace/wsp_stale_restore")) {
return new Response(JSON.stringify({ ok: true }), { status: 200 });
}
return new Response(JSON.stringify({ ok: false, error: { code: "unexpected_test_route" } }), { status: 404 });
},
sleep: async () => {}
});
assert.equal(result.exitCode, 0);
assert.equal(calls[4].url, "http://web.test/v1/workbench/workspace?projectId=prj_device_pod_workbench");
assert.equal(result.payload.workspace.selectedConversationId, "cnv_stale_restore");
assert.equal(result.payload.workspace.selectedAgentSessionId, "ses_stale_restore");
assert.equal(Object.hasOwn(result.payload.workspace, "threadId"), false);
assert.equal(result.payload.workspace.selectedConversation.conversationId, "cnv_stale_restore");
assert.equal(result.payload.workspace.selectedConversation.sessionId, "ses_stale_restore");
assert.equal(Object.hasOwn(result.payload.workspace.selectedConversation, "threadId"), false);
const session = JSON.parse(await readFile(path.join(cwd, ".state/hwlab-cli/session.json"), "utf8"));
assert.equal(session.workspace.selectedConversationId, "cnv_stale_restore");
assert.equal(session.workspace.selectedAgentSessionId, "ses_stale_restore");
assert.equal(Object.hasOwn(session.workspace, "threadId"), false);
});
test("hwlab-cli client agent send preserves Web continuation fields", async () => {
const cwd = await mkdtemp(path.join(os.tmpdir(), "hwlab-cli-client-agent-continuation-"));
await writeFile(path.join(cwd, "prompt.txt"), "看看有什么device-pod能用?", "utf8");
const calls: any[] = [];
const result = await runHwlabCli([
"client",
"agent",
"send",
"--base-url",
"http://web.test",
"--cookie",
"hwlab_session=session-a",
"--message-file",
"prompt.txt",
"--trace-id",
"trc_web_continue",
"--conversation-id",
"cnv_web_continue",
"--session-id",
"ses_web_continue",
"--thread-id",
"019e8078-db67-7750-a5d9-1a99f3abd445",
"--retry-of",
"trc_previous"
], {
cwd,
fetchImpl: async (url, init) => {
calls.push({ url: String(url), init, body: init?.body ? JSON.parse(String(init.body)) : null });
if (String(url).includes("/v1/workbench/workspace?")) {
return new Response(JSON.stringify({ ok: true, workspace: { workspaceId: "wsp_continue", revision: 7 } }), { status: 200 });
}
return new Response(JSON.stringify({ accepted: true, status: "running", traceId: "trc_web_continue", resultUrl: "/v1/agent/chat/result/trc_web_continue" }), { status: 202 });
},
stdinText: "fallback stdin",
sleep: async () => {}
});
assert.equal(result.exitCode, 0);
assert.equal(calls[0].url, "http://web.test/v1/workbench/workspace?projectId=prj_device_pod_workbench");
assert.equal(calls[1].url, "http://web.test/v1/agent/chat");
assert.equal(calls[1].body.message, "看看有什么device-pod能用?");
assert.equal(calls[1].body.conversationId, "cnv_web_continue");
assert.equal(calls[1].body.sessionId, "ses_web_continue");
assert.equal(calls[1].body.threadId, "019e8078-db67-7750-a5d9-1a99f3abd445");
assert.equal(calls[1].body.retryOf, "trc_previous");
assert.equal(result.payload.continuation.webEquivalent, true);
assert.equal(result.payload.continuation.threadId, "019e8078-db67-7750-a5d9-1a99f3abd445");
assert.equal(result.payload.continuation.retryOf, "trc_previous");
assert.equal(JSON.stringify(result.payload).includes("hwlab_session=session-a"), false);
});
test("hwlab-cli client agent send can replay continuation from an existing trace", async () => {
const calls: any[] = [];
const result = await runHwlabCli([
"client",
"agent",
"send",
"--base-url",
"http://web.test",
"--cookie",
"hwlab_session=session-a",
"--message",
"retry device pod question",
"--from-trace",
"trc_source_web",
"--trace-id",
"trc_replay_web"
], {
fetchImpl: async (url, init) => {
calls.push({ url: String(url), init, body: init?.body ? JSON.parse(String(init.body)) : null });
if (String(url).endsWith("/v1/agent/chat/inspect?traceId=trc_source_web")) {
return new Response(JSON.stringify({
ok: true,
status: "found",
latestTraceId: "trc_source_web",
query: { traceId: "trc_source_web" },
conversationFacts: { conversationId: "cnv_source", sessionId: "ses_source" },
session: { conversationId: "cnv_source", sessionId: "ses_source", threadId: "019e8078-db67-7750-a5d9-1a99f3abd445" }
}), { status: 200 });
}
if (String(url).endsWith("/v1/workbench/workspace?projectId=prj_device_pod_workbench")) {
return new Response(JSON.stringify({ ok: true, workspace: { workspaceId: "wsp_replay", revision: 4 } }), { status: 200 });
}
return new Response(JSON.stringify({ accepted: true, status: "running", traceId: "trc_replay_web", resultUrl: "/v1/agent/chat/result/trc_replay_web" }), { status: 202 });
},
sleep: async () => {}
});
assert.equal(result.exitCode, 0);
assert.equal(calls[0].url, "http://web.test/v1/agent/chat/inspect?traceId=trc_source_web");
assert.equal(calls[1].url, "http://web.test/v1/workbench/workspace?projectId=prj_device_pod_workbench");
assert.equal(calls[2].url, "http://web.test/v1/agent/chat");
assert.equal(calls[2].body.conversationId, "cnv_source");
assert.equal(calls[2].body.sessionId, "ses_source");
assert.equal(calls[2].body.threadId, "019e8078-db67-7750-a5d9-1a99f3abd445");
assert.equal(calls[2].body.retryOf, "trc_source_web");
assert.equal(calls[2].body.workspaceId, "wsp_replay");
assert.equal(calls[2].body.expectedWorkspaceRevision, 4);
assert.equal(result.payload.continuation.replayedFromTrace, "trc_source_web");
assert.equal(result.payload.replay.source, "cloud-web:/v1/agent/chat/inspect");
assert.equal(JSON.stringify(result.payload).includes("hwlab_session=session-a"), false);
});
test("hwlab-cli client agent steer posts to Web-equivalent steer route", async () => {
const calls: any[] = [];
const result = await runHwlabCli([
"client",
"agent",
"steer",
"trc_target_steer",
"--base-url",
"http://web.test",
"--cookie",
"hwlab_session=session-a",
"--message",
"please include STEER_MARK",
"--steer-trace-id",
"trc_steer_cli",
"--conversation-id",
"cnv_steer",
"--session-id",
"ses_steer",
"--thread-id",
"thread-steer"
], {
fetchImpl: async (url, init) => {
calls.push({ url: String(url), init, body: init?.body ? JSON.parse(String(init.body)) : null });
return new Response(JSON.stringify({
ok: true,
accepted: true,
status: "running",
route: "/v1/agent/chat/steer",
traceId: "trc_target_steer",
steerTraceId: "trc_steer_cli",
conversationId: "cnv_steer",
sessionId: "ses_steer",
agentRun: { runId: "run_steer", targetCommandId: "cmd_turn", steerCommandId: "cmd_steer" },
traceUrl: "/v1/agent/chat/trace/trc_target_steer",
resultUrl: "/v1/agent/chat/result/trc_target_steer"
}), { status: 202 });
},
sleep: async () => {}
});
assert.equal(result.exitCode, 0);
assert.equal(calls[0].url, "http://web.test/v1/agent/chat/steer");
assert.equal(calls[0].init.headers["x-trace-id"], "trc_target_steer");
assert.equal(calls[0].init.headers.prefer, "respond-async");
assert.equal(calls[0].body.traceId, "trc_target_steer");
assert.equal(calls[0].body.targetTraceId, "trc_target_steer");
assert.equal(calls[0].body.steerTraceId, "trc_steer_cli");
assert.equal(calls[0].body.message, "please include STEER_MARK");
assert.equal(calls[0].body.conversationId, "cnv_steer");
assert.equal(calls[0].body.sessionId, "ses_steer");
assert.equal(calls[0].body.threadId, "thread-steer");
assert.equal(result.payload.action, "client.agent.steer");
assert.equal(result.payload.route.path, "/v1/agent/chat/steer");
assert.equal(result.payload.traceId, "trc_target_steer");
assert.equal(result.payload.steerTraceId, "trc_steer_cli");
assert.equal(result.payload.body.agentRun.steerCommandId, "cmd_steer");
assert.equal(result.payload.waitPolicy.webEquivalent, true);
});
test("hwlab-cli agent composer status detects Web unlocked steer mode", async () => {
const calls: any[] = [];
const result = await runHwlabCli([
"client",
"agent",
"composer",
"status",
"--base-url",
"http://web.test",
"--cookie",
"hwlab_session=session-a"
], {
fetchImpl: async (url, init) => {
calls.push({ url: String(url), init });
return new Response(JSON.stringify({
ok: true,
workspace: {
workspaceId: "wsp_composer",
revision: 9,
selectedConversationId: "cnv_composer",
selectedAgentSessionId: "ses_composer",
workspace: {
activeTraceId: "trc_composer_active",
threadId: "thread-composer",
sessionStatus: "running",
messages: [{ role: "agent", status: "running", traceId: "trc_composer_active" }]
}
}
}), { status: 200 });
}
});
assert.equal(result.exitCode, 0);
assert.equal(calls[0].url, "http://web.test/v1/workbench/workspace?projectId=prj_device_pod_workbench");
assert.equal(result.payload.action, "client.agent.composer.status");
assert.equal(result.payload.composer.webEquivalent, true);
assert.equal(result.payload.composer.locked, false);
assert.equal(result.payload.composer.disabled, false);
assert.equal(result.payload.composer.submitMode, "steer");
assert.equal(result.payload.composer.route, "/v1/agent/chat/steer");
assert.equal(result.payload.composer.targetTraceId, "trc_composer_active");
assert.equal(result.payload.route.path, "/v1/agent/chat/steer");
});
test("hwlab-cli agent composer submit auto routes active Web turn to steer", async () => {
const calls: any[] = [];
const result = await runHwlabCli([
"client",
"agent",
"composer",
"submit",
"--base-url",
"http://web.test",
"--cookie",
"hwlab_session=session-a",
"--message",
"please steer from composer",
"--steer-trace-id",
"trc_steer_composer"
], {
fetchImpl: async (url, init) => {
calls.push({ url: String(url), init, body: init?.body ? JSON.parse(String(init.body)) : null });
if (String(url).endsWith("/v1/workbench/workspace?projectId=prj_device_pod_workbench")) {
return new Response(JSON.stringify({
ok: true,
workspace: {
workspaceId: "wsp_composer",
revision: 9,
selectedConversationId: "cnv_composer",
selectedAgentSessionId: "ses_composer",
workspace: {
activeTraceId: "trc_composer_active",
threadId: "thread-composer",
sessionStatus: "running",
messages: [{ role: "agent", status: "running", traceId: "trc_composer_active" }]
}
}
}), { status: 200 });
}
return new Response(JSON.stringify({
ok: true,
accepted: true,
status: "running",
traceId: "trc_composer_active",
steerTraceId: "trc_steer_composer",
agentRun: { steerCommandId: "cmd_composer_steer" }
}), { status: 202 });
},
sleep: async () => {}
});
assert.equal(result.exitCode, 0);
assert.equal(calls[1].url, "http://web.test/v1/agent/chat/steer");
assert.equal(calls[1].init.headers["x-trace-id"], "trc_composer_active");
assert.equal(calls[1].body.traceId, "trc_composer_active");
assert.equal(calls[1].body.targetTraceId, "trc_composer_active");
assert.equal(calls[1].body.steerTraceId, "trc_steer_composer");
assert.equal(calls[1].body.message, "please steer from composer");
assert.equal(calls[1].body.conversationId, "cnv_composer");
assert.equal(calls[1].body.sessionId, "ses_composer");
assert.equal(calls[1].body.threadId, "thread-composer");
assert.equal(result.payload.action, "client.agent.composer.submit");
assert.equal(result.payload.composer.locked, false);
assert.equal(result.payload.composer.submitMode, "steer");
assert.equal(result.payload.route.path, "/v1/agent/chat/steer");
assert.equal(result.payload.body.agentRun.steerCommandId, "cmd_composer_steer");
});
test("hwlab-cli client agent trace shows assistant stream text in compact output", async () => {
const result = await runHwlabCli(["client", "agent", "trace", "trc_test", "--base-url", "http://web.test", "--cookie", "hwlab_session=session-a"], {
fetchImpl: async () => new Response(JSON.stringify({
status: "completed",
traceId: "trc_test",
assistantStreams: [{ status: "streaming", text: "hello from agent", chunkCount: 3 }],
events: [{ status: "completed" }]
}), { status: 200 })
});
assert.equal(result.exitCode, 0);
assert.equal(result.payload.body.assistantText, "hello from agent");
assert.equal(result.payload.body.assistantStreams[0].text, "hello from agent");
assert.equal(result.payload.body.eventsCount, 1);
});
test("hwlab-cli client agent trace can render with the Web trace row path", async () => {
const result = await runHwlabCli(["client", "agent", "trace", "trc_render", "--base-url", "http://web.test", "--cookie", "hwlab_session=session-a", "--render", "web"], {
fetchImpl: async () => new Response(JSON.stringify({
status: "completed",
traceId: "trc_render",
events: [
{ traceId: "trc_render", seq: 1, label: "request:accepted", status: "accepted", createdAt: "2026-06-01T13:00:00.000Z", promptSummary: "看看有什么device-pod能用?" },
{ traceId: "trc_render", seq: 2, label: "session:reused", status: "observed", createdAt: "2026-06-01T13:00:01.000Z" },
{ traceId: "trc_render", seq: 3, label: "assistant:completed", type: "assistant_message", terminal: true, status: "completed", itemId: "assistant-final", createdAt: "2026-06-01T13:00:02.000Z", message: "当前有两个 device-pod 可用。" }
]
}), { status: 200 })
});
assert.equal(result.exitCode, 0);
assert.equal(result.payload.body.render, "web");
assert.equal(result.payload.body.renderer, "web/hwlab-cloud-web/app-trace:traceDisplayRows");
assert.equal(result.payload.body.sourceEventCount, 3);
assert.ok(result.payload.body.renderedRowCount >= 1);
assert.ok(result.payload.body.rows.some((row: any) => /助手最后一条消息/u.test(row.header)));
assert.equal(JSON.stringify(result.payload.body.rows).includes("session:reused"), false);
});
test("hwlab-cli Web trace render reports suppressed noise and command tool details", async () => {
const result = await runHwlabCli(["client", "agent", "trace", "trc_render_agentrun", "--base-url", "http://web.test", "--cookie", "hwlab_session=session-a", "--render", "web"], {
fetchImpl: async () => new Response(JSON.stringify({
status: "completed",
traceId: "trc_render_agentrun",
events: [
{ traceId: "trc_render_agentrun", seq: 1, label: "agentrun:backend:command-created", status: "running", type: "backend", createdAt: "2026-06-01T13:00:00.000Z" },
{ traceId: "trc_render_agentrun", seq: 2, label: "agentrun:backend:thread/status/changed", status: "running", type: "backend", createdAt: "2026-06-01T13:00:01.000Z" },
{ traceId: "trc_render_agentrun", seq: 3, label: "item/commandExecution:completed", type: "tool_call", toolName: "commandExecution", status: "completed", itemId: "call_1", command: "/bin/sh -lc 'hwpod profile list'", exitCode: 0, stdoutSummary: '{"ok":true,"action":"profile.list"}', createdAt: "2026-06-01T13:00:02.000Z" },
{ traceId: "trc_render_agentrun", seq: 4, label: "agentrun:tool:item/started", type: "tool_call", toolName: "commandExecution", status: "started", itemId: "call_2", command: "/bin/sh -lc 'printf TOOL_RENDER_OK'", createdAt: "2026-06-01T13:00:02.100Z" },
{ traceId: "trc_render_agentrun", seq: 5, label: "agentrun:tool:item/completed", type: "tool_call", toolName: "commandExecution", status: "completed", itemId: "call_2", command: "/bin/sh -lc 'printf TOOL_RENDER_OK'", exitCode: 0, stdoutSummary: "TOOL_RENDER_OK", outputBytes: 14, createdAt: "2026-06-01T13:00:02.200Z" },
{ traceId: "trc_render_agentrun", seq: 6, label: "agentrun:assistant:message", type: "assistant", status: "running", message: "当前可见 device-pod。", createdAt: "2026-06-01T13:00:03.000Z" }
]
}), { status: 200 })
});
assert.equal(result.exitCode, 0);
assert.equal(result.payload.body.render, "web");
assert.equal(result.payload.body.sourceEventCount, 6);
assert.equal(result.payload.body.noiseEventCount, 2);
const text = JSON.stringify(result.payload.body.rows);
assert.equal(text.includes("thread/status/changed"), false);
assert.match(text, /hwpod profile list/u);
assert.match(text, /profile\.list/u);
assert.match(text, /TOOL_RENDER_OK/u);
assert.equal(text.includes("toolName=item/started"), false);
assert.equal(text.includes("toolName=item/completed"), false);
assert.match(text, /当前可见 device-pod/u);
});
test("hwlab-cli Web trace render keeps AgentRun middle assistant message", async () => {
const result = await runHwlabCli(["client", "agent", "trace", "trc_render_agentrun_middle", "--base-url", "http://web.test", "--cookie", "hwlab_session=session-a", "--render", "web"], {
fetchImpl: async () => new Response(JSON.stringify({
status: "completed",
traceId: "trc_render_agentrun_middle",
events: [
{ traceId: "trc_render_agentrun_middle", seq: 1, label: "agentrun:request:accepted", status: "running", type: "request", createdAt: "2026-06-01T13:00:00.000Z" },
{ traceId: "trc_render_agentrun_middle", seq: 2, label: "agentrun:assistant:message", type: "assistant", status: "running", itemId: "msg_progress", messageIndex: 1, messageCount: 2, replyAuthority: false, final: false, message: "我先检查当前 device-pod 列表。", createdAt: "2026-06-01T13:00:01.000Z" },
{ traceId: "trc_render_agentrun_middle", seq: 3, label: "agentrun:assistant:message", type: "assistant", status: "completed", itemId: "msg_final", messageIndex: 2, messageCount: 2, replyAuthority: true, final: true, terminal: true, message: "当前有 2 个 device-pod 可用。", createdAt: "2026-06-01T13:00:02.000Z" },
{ traceId: "trc_render_agentrun_middle", seq: 4, label: "agentrun:terminal:completed", type: "result", terminal: true, status: "completed", createdAt: "2026-06-01T13:00:03.000Z" }
]
}), { status: 200 })
});
assert.equal(result.exitCode, 0);
assert.equal(result.payload.body.render, "web");
const text = JSON.stringify(result.payload.body.rows);
assert.match(text, /我先检查当前 device-pod 列表/u);
assert.match(text, /当前有 2 个 device-pod 可用/u);
assert.match(text, /助手最后一条消息/u);
});
test("hwlab-cli Web trace render keeps the full final-response body without truncating long agent summaries", async () => {
const longSummary = "下面是详细总结。".repeat(2000);
const result = await runHwlabCli(["client", "agent", "trace", "trc_final_long", "--base-url", "http://web.test", "--cookie", "hwlab_session=session-a", "--render", "web"], {
fetchImpl: async () => new Response(JSON.stringify({
status: "completed",
traceId: "trc_final_long",
events: [
{ traceId: "trc_final_long", seq: 1, label: "request:accepted", status: "accepted", createdAt: "2026-06-01T13:00:00.000Z", promptSummary: "请给出详细总结" },
{ traceId: "trc_final_long", seq: 2, label: "assistant:completed", type: "assistant_message", terminal: true, status: "completed", itemId: "assistant-final", createdAt: "2026-06-01T13:00:02.000Z", message: longSummary }
]
}), { status: 200 })
});
assert.equal(result.exitCode, 0);
const terminalRow = (result.payload.body.rows as any[]).find((row) => row.terminal === true);
assert.ok(terminalRow, "expected a terminal row to be rendered for the final response");
assert.equal(terminalRow.body, longSummary);
assert.equal(terminalRow.body?.includes("..."), false);
});
test("hwlab-cli client agent trace auto logs in when protected trace has no local session", async () => {
const cwd = await mkdtemp(path.join(os.tmpdir(), "hwlab-cli-client-auth-"));
const calls: any[] = [];
const result = await runHwlabCli([
"client",
"agent",
"trace",
"trc_protected",
"--base-url",
"http://web.test",
"--username",
"admin"
], {
cwd,
env: { HWLAB_PASSWORD: "secret-password" },
fetchImpl: async (url, init) => {
calls.push({ url: String(url), init, body: init?.body ? JSON.parse(String(init.body)) : null });
if (String(url).endsWith("/auth/login")) {
return new Response(JSON.stringify({ authenticated: true, user: { username: "admin" } }), {
status: 200,
headers: { "set-cookie": "hwlab_session=session-auto; Path=/; HttpOnly" }
});
}
return new Response(JSON.stringify({ status: "completed", traceId: "trc_protected", events: [] }), { status: 200 });
},
now: () => "2026-05-31T07:20:00.000Z"
});
assert.equal(result.exitCode, 0);
assert.deepEqual(calls.map((call) => call.url), [
"http://web.test/auth/login",
"http://web.test/v1/agent/chat/trace/trc_protected"
]);
assert.equal(calls[1].init.headers.cookie, "hwlab_session=session-auto");
assert.equal(JSON.stringify(result.payload).includes("secret-password"), false);
const session = JSON.parse(await readFile(path.join(cwd, ".state/hwlab-cli/session.json"), "utf8"));
assert.equal(session.cookie, "hwlab_session=session-auto");
});
test("hwlab-cli client agent result refreshes expired session after 401 and reports compact result", async () => {
const cwd = await mkdtemp(path.join(os.tmpdir(), "hwlab-cli-client-auth-refresh-"));
await runHwlabCli(["client", "auth", "login", "--base-url", "http://web.test", "--username", "admin", "--password", "old"], {
cwd,
fetchImpl: async () => new Response(JSON.stringify({ authenticated: true, user: { username: "admin" } }), { status: 200, headers: { "set-cookie": "hwlab_session=session-old; Path=/" } })
});
const calls: any[] = [];
const result = await runHwlabCli([
"client",
"agent",
"result",
"trc_refresh",
"--base-url",
"http://web.test",
"--username",
"admin"
], {
cwd,
env: { HWLAB_PASSWORD: "new-password" },
fetchImpl: async (url, init) => {
calls.push({ url: String(url), init, body: init?.body ? JSON.parse(String(init.body)) : null });
if (String(url).endsWith("/auth/login")) {
return new Response(JSON.stringify({ authenticated: true, user: { username: "admin" } }), {
status: 200,
headers: { "set-cookie": "hwlab_session=session-new; Path=/" }
});
}
if (init?.headers?.cookie === "hwlab_session=session-old") {
return new Response(JSON.stringify({ error: { code: "auth_required" } }), { status: 401 });
}
return new Response(JSON.stringify({ status: "completed", traceId: "trc_refresh", reply: { role: "assistant", content: "done" } }), { status: 200 });
},
sleep: async () => {},
now: () => "2026-05-31T07:21:00.000Z"
});
assert.equal(result.exitCode, 0);
assert.deepEqual(calls.map((call) => call.url), [
"http://web.test/v1/agent/chat/result/trc_refresh",
"http://web.test/auth/login",
"http://web.test/v1/agent/chat/result/trc_refresh"
]);
assert.equal(calls[0].init.headers.cookie, "hwlab_session=session-old");
assert.equal(calls[2].init.headers.cookie, "hwlab_session=session-new");
assert.equal(result.payload.action, "client.agent.result");
assert.equal(result.payload.body.assistantText, "done");
});
test("hwlab-cli client protected agent commands return structured auth diagnosis for forbidden trace", async () => {
const result = await runHwlabCli(["client", "agent", "trace", "trc_forbidden", "--base-url", "http://web.test", "--cookie", "hwlab_session=session-a"], {
fetchImpl: async () => new Response(JSON.stringify({ error: { code: "agent_session_owner_required" } }), { status: 403 })
});
assert.equal(result.exitCode, 1);
assert.equal(result.payload.httpStatus, 403);
assert.equal(result.payload.authDiagnosis.code, "auth_forbidden");
assert.equal(result.payload.authDiagnosis.cookieSource, "explicit");
assert.match(result.payload.authDiagnosis.message, /无权访问/u);
assert.equal(result.payload.request.url, "http://web.test/v1/agent/chat/trace/trc_forbidden");
assert.match(result.payload.authDiagnosis.nextCommands[1], /client auth login/u);
});
test("hwlab-cli client protected agent command reports missing credentials with state visibility", async () => {
const cwd = await mkdtemp(path.join(os.tmpdir(), "hwlab-cli-client-auth-missing-"));
const result = await runHwlabCli(["client", "agent", "result", "trc_missing_auth", "--base-url", "http://web.test"], {
cwd,
fetchImpl: async () => new Response(JSON.stringify({ ok: false, error: { code: "auth_required" } }), { status: 401 })
});
assert.equal(result.exitCode, 1);
assert.equal(result.payload.action, "client.agent.result");
assert.equal(result.payload.auth.required, true);
assert.equal(result.payload.auth.localSession.exists, false);
assert.equal(result.payload.authDiagnosis.code, "auth_credentials_missing");
assert.equal(result.payload.authDiagnosis.localSession.ignoredReason, "state_file_missing");
assert.match(result.payload.authDiagnosis.nextCommands[1], /--password-env HWLAB_PASSWORD/u);
assert.equal(result.payload.request.httpStatus, 401);
});
test("hwlab-cli client agent inspect builds protected inspect query", async () => {
const calls: any[] = [];
const result = await runHwlabCli([
"client",
"agent",
"inspect",
"--trace-id",
"trc_inspect",
"--conversation-id",
"cnv_inspect",
"--session-id",
"ses_inspect",
"--thread-id",
"thread-1",
"--base-url",
"http://web.test",
"--cookie",
"hwlab_session=session-a"
], {
fetchImpl: async (url, init) => {
calls.push({ url: String(url), init });
return new Response(JSON.stringify({ ok: true, status: "found", latestTraceId: "trc_inspect" }), { status: 200 });
}
});
assert.equal(result.exitCode, 0);
assert.equal(calls[0].url, "http://web.test/v1/agent/chat/inspect?traceId=trc_inspect&conversationId=cnv_inspect&sessionId=ses_inspect&threadId=thread-1");
assert.equal(calls[0].init.headers.cookie, "hwlab_session=session-a");
assert.equal(result.payload.action, "client.agent.inspect");
assert.equal(result.payload.body.status, "found");
});
test("hwlab-cli client harness submits waits and audits trace friction", async () => {
const calls: any[] = [];
const result = await runHwlabCli([
"client",
"harness",
"submit",
"--base-url",
"http://web.test",
"--cookie",
"hwlab_session=session-a",
"--message",
"hello",
"--trace-id",
"trc_harness",
"--conversation-id",
"cnv_harness"
], {
fetchImpl: async (url, init) => {
calls.push({ url: String(url), init, body: JSON.parse(String(init?.body ?? "{}")) });
return new Response(JSON.stringify({
accepted: true,
status: "running",
resultUrl: "/v1/agent/chat/result/trc_harness",
traceUrl: "/v1/agent/chat/trace/trc_harness"
}), { status: 202 });
}
});
assert.equal(result.exitCode, 0);
assert.equal(result.payload.action, "client.harness.submit");
assert.equal(calls[0].url, "http://web.test/v1/agent/chat");
assert.equal(calls[0].init.headers.prefer, "respond-async");
assert.equal(calls[0].body.shortConnection, true);
let polls = 0;
const wait = await runHwlabCli([
"client",
"harness-opt",
"wait",
"trc_harness",
"--base-url",
"http://web.test",
"--cookie",
"hwlab_session=session-a",
"--poll-interval-ms",
"1",
"--timeout-ms",
"1000"
], {
fetchImpl: async () => {
polls += 1;
return new Response(JSON.stringify(polls === 1
? { status: "running", traceId: "trc_harness" }
: { status: "completed", traceId: "trc_harness", reply: { content: "done" } }), { status: 200 });
},
sleep: async () => {}
});
assert.equal(wait.exitCode, 0);
assert.equal(wait.payload.timedOut, false);
assert.equal(wait.payload.timeoutMsRequested, 1000);
assert.equal(wait.payload.timeoutMsEffective, 1000);
assert.equal(wait.payload.waitPolicy.remotePassthroughSafe, true);
assert.equal(wait.payload.body.assistantText, "done");
const cappedWait = await runHwlabCli([
"client",
"harness",
"wait",
"trc_harness",
"--base-url",
"http://web.test",
"--cookie",
"hwlab_session=session-a",
"--poll-interval-ms",
"1000",
"--timeout-ms",
"60000"
], {
fetchImpl: async () => new Response(JSON.stringify({ status: "completed", traceId: "trc_harness", reply: { content: "done" } }), { status: 200 }),
sleep: async () => {}
});
assert.equal(cappedWait.exitCode, 0);
assert.equal(cappedWait.payload.status, "succeeded");
assert.equal(cappedWait.payload.timeoutMsRequested, 60000);
assert.equal(cappedWait.payload.timeoutMsEffective, 50000);
assert.equal(cappedWait.payload.waitPolicy.capped, true);
assert.ok(cappedWait.payload.waitPolicy.nextCommands.some((command) => command.includes("harness result trc_harness")));
const audit = await runHwlabCli([
"client",
"harness",
"audit",
"trc_harness",
"--require-bootsharp",
"--base-url",
"http://web.test",
"--cookie",
"hwlab_session=session-a"
], {
fetchImpl: async () => new Response(JSON.stringify({
traceId: "trc_harness",
events: [
{
type: "tool_call",
command: "node /app/tools/device-pod-cli.mjs device-pod-a:workspace:/ put User/main.c"
}
]
}), { status: 200 })
});
assert.equal(audit.exitCode, 0);
assert.equal(audit.payload.status, "friction_detected");
assert.ok(audit.payload.frictionSignals.some((signal) => signal.kind === "long_device_pod_cli_path"));
assert.ok(audit.payload.frictionSignals.some((signal) => signal.kind === "workspace_put_text_edit"));
assert.ok(audit.payload.frictionSignals.some((signal) => signal.kind === "missing_bootsharp"));
});
test("hwlab-cli client harness audit treats hwpod bootsharp flow as clean", async () => {
const audit = await runHwlabCli([
"client",
"harness",
"audit",
"trc_hwpod",
"--require-bootsharp",
"--base-url",
"http://web.test",
"--cookie",
"hwlab_session=session-a"
], {
fetchImpl: async () => new Response(JSON.stringify({
traceId: "trc_hwpod",
events: [
{ type: "tool_call", command: "hwpod bootsharp --pod-id D601-F103-V2" },
{ type: "tool_call", command: "hwpod D601-F103-V2:workspace:/ build status job_123" }
]
}), { status: 200 })
});
assert.equal(audit.exitCode, 0);
assert.equal(audit.payload.status, "clean");
assert.equal(audit.payload.summary.hasHwpod, true);
assert.equal(audit.payload.summary.hasBootsharp, true);
});
test("hwlab-cli client workbench summary probes Cloud Web non-visual surfaces", async () => {
const seen: string[] = [];
const result = await runHwlabCli(["client", "workbench", "summary", "--base-url", "http://web.test", "--cookie", "session-a", "--pod-id", "device-pod-71-freq"], {
fetchImpl: async (url) => {
seen.push(String(url));
return new Response(JSON.stringify({ ok: true, status: "ok" }), { status: 200 });
}
});
assert.equal(result.exitCode, 0);
assert.equal(seen.includes("http://web.test/health/live"), true);
assert.equal(seen.includes("http://web.test/v1"), true);
assert.equal(seen.includes("http://web.test/v1/device-pods"), true);
assert.equal(seen.includes("http://web.test/v1/device-pods/device-pod-71-freq/status"), true);
assert.equal(result.payload.serviceRuntime, undefined);
});
test("hwlab-cli client request covers arbitrary Cloud Web same-origin API routes", async () => {
const calls: any[] = [];
const result = await runHwlabCli([
"client",
"request",
"GET",
"/v1/access/status",
"--base-url",
"http://web.test",
"--cookie",
"hwlab_session=session-a",
"--trace-id",
"trc_request"
], {
fetchImpl: async (url, init) => {
calls.push({ url: String(url), init });
return new Response(JSON.stringify({ ok: true, authenticated: true, actor: { username: "admin" }, roles: ["admin"] }), { status: 200 });
}
});
assert.equal(result.exitCode, 0);
assert.equal(calls[0].url, "http://web.test/v1/access/status");
assert.equal(calls[0].init.headers.cookie, "hwlab_session=session-a");
assert.equal(calls[0].init.headers["x-trace-id"], "trc_request");
assert.equal(result.payload.action, "client.request");
assert.equal(result.payload.route.path, "/v1/access/status");
assert.equal(result.payload.body.actor.username, "admin");
assert.equal(result.payload.body.fullBodyAvailable, true);
});
test("hwlab-cli client request compacts array responses by default", async () => {
const result = await runHwlabCli([
"client",
"request",
"GET",
"/v1/device-pods",
"--base-url",
"http://web.test",
"--cookie",
"hwlab_session=session-a"
], {
fetchImpl: async () => new Response(JSON.stringify([{ id: "pod-1", status: "ready" }, { id: "pod-2", status: "ready" }]), { status: 200 })
});
assert.equal(result.exitCode, 0);
assert.equal(result.payload.body.itemCount, 2);
assert.equal(result.payload.body.items[0].id, "pod-1");
assert.equal(result.payload.body.fullBodyAvailable, true);
});
test("hwlab-cli client rpc mirrors Cloud Web JSON-RPC envelope metadata", async () => {
const calls: any[] = [];
const result = await runHwlabCli([
"client",
"rpc",
"system.health",
"--base-url",
"http://web.test",
"--cookie",
"hwlab_session=session-a",
"--trace-id",
"trc_rpc",
"--id",
"req_rpc",
"--params-json",
"{}"
], {
fetchImpl: async (url, init) => {
calls.push({ url: String(url), init, body: JSON.parse(String(init?.body ?? "{}")) });
return new Response(JSON.stringify({ jsonrpc: "2.0", id: "req_rpc", result: { status: "ok" }, meta: { traceId: "trc_rpc", serviceId: "hwlab-cloud-api", environment: "dev" } }), { status: 200 });
}
});
assert.equal(result.exitCode, 0);
assert.equal(calls[0].url, "http://web.test/json-rpc");
assert.equal(calls[0].init.headers.cookie, "hwlab_session=session-a");
assert.equal(calls[0].body.method, "system.health");
assert.deepEqual(calls[0].body.params, {});
assert.deepEqual(calls[0].body.meta, { traceId: "trc_rpc", serviceId: "hwlab-cloud-web", environment: "dev" });
assert.equal(result.payload.action, "client.rpc");
assert.equal(result.payload.rpcMethod, "system.health");
assert.equal(result.payload.body.result.status, "ok");
assert.equal(result.payload.body.fullBodyAvailable, true);
});
test("hwlab-cli client request rejects absolute URLs", async () => {
const result = await runHwlabCli(["client", "request", "GET", "http://internal.test/v1", "--base-url", "http://web.test"], {
fetchImpl: async () => new Response("{}", { status: 200 })
});
assert.equal(result.exitCode, 1);
assert.equal(result.payload.error.code, "invalid_request_path");
});
test("hwlab-cli client request keeps command visibility on transport failure", async () => {
const result = await runHwlabCli(["client", "request", "GET", "/v1/access/status", "--base-url", "http://web.test", "--public"], {
fetchImpl: async () => {
throw Object.assign(new Error("connect ECONNREFUSED"), { name: "FetchError" });
}
});
assert.equal(result.exitCode, 1);
assert.equal(result.payload.action, "client.request");
assert.equal(result.payload.httpStatus, 0);
assert.equal(result.payload.request.url, "http://web.test/v1/access/status");
assert.equal(result.payload.request.transportError.code, "request_failed");
assert.equal(result.payload.body.error.code, "request_failed");
});