2363 lines
112 KiB
TypeScript
2363 lines
112 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 access uses API key auth on Cloud Web admin access routes", async () => {
|
|
const calls: any[] = [];
|
|
const grant = await runHwlabCli(["client", "access", "tools", "grant", "usr_alice", "hwpod", "--base-url", "http://web.test"], {
|
|
env: { HWLAB_API_KEY: "hwl_live_admin_test_key" },
|
|
fetchImpl: async (url, init) => {
|
|
calls.push({ url: String(url), init, body: String(init?.body ?? "") });
|
|
return new Response(JSON.stringify({ ok: true, changed: true, access: { user: { id: "usr_alice" }, tools: {}, tuples: [], openfga: { mode: "enforce", ready: true } } }), { status: 201 });
|
|
}
|
|
});
|
|
assert.equal(grant.exitCode, 0);
|
|
assert.equal(grant.payload.action, "client.access.tools.grant");
|
|
assert.equal(calls[0].url, "http://web.test/v1/admin/access/users/usr_alice/tools/hwpod/can-use");
|
|
assert.equal(calls[0].init.method, "PUT");
|
|
assert.equal(calls[0].init.headers.authorization, "Bearer hwl_live_admin_test_key");
|
|
assert.equal(calls[0].init.headers.cookie, undefined);
|
|
assert.equal(grant.payload.auth.authMethod, "api-key");
|
|
assert.equal(grant.payload.route.path, "/v1/admin/access/users/usr_alice/tools/hwpod/can-use");
|
|
assert.equal(grant.payload.body.openfga.mode, "enforce");
|
|
|
|
const user = await runHwlabCli(["client", "access", "users", "inspect", "usr_alice", "--base-url", "http://web.test"], {
|
|
env: { HWLAB_API_KEY: "hwl_live_admin_test_key" },
|
|
fetchImpl: async (url, init) => {
|
|
calls.push({ url: String(url), init });
|
|
return new Response(JSON.stringify({ contractVersion: "admin-access-v1", user: { id: "usr_alice", username: "alice" }, tools: {}, tuples: [], openfga: { mode: "enforce", ready: true } }), { status: 200 });
|
|
}
|
|
});
|
|
assert.equal(user.exitCode, 0);
|
|
assert.equal(calls[1].url, "http://web.test/v1/admin/access/users/usr_alice");
|
|
assert.equal(calls[1].init.method, "GET");
|
|
assert.equal(calls[1].init.headers.authorization, "Bearer hwl_live_admin_test_key");
|
|
assert.equal(user.payload.route.path, "/v1/admin/access/users/usr_alice");
|
|
assert.equal(user.payload.body.user.username, "alice");
|
|
});
|
|
|
|
test("hwlab-cli client provider-profiles uses Cloud Web admin routes", async () => {
|
|
const calls: any[] = [];
|
|
const result = await runHwlabCli(["client", "provider-profiles", "list", "--base-url", "http://web.test"], {
|
|
env: { HWLAB_API_KEY: "hwl_live_admin_test_key" },
|
|
fetchImpl: async (url, init) => {
|
|
calls.push({ url: String(url), init });
|
|
return new Response(JSON.stringify({
|
|
ok: true,
|
|
contractVersion: "provider-profile-management-v1",
|
|
delegation: { provider: "agentrun-v01", agentRunHost: "agentrun-mgr.agentrun-v01.svc.cluster.local", valuesPrinted: false },
|
|
items: [{ profile: "deepseek", configured: true, secretRef: { namespace: "agentrun-v01", name: "agentrun-v01-provider-deepseek", keys: ["auth.json", "config.toml"] }, credentialHashSuffix: "abc12345", bridge: { route: "HWLAB Moon Bridge -> DeepSeek official upstream", serviceUrl: "http://hwlab-deepseek-proxy.hwlab-v02.svc.cluster.local:4000/v1", responsesPath: "/v1/responses" }, valuesPrinted: false }]
|
|
}), { status: 200 });
|
|
}
|
|
});
|
|
|
|
assert.equal(result.exitCode, 0);
|
|
assert.equal(calls[0].url, "http://web.test/v1/admin/provider-profiles");
|
|
assert.equal(calls[0].init.method, "GET");
|
|
assert.equal(calls[0].init.headers.authorization, "Bearer hwl_live_admin_test_key");
|
|
assert.equal(calls[0].init.headers.cookie, undefined);
|
|
assert.equal(result.payload.route.path, "/v1/admin/provider-profiles");
|
|
assert.equal(result.payload.auth.authMethod, "api-key");
|
|
assert.equal(result.payload.body.items[0].profile, "deepseek");
|
|
assert.equal(result.payload.body.items[0].bridge.route, "HWLAB Moon Bridge -> DeepSeek official upstream");
|
|
assert.equal(JSON.stringify(result.payload).includes("sk-"), false);
|
|
});
|
|
|
|
test("hwlab-cli client provider-profiles set-key reads stdin and never prints key", async () => {
|
|
const calls: any[] = [];
|
|
const secret = "sk-test-provider-secret-123456";
|
|
const result = await runHwlabCli(["client", "provider-profiles", "set-key", "deepseek", "--key-stdin", "--base-url", "http://web.test", "--cookie", "hwlab_session=session-admin"], {
|
|
stdinText: secret,
|
|
fetchImpl: async (url, init) => {
|
|
calls.push({ url: String(url), init, body: JSON.parse(String(init?.body ?? "{}")) });
|
|
return new Response(JSON.stringify({ ok: true, status: "updated", profile: "deepseek", data: { profile: "deepseek", configured: true, secretRef: { namespace: "agentrun-v01", name: "agentrun-v01-provider-deepseek", keys: ["auth.json", "config.toml"] }, credentialHashSuffix: "abc12345", configHashSuffix: "def67890", requiresExternalBridgeUpdate: true, valuesPrinted: false }, valuesPrinted: false }), { status: 200 });
|
|
}
|
|
});
|
|
|
|
assert.equal(result.exitCode, 0);
|
|
assert.equal(calls[0].url, "http://web.test/v1/admin/provider-profiles/deepseek/credential");
|
|
assert.equal(calls[0].init.method, "PUT");
|
|
assert.equal(calls[0].body.apiKey, secret);
|
|
assert.equal(result.payload.body.profileStatus.profile, "deepseek");
|
|
assert.equal(result.payload.body.profileStatus.requiresExternalBridgeUpdate, true);
|
|
assert.equal(JSON.stringify(result.payload).includes(secret), false);
|
|
});
|
|
|
|
test("hwlab-cli client provider-profiles set-auth-json reads stdin and never prints auth json", async () => {
|
|
const calls: any[] = [];
|
|
const authJson = `${JSON.stringify({ OPENAI_API_KEY: "sk-test-provider-secret-123456" })}\n`;
|
|
const result = await runHwlabCli(["client", "provider-profiles", "set-auth-json", "hy", "--auth-json-stdin", "--base-url", "http://web.test"], {
|
|
stdinText: authJson,
|
|
env: { HWLAB_API_KEY: "hwl_live_admin_test_key" },
|
|
fetchImpl: async (url, init) => {
|
|
calls.push({ url: String(url), init, body: JSON.parse(String(init?.body ?? "{}")) });
|
|
return new Response(JSON.stringify({ ok: true, status: "updated", profile: "hy", data: { profile: "hy", configured: true, credentialSource: "auth-json", secretRef: { namespace: "agentrun-v01", name: "agentrun-v01-provider-hy", keys: ["auth.json", "config.toml"] }, credentialHashSuffix: "abc12345", configHashSuffix: "def67890", valuesPrinted: false }, valuesPrinted: false }), { status: 200 });
|
|
}
|
|
});
|
|
|
|
assert.equal(result.exitCode, 0);
|
|
assert.equal(calls[0].url, "http://web.test/v1/admin/provider-profiles/hy/credential");
|
|
assert.equal(calls[0].init.method, "PUT");
|
|
assert.equal(calls[0].body.authJson, authJson);
|
|
assert.equal(calls[0].body.apiKey, undefined);
|
|
assert.equal(result.payload.action, "client.provider-profiles.set-auth-json");
|
|
assert.equal(result.payload.body.profileStatus.profile, "hy");
|
|
assert.equal(JSON.stringify(result.payload).includes("OPENAI_API_KEY"), false);
|
|
assert.equal(JSON.stringify(result.payload).includes("sk-test-provider-secret-123456"), false);
|
|
});
|
|
|
|
test("hwlab-cli client provider-profiles config reads and writes config.toml", async () => {
|
|
const calls: any[] = [];
|
|
const configToml = 'model = "deepseek-chat"\nbase_url = "http://bridge/v1"\n';
|
|
const read = await runHwlabCli(["client", "provider-profiles", "config", "deepseek", "--base-url", "http://web.test", "--cookie", "hwlab_session=session-admin"], {
|
|
fetchImpl: async (url, init) => {
|
|
calls.push({ url: String(url), init });
|
|
return new Response(JSON.stringify({ ok: true, status: "ok", profile: "deepseek", data: { profile: "deepseek", configured: true, configToml, configHashSuffix: "def67890", configTomlPrinted: true, credentialValuesPrinted: false, valuesPrinted: false } }), { status: 200 });
|
|
}
|
|
});
|
|
assert.equal(read.exitCode, 0);
|
|
assert.equal(calls[0].url, "http://web.test/v1/admin/provider-profiles/deepseek/config");
|
|
assert.equal(calls[0].init.method, "GET");
|
|
assert.equal(read.payload.body.configToml, configToml);
|
|
assert.equal(read.payload.body.configTomlPrinted, true);
|
|
|
|
const write = await runHwlabCli(["client", "provider-profiles", "set-config", "deepseek", "--config-stdin", "--base-url", "http://web.test", "--cookie", "hwlab_session=session-admin"], {
|
|
stdinText: `${configToml}# edited\n`,
|
|
fetchImpl: async (url, init) => {
|
|
calls.push({ url: String(url), init, body: JSON.parse(String(init?.body ?? "{}")) });
|
|
return new Response(JSON.stringify({ ok: true, status: "updated", profile: "deepseek", data: { profile: "deepseek", configured: true, configHashSuffix: "fedcba98", valuesPrinted: false } }), { status: 200 });
|
|
}
|
|
});
|
|
assert.equal(write.exitCode, 0);
|
|
assert.equal(calls[1].url, "http://web.test/v1/admin/provider-profiles/deepseek/config");
|
|
assert.equal(calls[1].init.method, "PUT");
|
|
assert.equal(calls[1].body.configToml, `${configToml}# edited\n`);
|
|
assert.equal(write.payload.body.profileStatus.profile, "deepseek");
|
|
assert.equal(JSON.stringify(write.payload).includes("# edited"), false);
|
|
});
|
|
|
|
test("hwlab-cli client provider-profiles remove delegates to Cloud Web admin route", async () => {
|
|
const calls: any[] = [];
|
|
const result = await runHwlabCli(["client", "provider-profiles", "remove", "deepseek", "--base-url", "http://web.test"], {
|
|
env: { HWLAB_API_KEY: "hwl_live_admin_test_key" },
|
|
fetchImpl: async (url, init) => {
|
|
calls.push({ url: String(url), init });
|
|
return new Response(JSON.stringify({ ok: true, status: "removed", profile: "deepseek", data: { profile: "deepseek", configured: false, removed: true, builtinCapabilityRetained: true, deletedResourceVersion: "rv-removed", secretRef: { namespace: "agentrun-v01", name: "agentrun-v01-provider-deepseek", keys: ["auth.json", "config.toml"] }, credentialHashSuffix: "abc12345", configHashSuffix: "def67890", valuesPrinted: false }, valuesPrinted: false }), { status: 200 });
|
|
}
|
|
});
|
|
|
|
assert.equal(result.exitCode, 0);
|
|
assert.equal(calls[0].url, "http://web.test/v1/admin/provider-profiles/deepseek");
|
|
assert.equal(calls[0].init.method, "DELETE");
|
|
assert.equal(calls[0].init.headers.authorization, "Bearer hwl_live_admin_test_key");
|
|
assert.equal(result.payload.route.path, "/v1/admin/provider-profiles/deepseek");
|
|
assert.equal(result.payload.body.profileStatus.profile, "deepseek");
|
|
assert.equal(result.payload.body.profileStatus.removed, true);
|
|
assert.equal(result.payload.body.profileStatus.builtinCapabilityRetained, true);
|
|
});
|
|
|
|
test("hwlab-cli client provider-profiles validate can wait by polling validation result", async () => {
|
|
const calls: any[] = [];
|
|
const result = await runHwlabCli(["client", "provider-profiles", "validate", "deepseek", "--wait", "--timeout-ms", "5000", "--poll-interval-ms", "10", "--base-url", "http://web.test", "--cookie", "hwlab_session=session-admin"], {
|
|
sleep: async () => undefined,
|
|
fetchImpl: async (url, init) => {
|
|
calls.push({ url: String(url), init });
|
|
if (String(url).endsWith("/v1/admin/provider-profiles/deepseek/validate")) {
|
|
return new Response(JSON.stringify({ ok: true, data: { validationId: "val_0123456789abcdef0123456789abcdef", profile: "deepseek", runId: "run_1", commandId: "cmd_1", status: "running", valuesPrinted: false } }), { status: 200 });
|
|
}
|
|
return new Response(JSON.stringify({ ok: true, data: { validationId: "val_0123456789abcdef0123456789abcdef", profile: "deepseek", runId: "run_1", commandId: "cmd_1", status: "completed", result: { terminalStatus: "completed", agentRun: { jobName: "agentrun-v01-runner-test" } }, valuesPrinted: false } }), { status: 200 });
|
|
}
|
|
});
|
|
|
|
assert.equal(result.exitCode, 0);
|
|
assert.equal(calls[0].url, "http://web.test/v1/admin/provider-profiles/deepseek/validate");
|
|
assert.equal(calls[1].url, "http://web.test/v1/admin/provider-profiles/deepseek/validations/val_0123456789abcdef0123456789abcdef");
|
|
assert.equal(result.payload.waited, true);
|
|
assert.equal(result.payload.timedOut, false);
|
|
assert.equal(result.payload.body.validation.terminalStatus, "completed");
|
|
assert.equal(result.payload.body.validation.jobName, "agentrun-v01-runner-test");
|
|
});
|
|
|
|
test("hwlab-cli client request defaults to API key instead of selected Web session", async () => {
|
|
const calls: any[] = [];
|
|
const result = await runHwlabCli(["client", "request", "GET", "/v1/access/status", "--base-url", "http://web.test", "--profile", "alice"], {
|
|
env: { HWLAB_API_KEY: "hwl_live_alice_test_key" },
|
|
fetchImpl: async (url, init) => {
|
|
calls.push({ url: String(url), init });
|
|
return new Response(JSON.stringify({ ok: true, authenticated: true, actor: { username: "alice" }, authMethod: "api-key" }), { status: 200 });
|
|
}
|
|
});
|
|
|
|
assert.equal(result.exitCode, 0);
|
|
assert.equal(calls[0].init.headers.authorization, "Bearer hwl_live_alice_test_key");
|
|
assert.equal(calls[0].init.headers.cookie, undefined);
|
|
assert.equal(result.payload.auth.authMethod, "api-key");
|
|
});
|
|
|
|
test("hwlab-cli rejects API key aliases and requires HWLAB_API_KEY", async () => {
|
|
const aliasFromEnv = await runHwlabCli(["client", "auth", "whoami", "--base-url", "http://web.test"], {
|
|
env: { HWLAB_BEARER_TOKEN: "hwl_live_legacy_alias" },
|
|
fetchImpl: async () => {
|
|
throw new Error("request should not be sent when an API key alias is present");
|
|
}
|
|
});
|
|
assert.equal(aliasFromEnv.exitCode, 1);
|
|
assert.equal(aliasFromEnv.payload.error.code, "unsupported_api_key_source");
|
|
assert.deepEqual(aliasFromEnv.payload.error.details.unsupported, ["HWLAB_BEARER_TOKEN"]);
|
|
assert.equal(JSON.stringify(aliasFromEnv.payload).includes("hwl_live_legacy_alias"), false);
|
|
|
|
const aliasFromFlag = await runHwlabCli(["client", "auth", "whoami", "--base-url", "http://web.test", "--api-key", "hwl_live_flag_alias"], {
|
|
fetchImpl: async () => {
|
|
throw new Error("request should not be sent when an API key flag alias is present");
|
|
}
|
|
});
|
|
assert.equal(aliasFromFlag.exitCode, 1);
|
|
assert.equal(aliasFromFlag.payload.error.code, "unsupported_api_key_source");
|
|
assert.deepEqual(aliasFromFlag.payload.error.details.unsupported, ["--api-key"]);
|
|
assert.equal(JSON.stringify(aliasFromFlag.payload).includes("hwl_live_flag_alias"), false);
|
|
});
|
|
|
|
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.equal(missing.payload.authBoundary.defaultAuthMethod, "api-key");
|
|
assert.equal(missing.payload.authBoundary.localSessionDefault, false);
|
|
assert.match(missing.payload.nextCommands[1], /client auth whoami/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 whoami/u);
|
|
assert.equal(JSON.stringify(mismatch.payload.nextCommands).includes("run-bun.mjs"), false);
|
|
});
|
|
|
|
test("hwlab-cli auth status reports public v02 session but keeps it out of default CLI auth", 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");
|
|
assert.equal(status.payload.authBoundary.defaultAuthMethod, "api-key");
|
|
assert.equal(status.payload.authBoundary.localSessionDefault, false);
|
|
});
|
|
|
|
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 ")));
|
|
assert.ok(agent.payload.commands.some((command: string) => command.startsWith("send ") && command.includes("--provider-profile")));
|
|
|
|
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_hwpod_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 client session final-response verifies original conversation across project list and result", async () => {
|
|
const calls: any[] = [];
|
|
const finalText = "**直接答:能,但只到一半。**\n\n真实 final response";
|
|
const conversation = {
|
|
conversationId: "cnv_issue834",
|
|
projectId: "prj_v02_code_agent",
|
|
sessionId: "ses_issue834",
|
|
threadId: "thread_issue834",
|
|
status: "idle",
|
|
lastTraceId: "trc_issue834_final",
|
|
snapshot: { source: "code-agent-result" },
|
|
messages: [{ role: "agent", traceId: "trc_issue834_final", status: "idle", text: finalText }]
|
|
};
|
|
const fetchImpl = async (url: string | URL) => {
|
|
calls.push(String(url));
|
|
if (String(url).endsWith("/v1/agent/conversations/cnv_issue834")) {
|
|
return new Response(JSON.stringify({ ok: true, conversation }), { status: 200 });
|
|
}
|
|
if (String(url).endsWith("/v1/agent/conversations?projectId=prj_v02_code_agent&limit=100")) {
|
|
return new Response(JSON.stringify({ ok: true, conversations: [conversation] }), { status: 200 });
|
|
}
|
|
if (String(url).endsWith("/v1/agent/turns/trc_issue834_final")) {
|
|
return new Response(JSON.stringify({ ok: true, status: "completed", terminal: true, traceId: "trc_issue834_final", reply: { content: finalText } }), { status: 200 });
|
|
}
|
|
return new Response(JSON.stringify({ ok: false, error: { code: "unexpected_route" } }), { status: 404 });
|
|
};
|
|
|
|
const result = await runHwlabCli(["client", "session", "final-response", "cnv_issue834", "--base-url", "http://web.test", "--cookie", "hwlab_session=session-a"], { fetchImpl });
|
|
|
|
assert.equal(result.exitCode, 0);
|
|
assert.equal(result.payload.action, "client.session.final-response");
|
|
assert.equal(result.payload.validation.state, "passed");
|
|
assert.equal(result.payload.projectId, "prj_v02_code_agent");
|
|
assert.equal(result.payload.validation.checks.listFound, true);
|
|
assert.equal(result.payload.validation.checks.inspectTextMatchesResult, true);
|
|
assert.equal(result.payload.validation.checks.listTextMatchesResult, true);
|
|
assert.equal(calls[1], "http://web.test/v1/agent/conversations?projectId=prj_v02_code_agent&limit=100");
|
|
assert.deepEqual(result.payload.validation.failures, []);
|
|
});
|
|
|
|
test("hwlab-cli client session final-response preserves inspect auth failure visibility", async () => {
|
|
const fetchImpl = async (url: string | URL) => {
|
|
if (String(url).endsWith("/v1/agent/conversations/cnv_issue834")) {
|
|
return new Response(JSON.stringify({ ok: false, status: 401, error: { code: "auth_required", message: "Authentication is required" } }), { status: 401 });
|
|
}
|
|
return new Response(JSON.stringify({ ok: false, error: { code: "unexpected_route" } }), { status: 404 });
|
|
};
|
|
|
|
const result = await runHwlabCli(["client", "session", "final-response", "cnv_issue834", "--base-url", "http://web.test", "--cookie", "hwlab_session=session-a"], { fetchImpl });
|
|
|
|
assert.equal(result.exitCode, 1);
|
|
assert.equal(result.payload.action, "client.session.final-response");
|
|
assert.equal(result.payload.httpStatus, 401);
|
|
assert.equal(result.payload.validation.failures[0], "inspect-conversation-missing");
|
|
assert.equal(result.payload.validation.error.code, "auth_required");
|
|
assert.equal(result.payload.error?.code, undefined);
|
|
});
|
|
|
|
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 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_hwpod_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_hwpod_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_hwpod_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_hwpod_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_hwpod_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_hwpod_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_hwpod_workbench");
|
|
});
|
|
|
|
test("hwlab-cli client agent send resolves explicit session conversation before using workspace conversation", async () => {
|
|
const calls: any[] = [];
|
|
const result = await runHwlabCli([
|
|
"client",
|
|
"agent",
|
|
"send",
|
|
"--base-url",
|
|
"http://web.test",
|
|
"--cookie",
|
|
"hwlab_session=session-a",
|
|
"--message",
|
|
"send through explicit session",
|
|
"--trace-id",
|
|
"trc_explicit_session",
|
|
"--session-id",
|
|
"ses_manual_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_hwpod_workbench")) {
|
|
return new Response(JSON.stringify({
|
|
ok: true,
|
|
workspace: {
|
|
workspaceId: "wsp_stale_selection",
|
|
revision: 12,
|
|
selectedConversationId: "cnv_stale_workspace",
|
|
selectedAgentSessionId: "ses_stale_workspace",
|
|
selectedConversation: {
|
|
conversationId: "cnv_stale_workspace",
|
|
sessionId: "ses_stale_workspace",
|
|
threadId: "thread-stale-workspace"
|
|
},
|
|
providerProfile: "deepseek",
|
|
workspace: { sessionStatus: "idle", threadId: "thread-stale-workspace", providerProfile: "deepseek" }
|
|
}
|
|
}), { status: 200 });
|
|
}
|
|
if (String(url).endsWith("/v1/agent/sessions/ses_manual_current")) {
|
|
return new Response(JSON.stringify({
|
|
ok: true,
|
|
status: "found",
|
|
session: {
|
|
sessionId: "ses_manual_current",
|
|
conversationId: "cnv_manual_current",
|
|
threadId: "thread-manual-current",
|
|
status: "idle",
|
|
usable: true,
|
|
providerProfile: "minimax-m3"
|
|
}
|
|
}), { status: 200 });
|
|
}
|
|
if (String(url).endsWith("/v1/agent/chat")) {
|
|
return new Response(JSON.stringify({ accepted: true, status: "running", traceId: "trc_explicit_session", resultUrl: "/v1/agent/chat/result/trc_explicit_session" }), { status: 202 });
|
|
}
|
|
return new Response(JSON.stringify({ status: "completed", traceId: "trc_explicit_session", conversationId: "cnv_manual_current", sessionId: "ses_manual_current", reply: { role: "assistant", content: "explicit session ok" } }), { status: 200 });
|
|
},
|
|
sleep: async () => {}
|
|
});
|
|
|
|
assert.equal(result.exitCode, 0);
|
|
assert.equal(calls[0].url, "http://web.test/v1/workbench/workspace?projectId=prj_hwpod_workbench");
|
|
assert.equal(calls[1].url, "http://web.test/v1/agent/sessions/ses_manual_current");
|
|
assert.equal(calls[2].url, "http://web.test/v1/agent/chat");
|
|
assert.equal(calls[2].body.conversationId, "cnv_manual_current");
|
|
assert.equal(calls[2].body.sessionId, "ses_manual_current");
|
|
assert.equal(calls[2].body.threadId, "thread-manual-current");
|
|
assert.equal(calls[2].body.providerProfile, "minimax-m3");
|
|
assert.equal(calls[2].body.workspaceId, "wsp_stale_selection");
|
|
assert.equal(result.payload.continuation.conversationId, "cnv_manual_current");
|
|
assert.equal(result.payload.continuation.sessionId, "ses_manual_current");
|
|
assert.equal(result.payload.continuation.threadId, "thread-manual-current");
|
|
});
|
|
|
|
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_hwpod_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_hwpod_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", turnUrl: "/v1/agent/turns/trc_wait", resultUrl: "/v1/agent/chat/result/trc_wait" }), { status: 202 });
|
|
}
|
|
if (String(url).endsWith("/v1/agent/turns/trc_wait")) {
|
|
return new Response(JSON.stringify({ ok: true, terminal: true, status: "completed", traceId: "trc_wait", conversationId: "cnv_wait", reply: { role: "assistant", content: "hi" } }), { status: 200 });
|
|
}
|
|
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({ ok: false, error: { code: "unexpected_test_route" } }), { status: 404 });
|
|
},
|
|
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/turns/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_hwpod_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/sessions/ses_new_workspace")) {
|
|
return new Response(JSON.stringify({
|
|
ok: true,
|
|
status: "found",
|
|
session: {
|
|
sessionId: "ses_new_workspace",
|
|
conversationId: "cnv_new_workspace",
|
|
threadId: "thread-new-workspace",
|
|
status: "idle",
|
|
usable: true,
|
|
providerProfile: "deepseek"
|
|
}
|
|
}), { status: 200 });
|
|
}
|
|
if (String(url).endsWith("/v1/agent/chat")) {
|
|
return new Response(JSON.stringify({ accepted: true, status: "running", traceId: "trc_new_workspace", turnUrl: "/v1/agent/turns/trc_new_workspace", resultUrl: "/v1/agent/chat/result/trc_new_workspace" }), { status: 202 });
|
|
}
|
|
if (String(url).endsWith("/v1/agent/turns/trc_new_workspace")) {
|
|
return new Response(JSON.stringify({ ok: true, terminal: true, 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[2].url, "http://web.test/v1/agent/chat");
|
|
assert.equal(calls[2].body.conversationId, "cnv_new_workspace");
|
|
assert.equal(calls[2].body.sessionId, "ses_new_workspace");
|
|
assert.equal(Object.hasOwn(calls[2].body, "threadId"), true);
|
|
assert.equal(calls[4].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_hwpod_workbench")) {
|
|
const restored = calls.filter((call) => String(call.url).endsWith("/v1/workbench/workspace?projectId=prj_hwpod_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/sessions/ses_restore_workspace")) {
|
|
return new Response(JSON.stringify({
|
|
ok: true,
|
|
status: "found",
|
|
session: {
|
|
sessionId: "ses_restore_workspace",
|
|
conversationId: "cnv_restore_workspace",
|
|
threadId: "thread-restore-workspace",
|
|
status: "idle",
|
|
usable: true,
|
|
providerProfile: "deepseek"
|
|
}
|
|
}), { status: 200 });
|
|
}
|
|
if (String(url).endsWith("/v1/agent/chat")) {
|
|
return new Response(JSON.stringify({ accepted: true, status: "running", traceId: "trc_restore_workspace", turnUrl: "/v1/agent/turns/trc_restore_workspace", resultUrl: "/v1/agent/chat/result/trc_restore_workspace" }), { status: 202 });
|
|
}
|
|
if (String(url).endsWith("/v1/agent/turns/trc_restore_workspace")) {
|
|
return new Response(JSON.stringify({ ok: true, terminal: true, 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,
|
|
workspace: {
|
|
workspaceId: "wsp_restore_workspace",
|
|
revision: 12,
|
|
selectedConversationId: "cnv_restore_workspace",
|
|
selectedAgentSessionId: "ses_restore_workspace",
|
|
selectedConversation: {
|
|
conversationId: "cnv_previous_workspace",
|
|
sessionId: "ses_previous_workspace",
|
|
threadId: "thread-previous-workspace"
|
|
},
|
|
workspace: {
|
|
sessionStatus: "completed",
|
|
threadId: "thread-restore-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[2].url, "http://web.test/v1/agent/chat");
|
|
assert.equal(calls[2].body.sessionId, "ses_restore_workspace");
|
|
assert.equal(Object.hasOwn(calls[2].body, "threadId"), true);
|
|
assert.equal(calls[4].url, "http://web.test/v1/workbench/workspace/wsp_restore_workspace");
|
|
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");
|
|
assert.equal(result.payload.workspace.selectedConversation.conversationId, "cnv_restore_workspace");
|
|
assert.equal(result.payload.workspace.selectedConversation.sessionId, "ses_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");
|
|
assert.equal(session.workspace.selectedConversation.conversationId, "cnv_restore_workspace");
|
|
assert.equal(session.workspace.selectedConversation.sessionId, "ses_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_hwpod_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/sessions/ses_stale_restore")) {
|
|
return new Response(JSON.stringify({
|
|
ok: true,
|
|
status: "found",
|
|
session: {
|
|
sessionId: "ses_stale_restore",
|
|
conversationId: "cnv_stale_restore",
|
|
threadId: "thread-stale-restore",
|
|
status: "idle",
|
|
usable: true,
|
|
providerProfile: "deepseek"
|
|
}
|
|
}), { status: 200 });
|
|
}
|
|
if (String(url).endsWith("/v1/agent/sessions/ses_web_continue")) {
|
|
return new Response(JSON.stringify({
|
|
ok: true,
|
|
status: "found",
|
|
session: {
|
|
sessionId: "ses_web_continue",
|
|
conversationId: "cnv_web_continue",
|
|
threadId: "thread-web-continue",
|
|
status: "idle",
|
|
usable: true,
|
|
providerProfile: "deepseek"
|
|
}
|
|
}), { status: 200 });
|
|
}
|
|
if (String(url).endsWith("/v1/agent/chat")) {
|
|
return new Response(JSON.stringify({ accepted: true, status: "running", traceId: "trc_stale_restore", turnUrl: "/v1/agent/turns/trc_stale_restore", resultUrl: "/v1/agent/chat/result/trc_stale_restore" }), { status: 202 });
|
|
}
|
|
if (String(url).endsWith("/v1/agent/turns/trc_stale_restore")) {
|
|
return new Response(JSON.stringify({ ok: true, terminal: true, 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[5].url, "http://web.test/v1/workbench/workspace?projectId=prj_hwpod_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"), true);
|
|
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"), true);
|
|
|
|
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"), true);
|
|
});
|
|
|
|
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"), "看看有什么 HWPOD 能用?", "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_hwpod_workbench");
|
|
assert.equal(calls[2].url, "http://web.test/v1/agent/chat");
|
|
assert.equal(calls[2].body.message, "看看有什么 HWPOD 能用?");
|
|
assert.equal(calls[2].body.conversationId, "cnv_web_continue");
|
|
assert.equal(calls[2].body.sessionId, "ses_web_continue");
|
|
assert.equal(calls[2].body.threadId, "019e8078-db67-7750-a5d9-1a99f3abd445");
|
|
assert.equal(calls[2].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 HWPOD 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_hwpod_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_hwpod_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/traces/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_hwpod_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 status ignores stale workspace activeTrace after selected conversation completed", async () => {
|
|
const result = await runHwlabCli([
|
|
"client",
|
|
"agent",
|
|
"composer",
|
|
"status",
|
|
"--base-url",
|
|
"http://web.test",
|
|
"--cookie",
|
|
"hwlab_session=session-a"
|
|
], {
|
|
fetchImpl: async () => new Response(JSON.stringify({
|
|
ok: true,
|
|
workspace: {
|
|
workspaceId: "wsp_composer",
|
|
revision: 10,
|
|
selectedConversationId: "cnv_completed",
|
|
selectedAgentSessionId: "ses_completed",
|
|
activeTraceId: "trc_stale_running",
|
|
workspace: {
|
|
activeTraceId: "trc_stale_running",
|
|
threadId: "thread-composer",
|
|
sessionStatus: "running",
|
|
messages: [{ role: "agent", status: "completed", traceId: "trc_completed", conversationId: "cnv_completed" }]
|
|
}
|
|
}
|
|
}), { status: 200 })
|
|
});
|
|
|
|
assert.equal(result.exitCode, 0);
|
|
assert.equal(result.payload.composer.locked, false);
|
|
assert.equal(result.payload.composer.disabled, false);
|
|
assert.equal(result.payload.composer.submitMode, "turn");
|
|
assert.equal(result.payload.composer.route, "/v1/agent/chat");
|
|
assert.equal(result.payload.composer.targetTraceId, null);
|
|
assert.equal(result.payload.composer.reason, "terminal-turn");
|
|
});
|
|
|
|
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_hwpod_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: "看看有什么 HWPOD 能用?" },
|
|
{ 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: "当前有两个 HWPOD 可用。" }
|
|
]
|
|
}), { status: 200 })
|
|
});
|
|
|
|
assert.equal(result.exitCode, 0);
|
|
assert.equal(result.payload.body.render, "web");
|
|
assert.equal(result.payload.body.renderer, "tools/src/hwlab-cli/trace-renderer: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("request accepted"), false);
|
|
assert.equal(JSON.stringify(result.payload.body.rows).includes("request:accepted"), false);
|
|
assert.equal(JSON.stringify(result.payload.body.rows).includes("session:reused"), false);
|
|
assert.equal(result.payload.body.rows[0].body, "当前有两个 HWPOD 可用。");
|
|
assert.equal(result.payload.traceStatus, "completed");
|
|
assert.equal(result.payload.rendered.traceStatus, "completed");
|
|
assert.equal(result.payload.rendered.render, "web");
|
|
assert.equal(result.payload.rendered.renderedRowCount, result.payload.body.renderedRowCount);
|
|
assert.deepEqual(result.payload.rendered.rows, result.payload.body.rows);
|
|
assert.deepEqual(result.payload.data, result.payload.rendered);
|
|
});
|
|
|
|
test("hwlab-cli client agent trace exposes missing Web render state without a body-path trap", async () => {
|
|
const result = await runHwlabCli(["client", "agent", "trace", "trc_missing", "--base-url", "http://web.test", "--cookie", "hwlab_session=session-a", "--render", "web"], {
|
|
fetchImpl: async () => new Response(JSON.stringify({
|
|
status: "missing",
|
|
traceId: "trc_missing",
|
|
eventCount: 0,
|
|
events: []
|
|
}), { status: 200 })
|
|
});
|
|
|
|
assert.equal(result.exitCode, 0);
|
|
assert.equal(result.payload.status, "succeeded");
|
|
assert.equal(result.payload.traceStatus, "missing");
|
|
assert.equal(result.payload.body.status, "missing");
|
|
assert.equal(result.payload.rendered.traceStatus, "missing");
|
|
assert.equal(result.payload.rendered.sourceEventCount, 0);
|
|
assert.equal(result.payload.rendered.renderedRowCount, 0);
|
|
assert.deepEqual(result.payload.rendered.rows, []);
|
|
assert.deepEqual(result.payload.data, result.payload.rendered);
|
|
});
|
|
|
|
test("hwlab-cli client agent trace renders AgentRun terminal evidence for expired traces", async () => {
|
|
const finalText = "历史 trace 已过期,但 final response 仍可审计。";
|
|
const result = await runHwlabCli(["client", "agent", "trace", "trc_expired", "--base-url", "http://web.test", "--cookie", "hwlab_session=session-a", "--render", "web"], {
|
|
fetchImpl: async () => new Response(JSON.stringify({
|
|
ok: true,
|
|
status: "expired",
|
|
traceStatus: "expired",
|
|
traceId: "trc_expired",
|
|
eventCount: 0,
|
|
events: [],
|
|
conversationId: "cnv_expired",
|
|
sessionId: "ses_expired",
|
|
threadId: "thread-expired",
|
|
retention: { traceStatus: "expired", liveTraceStore: "expired-or-evicted" },
|
|
agentRun: { runId: "run_expired", commandId: "cmd_expired", valuesPrinted: false },
|
|
finalResponse: { text: finalText, textChars: finalText.length, role: "assistant", status: "completed", source: "agentrun-command-result", valuesPrinted: false },
|
|
traceSummary: { source: "agentrun-command-result", sourceEventCount: 26, terminalStatus: "completed", valuesPrinted: false },
|
|
terminalEvidence: {
|
|
available: true,
|
|
source: "agentrun-command-result",
|
|
conversationId: "cnv_expired",
|
|
sessionId: "ses_expired",
|
|
threadId: "thread-expired",
|
|
agentRun: { runId: "run_expired", commandId: "cmd_expired", valuesPrinted: false },
|
|
finalResponse: { text: finalText, textChars: finalText.length, role: "assistant", status: "completed", valuesPrinted: false },
|
|
traceSummary: { source: "agentrun-command-result", sourceEventCount: 26, terminalStatus: "completed", valuesPrinted: false },
|
|
valuesPrinted: false
|
|
}
|
|
}), { status: 200 })
|
|
});
|
|
|
|
assert.equal(result.exitCode, 0);
|
|
assert.equal(result.payload.status, "succeeded");
|
|
assert.equal(result.payload.traceStatus, "expired");
|
|
assert.equal(result.payload.body.traceStatus, "expired");
|
|
assert.equal(result.payload.body.assistantText, finalText);
|
|
assert.equal(result.payload.rendered.traceStatus, "expired");
|
|
assert.equal(result.payload.rendered.sourceEventCount, 0);
|
|
assert.equal(result.payload.rendered.renderedRowCount, 1);
|
|
assert.match(result.payload.rendered.rows[0].header, /历史 trace 已过期/u);
|
|
assert.match(result.payload.rendered.rows[0].body, /final response 仍可审计/u);
|
|
assert.match(result.payload.rendered.rows[0].body, /run_expired/u);
|
|
assert.equal(result.payload.rendered.terminalEvidence.available, true);
|
|
assert.equal(result.payload.rendered.traceSummary.sourceEventCount, 26);
|
|
assert.deepEqual(result.payload.data, result.payload.rendered);
|
|
});
|
|
|
|
test("hwlab-cli Web trace render does not treat AgentRun result-ready as final assistant text", async () => {
|
|
const result = await runHwlabCli(["client", "agent", "trace", "trc_result_ready", "--base-url", "http://web.test", "--cookie", "hwlab_session=session-a", "--render", "web"], {
|
|
fetchImpl: async () => new Response(JSON.stringify({
|
|
status: "completed",
|
|
traceId: "trc_result_ready",
|
|
events: [
|
|
{ traceId: "trc_result_ready", seq: 1, label: "agentrun:request:accepted", status: "running", type: "request", createdAt: "2026-06-01T13:00:00.000Z" },
|
|
{ traceId: "trc_result_ready", seq: 2, label: "agentrun:result:completed", type: "result", status: "completed", terminal: true, message: "AgentRun result is ready for HWLAB short-connection polling.", createdAt: "2026-06-01T13:00:02.000Z" }
|
|
]
|
|
}), { status: 200 })
|
|
});
|
|
|
|
assert.equal(result.exitCode, 0);
|
|
const text = JSON.stringify(result.payload.body.rows);
|
|
assert.equal(text.includes("助手最终消息"), false);
|
|
assert.equal(text.includes("助手消息"), false);
|
|
assert.match(text, /轮次完成/u);
|
|
assert.match(text, /总耗时 00:00:02/u);
|
|
assert.equal(text.includes("总耗时 00:00:00"), false);
|
|
assert.equal(text.includes("AgentRun result is ready for HWLAB short-connection polling"), 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 inspect --dry-run'", exitCode: 0, stdoutSummary: '{"ok":true,"action":"hwpod-cli.plan"}', 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: "当前可见 HWPOD。", 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 inspect --dry-run/u);
|
|
assert.match(text, /hwpod-cli\.plan/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, /当前可见 HWPOD/u);
|
|
});
|
|
|
|
test("hwlab-cli Web trace render keeps full tool details with --full", async () => {
|
|
const longStdout = `first line\n${"X".repeat(1500)}\nlast line`;
|
|
const command = "/bin/sh -lc 'cat > /tmp/main_patch.txt << '\''PATCHMAIN'\''\n*** Begin Patch\n*** Update File: projects/01_baseline/User/main.c\n@@\n+d601_arm2d_demo_task();\n*** End Patch\nPATCHMAIN\nhwpod workspace apply-patch --spec .hwlab/hwpod-spec.yaml --reason demo'";
|
|
const result = await runHwlabCli(["client", "agent", "trace", "trc_render_full", "--base-url", "http://web.test", "--cookie", "hwlab_session=session-a", "--render", "web", "--full"], {
|
|
fetchImpl: async () => new Response(JSON.stringify({
|
|
status: "completed",
|
|
traceId: "trc_render_full",
|
|
events: [
|
|
{ traceId: "trc_render_full", seq: 1, label: "item/commandExecution:completed", type: "tool_call", toolName: "commandExecution", status: "completed", itemId: "call_full", command, exitCode: 0, stdoutSummary: longStdout, createdAt: "2026-06-01T13:00:02.000Z" }
|
|
]
|
|
}), { status: 200 })
|
|
});
|
|
|
|
assert.equal(result.exitCode, 0);
|
|
const row = result.payload.body.rows[0];
|
|
assert.match(row.body, /cat > \/tmp\/main_patch\.txt << '+PATCHMAIN'+/u);
|
|
assert.match(row.body, /\n\*\*\* Begin Patch/u);
|
|
assert.match(row.body, /stdout:\nfirst line\nX{1500}\nlast line/u);
|
|
assert.match(row.body, /exitCode=0/u);
|
|
assert.doesNotMatch(row.body, /last line\.\.\./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: "我先检查当前 HWPOD 列表。", 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 个 HWPOD 可用。", 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, /我先检查当前 HWPOD 列表/u);
|
|
assert.match(text, /当前有 2 个 HWPOD 可用/u);
|
|
assert.match(text, /助手最终消息/u);
|
|
assert.match(text, /总耗时 00:00:03/u);
|
|
assert.equal(text.includes("agentrun:request:accepted"), false);
|
|
assert.equal(text.includes("总耗时 00:00:00"), false);
|
|
});
|
|
|
|
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 does not auto login with Web session credentials", 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 });
|
|
return new Response(JSON.stringify({ ok: false, error: { code: "auth_required" } }), { status: 401 });
|
|
},
|
|
now: () => "2026-05-31T07:20:00.000Z"
|
|
});
|
|
|
|
assert.equal(result.exitCode, 1);
|
|
assert.deepEqual(calls.map((call) => call.url), [
|
|
"http://web.test/v1/agent/turns/trc_protected",
|
|
"http://web.test/v1/agent/traces/trc_protected"
|
|
]);
|
|
assert.equal(calls[0].init.headers.cookie, undefined);
|
|
assert.equal(calls[0].init.headers.authorization, undefined);
|
|
assert.equal(result.payload.auth.requiredAuthMethod, "api-key");
|
|
assert.equal(result.payload.authDiagnosis.code, "api_key_required");
|
|
assert.equal(JSON.stringify(result.payload).includes("secret-password"), false);
|
|
});
|
|
|
|
test("hwlab-cli client agent result ignores stored Web session by default after 401", 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 });
|
|
return new Response(JSON.stringify({ error: { code: "auth_required" } }), { status: 401 });
|
|
},
|
|
sleep: async () => {},
|
|
now: () => "2026-05-31T07:21:00.000Z"
|
|
});
|
|
|
|
assert.equal(result.exitCode, 1);
|
|
assert.deepEqual(calls.map((call) => call.url), [
|
|
"http://web.test/v1/agent/turns/trc_refresh"
|
|
]);
|
|
assert.equal(calls[0].init.headers.cookie, undefined);
|
|
assert.equal(result.payload.auth.requiredAuthMethod, "api-key");
|
|
assert.equal(result.payload.auth.webSessionIgnoredReason, "cli_requires_api_key");
|
|
assert.equal(result.payload.auth.localSession.usable, true);
|
|
assert.equal(result.payload.action, "client.agent.result");
|
|
assert.equal(result.payload.authDiagnosis.code, "api_key_required");
|
|
});
|
|
|
|
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"], {
|
|
env: { HWLAB_API_KEY: "hwl_live_owner_test_key" },
|
|
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.auth.authMethod, "api-key");
|
|
assert.equal(result.payload.authDiagnosis.cookieSource, null);
|
|
assert.match(result.payload.authDiagnosis.message, /无权访问/u);
|
|
assert.equal(result.payload.request.url, "http://web.test/v1/agent/traces/trc_forbidden");
|
|
assert.match(result.payload.authDiagnosis.nextCommands[1], /client auth whoami/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,
|
|
env: {},
|
|
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.auth.requiredAuthMethod, "api-key");
|
|
assert.equal(result.payload.authDiagnosis.code, "api_key_required");
|
|
assert.equal(result.payload.authDiagnosis.localSession.ignoredReason, "state_file_missing");
|
|
assert.match(result.payload.authDiagnosis.nextCommands[0], /HWLAB_API_KEY/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"
|
|
], {
|
|
env: { HWLAB_API_KEY: "hwl_live_owner_test_key" },
|
|
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.authorization, "Bearer hwl_live_owner_test_key");
|
|
assert.equal(calls[0].init.headers.cookie, undefined);
|
|
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/traces/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")));
|
|
|
|
});
|
|
|
|
test("hwlab-cli client harness audit treats standard hwpod flow as clean", async () => {
|
|
const audit = await runHwlabCli([
|
|
"client",
|
|
"harness",
|
|
"audit",
|
|
"trc_hwpod",
|
|
"--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 inspect --dry-run" },
|
|
{ type: "tool_call", command: "hwpod workspace ls ." }
|
|
]
|
|
}), { status: 200 })
|
|
});
|
|
assert.equal(audit.exitCode, 0);
|
|
assert.equal(audit.payload.status, "clean");
|
|
assert.equal(audit.payload.summary.hasHwpod, 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"], {
|
|
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/live-builds"), true);
|
|
assert.equal(seen.includes("http://web.test/v1/skills"), 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",
|
|
"--trace-id",
|
|
"trc_request"
|
|
], {
|
|
env: { HWLAB_API_KEY: "hwl_live_admin_test_key" },
|
|
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.authorization, "Bearer hwl_live_admin_test_key");
|
|
assert.equal(calls[0].init.headers.cookie, undefined);
|
|
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 user query wrappers expose typed redacted summaries", async () => {
|
|
const calls: any[] = [];
|
|
const fetchImpl = async (url: string | URL, init: any = {}) => {
|
|
calls.push({ url: String(url), init });
|
|
if (String(url).endsWith("/v1/users/me")) {
|
|
return new Response(JSON.stringify({ ok: true, authenticated: true, actor: { id: "usr_test", username: "inner-test", role: "user", status: "active" }, authMethod: "user-billing-api-key", session: { id: "key_test", keyPrefix: "hwl_live_Zci", key: "hwl_live_should_not_print" } }), { status: 200 });
|
|
}
|
|
if (String(url).endsWith("/v1/access/status")) {
|
|
return new Response(JSON.stringify({ ok: true, authenticated: true, actor: { id: "usr_test", username: "inner-test", role: "user" }, authMethod: "user-billing-api-key", permissions: ["code_agent"], routes: { currentUser: "/v1/users/me" } }), { status: 200 });
|
|
}
|
|
if (String(url).endsWith("/v1/api-keys")) {
|
|
return new Response(JSON.stringify({ ok: true, count: 1, items: [{ id: "key_test", name: "fixed", keyPrefix: "hwl_live_Zci", key: "hwl_live_raw_secret", scopes: ["api"], status: "active", valuesRedacted: true }] }), { status: 200 });
|
|
}
|
|
if (String(url).endsWith("/v1/billing/summary?limit=7")) {
|
|
return new Response(JSON.stringify({ ok: true, credits: { balance: 99, reserved: 0 }, ledger: { count: 1, items: [{ id: "led_test", kind: "usage", reason: "hwlab-code-agent", deltaCredits: -1, metadata: { traceId: "trc_test", sessionId: "ses_test" } }] }, usage: { recordCount: 1, byService: [{ service: "hwlab-code-agent", credits: 1 }] } }), { status: 200 });
|
|
}
|
|
if (String(url).endsWith("/v1/usage/summary")) {
|
|
return new Response(JSON.stringify({ ok: true, usage: { recordCount: 1, byService: [{ service: "hwlab-code-agent", credits: 1 }], lastUsedAt: "2026-06-15T00:00:00Z" }, records: [{ id: "usage_test", service: "hwlab-code-agent", credits: 1, metadata: { traceId: "trc_test" } }] }), { status: 200 });
|
|
}
|
|
return new Response(JSON.stringify({ ok: false, error: { code: "unexpected_test_route" } }), { status: 404 });
|
|
};
|
|
const env = { HWLAB_API_KEY: "hwl_live_user_test_key" };
|
|
|
|
const account = await runHwlabCli(["client", "account", "status", "--base-url", "http://web.test"], { env, fetchImpl });
|
|
const keys = await runHwlabCli(["client", "api-keys", "list", "--base-url", "http://web.test"], { env, fetchImpl });
|
|
const billing = await runHwlabCli(["client", "billing", "summary", "--limit", "7", "--base-url", "http://web.test"], { env, fetchImpl });
|
|
const usage = await runHwlabCli(["client", "usage", "summary", "--base-url", "http://web.test"], { env, fetchImpl });
|
|
|
|
assert.equal(account.exitCode, 0);
|
|
assert.equal(account.payload.action, "client.account.status");
|
|
assert.equal(account.payload.identity.actor.username, "inner-test");
|
|
assert.equal(account.payload.access.permissions[0], "code_agent");
|
|
assert.equal(keys.exitCode, 0);
|
|
assert.equal(keys.payload.action, "client.api-keys.list");
|
|
assert.equal(keys.payload.body.items[0].prefix, "hwl_live_Zci");
|
|
assert.equal(billing.exitCode, 0);
|
|
assert.equal(billing.payload.route.path, "/v1/billing/summary?limit=7");
|
|
assert.equal(billing.payload.body.ledger.items[0].traceId, "trc_test");
|
|
assert.equal(usage.exitCode, 0);
|
|
assert.equal(usage.payload.body.usage.recordCount, 1);
|
|
assert.equal(calls.every((call) => call.init.headers.authorization === "Bearer hwl_live_user_test_key"), true);
|
|
assert.equal(calls.every((call) => call.init.headers.cookie === undefined), true);
|
|
assert.equal(JSON.stringify([account.payload, keys.payload, billing.payload, usage.payload]).includes("hwl_live_raw_secret"), false);
|
|
assert.equal(JSON.stringify([account.payload, keys.payload, billing.payload, usage.payload]).includes("hwl_live_should_not_print"), false);
|
|
});
|
|
|
|
test("hwlab-cli client request compacts array responses by default", async () => {
|
|
const result = await runHwlabCli([
|
|
"client",
|
|
"request",
|
|
"GET",
|
|
"/v1/skills",
|
|
"--base-url",
|
|
"http://web.test",
|
|
"--cookie",
|
|
"hwlab_session=session-a"
|
|
], {
|
|
fetchImpl: async () => new Response(JSON.stringify([{ name: "hwpod-cli", status: "ready" }, { name: "hwpod-ctl", status: "ready" }]), { status: 200 })
|
|
});
|
|
|
|
assert.equal(result.exitCode, 0);
|
|
assert.equal(result.payload.body.itemCount, 2);
|
|
assert.equal(result.payload.body.items[0].name, "hwpod-cli");
|
|
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",
|
|
"--trace-id",
|
|
"trc_rpc",
|
|
"--id",
|
|
"req_rpc",
|
|
"--params-json",
|
|
"{}"
|
|
], {
|
|
env: { HWLAB_API_KEY: "hwl_live_admin_test_key" },
|
|
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.authorization, "Bearer hwl_live_admin_test_key");
|
|
assert.equal(calls[0].init.headers.cookie, undefined);
|
|
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");
|
|
});
|
|
|
|
test("hwlab-cli client agent send with --session-id and --conversation-id still inherits explicit session providerProfile (#792)", async () => {
|
|
const calls: any[] = [];
|
|
const result = await runHwlabCli([
|
|
"client",
|
|
"agent",
|
|
"send",
|
|
"--base-url",
|
|
"http://web.test",
|
|
"--cookie",
|
|
"hwlab_session=session-a",
|
|
"--message",
|
|
"send with explicit session + conversation",
|
|
"--trace-id",
|
|
"trc_explicit_session_with_conv",
|
|
"--session-id",
|
|
"ses_explicit",
|
|
"--conversation-id",
|
|
"cnv_explicit"
|
|
], {
|
|
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_hwpod_workbench")) {
|
|
return new Response(JSON.stringify({
|
|
ok: true,
|
|
workspace: {
|
|
workspaceId: "wsp_other",
|
|
revision: 4,
|
|
providerProfile: "deepseek",
|
|
workspace: { sessionStatus: "idle", providerProfile: "deepseek" }
|
|
}
|
|
}), { status: 200 });
|
|
}
|
|
if (String(url).endsWith("/v1/agent/sessions/ses_explicit")) {
|
|
return new Response(JSON.stringify({
|
|
ok: true,
|
|
status: "found",
|
|
session: {
|
|
sessionId: "ses_explicit",
|
|
conversationId: "cnv_explicit",
|
|
threadId: "thread-explicit",
|
|
status: "idle",
|
|
usable: true,
|
|
providerProfile: "minimax-m3"
|
|
}
|
|
}), { status: 200 });
|
|
}
|
|
if (String(url).endsWith("/v1/agent/chat")) {
|
|
return new Response(JSON.stringify({ accepted: true, status: "running", traceId: "trc_explicit_session_with_conv", resultUrl: "/v1/agent/chat/result/trc_explicit_session_with_conv" }), { status: 202 });
|
|
}
|
|
return new Response(JSON.stringify({ status: "completed", traceId: "trc_explicit_session_with_conv", conversationId: "cnv_explicit", sessionId: "ses_explicit", reply: { role: "assistant", content: "ok" } }), { status: 200 });
|
|
},
|
|
sleep: async () => {}
|
|
});
|
|
|
|
assert.equal(result.exitCode, 0);
|
|
const sessionLookup = calls.find((c) => String(c.url).endsWith("/v1/agent/sessions/ses_explicit"));
|
|
assert.ok(sessionLookup, "CLI must look up explicit session when --session-id is given alongside --conversation-id (without --provider-profile)");
|
|
const chatCall = calls.find((c) => String(c.url).endsWith("/v1/agent/chat"));
|
|
assert.ok(chatCall);
|
|
assert.equal(chatCall.body.providerProfile, "minimax-m3");
|
|
assert.equal(chatCall.body.sessionId, "ses_explicit");
|
|
assert.equal(chatCall.body.conversationId, "cnv_explicit");
|
|
assert.equal(chatCall.body.threadId, "thread-explicit");
|
|
});
|
|
|
|
test("hwlab-cli client agent send fails when explicit session conversationId does not match requested conversationId (#792)", async () => {
|
|
const calls: any[] = [];
|
|
const result = await runHwlabCli([
|
|
"client",
|
|
"agent",
|
|
"send",
|
|
"--base-url",
|
|
"http://web.test",
|
|
"--cookie",
|
|
"hwlab_session=session-a",
|
|
"--message",
|
|
"wrong conversation",
|
|
"--trace-id",
|
|
"trc_mismatch",
|
|
"--session-id",
|
|
"ses_a",
|
|
"--conversation-id",
|
|
"cnv_b"
|
|
], {
|
|
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_hwpod_workbench")) {
|
|
return new Response(JSON.stringify({ ok: true, workspace: { workspaceId: "wsp_test", revision: 1, workspace: {} } }), { status: 200 });
|
|
}
|
|
if (String(url).endsWith("/v1/agent/sessions/ses_a")) {
|
|
return new Response(JSON.stringify({
|
|
ok: true,
|
|
status: "found",
|
|
session: {
|
|
sessionId: "ses_a",
|
|
conversationId: "cnv_a",
|
|
threadId: "thread-a",
|
|
status: "idle",
|
|
usable: true,
|
|
providerProfile: "minimax-m3"
|
|
}
|
|
}), { status: 200 });
|
|
}
|
|
if (String(url).endsWith("/v1/agent/chat")) {
|
|
return new Response(JSON.stringify({ accepted: true }), { status: 202 });
|
|
}
|
|
return new Response(JSON.stringify({ status: "completed" }), { status: 200 });
|
|
}
|
|
});
|
|
|
|
assert.equal(result.exitCode, 1);
|
|
assert.equal(result.payload.error.code, "session_conversation_mismatch");
|
|
assert.equal(result.payload.error.details.sessionId, "ses_a");
|
|
assert.equal(result.payload.error.details.requestedConversationId, "cnv_b");
|
|
assert.equal(result.payload.error.details.sessionConversationId, "cnv_a");
|
|
const chatCall = calls.find((c) => String(c.url).endsWith("/v1/agent/chat"));
|
|
assert.equal(chatCall, undefined);
|
|
});
|