diff --git a/tools/hwlab-cli/client.test.ts b/tools/hwlab-cli/client.test.ts index 86c0e5c5..34c0be8d 100644 --- a/tools/hwlab-cli/client.test.ts +++ b/tools/hwlab-cli/client.test.ts @@ -457,6 +457,101 @@ test("hwlab-cli client agent send waits only when --wait is explicit", async () 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", + "--wait", + "--poll-interval-ms", + "1", + "--timeout-ms", + "1000" + ], { + cwd, + fetchImpl: async (url, init) => { + calls.push({ url: String(url), init, body: init?.body ? JSON.parse(String(init.body)) : null }); + if (String(url).endsWith("/v1/workbench/workspace?projectId=prj_device_pod_workbench")) { + return new Response(JSON.stringify({ + ok: true, + workspace: { + workspaceId: "wsp_new_workspace", + revision: 8, + selectedConversationId: "cnv_old_workspace", + selectedAgentSessionId: "ses_old_workspace", + selectedConversation: { + conversationId: "cnv_old_workspace", + sessionId: "ses_old_workspace", + threadId: "thread-old-workspace" + }, + workspace: { + sessionStatus: "idle", + threadId: "thread-old-workspace" + } + } + }), { status: 200 }); + } + if (String(url).endsWith("/v1/agent/chat")) { + return new Response(JSON.stringify({ accepted: true, status: "running", traceId: "trc_new_workspace", resultUrl: "/v1/agent/chat/result/trc_new_workspace" }), { status: 202 }); + } + if (String(url).endsWith("/v1/agent/chat/result/trc_new_workspace")) { + return new Response(JSON.stringify({ status: "completed", traceId: "trc_new_workspace", conversationId: "cnv_new_workspace", sessionId: "ses_new_workspace", reply: { role: "assistant", content: "new workspace ready" } }), { status: 200 }); + } + if (String(url).endsWith("/v1/workbench/workspace/wsp_new_workspace")) { + return new Response(JSON.stringify({ + ok: true, + workspace: { + workspaceId: "wsp_new_workspace", + revision: 9, + selectedConversationId: "cnv_new_workspace", + selectedAgentSessionId: "ses_new_workspace", + selectedConversation: { + conversationId: "cnv_new_workspace", + sessionId: "ses_new_workspace", + threadId: "thread-new-workspace" + }, + workspace: { + sessionStatus: "idle", + threadId: "thread-new-workspace" + } + } + }), { status: 200 }); + } + return new Response(JSON.stringify({ ok: false, error: { code: "unexpected_test_route" } }), { status: 404 }); + }, + sleep: async () => {} + }); + + assert.equal(result.exitCode, 0); + assert.equal(calls[1].url, "http://web.test/v1/agent/chat"); + assert.equal(calls[1].body.conversationId, "cnv_new_workspace"); + assert.equal(Object.hasOwn(calls[1].body, "sessionId"), false); + assert.equal(Object.hasOwn(calls[1].body, "threadId"), false); + assert.equal(calls[3].url, "http://web.test/v1/workbench/workspace/wsp_new_workspace"); + assert.equal(result.payload.workspace.selectedConversationId, "cnv_new_workspace"); + assert.equal(result.payload.workspace.selectedAgentSessionId, "ses_new_workspace"); + assert.equal(result.payload.workspace.threadId, "thread-new-workspace"); + assert.equal(result.payload.workspace.selectedConversation.conversationId, "cnv_new_workspace"); + + const session = JSON.parse(await readFile(path.join(cwd, ".state/hwlab-cli/session.json"), "utf8")); + assert.equal(session.workspace.selectedConversationId, "cnv_new_workspace"); + assert.equal(session.workspace.selectedAgentSessionId, "ses_new_workspace"); + assert.equal(session.workspace.threadId, "thread-new-workspace"); + assert.equal(session.workspace.selectedConversation.conversationId, "cnv_new_workspace"); +}); + test("hwlab-cli client agent send preserves Web continuation fields", async () => { const cwd = await mkdtemp(path.join(os.tmpdir(), "hwlab-cli-client-agent-continuation-")); await writeFile(path.join(cwd, "prompt.txt"), "看看有什么device-pod能用?", "utf8"); diff --git a/tools/src/hwlab-cli-lib.ts b/tools/src/hwlab-cli-lib.ts index 2cee693f..ca53e1c2 100644 --- a/tools/src/hwlab-cli-lib.ts +++ b/tools/src/hwlab-cli-lib.ts @@ -1203,7 +1203,7 @@ async function workbenchWatchCommand(context: any) { const afterRevision = numberOption(context.parsed.afterRevision) ?? (Number(session?.workspace?.revision ?? 0) || 0); const pathName = `/v1/workbench/workspace/${encodeURIComponent(workspaceId)}/events?afterRevision=${encodeURIComponent(String(afterRevision))}`; const response = await requestJson({ ...context, method: "GET", path: pathName }); - if (response.ok && response.body?.workspace) await saveWorkspaceState(context, response.body.workspace); + if (responseSucceeded(response) && response.body?.workspace) await saveWorkspaceState(context, response.body.workspace); return responsePayload("client.workbench.watch", response, context, { route: route("GET", pathName), body: responseBodyForCli(response.body, context.parsed) }); } @@ -1214,7 +1214,7 @@ async function workbenchResetCommand(context: any) { if (!workspaceId) throw cliError("workspace_restore_required", "workbench reset requires a restored workspace or --workspace-id", { nextCommand: "client workbench restore" }); const pathName = `/v1/workbench/workspace/${encodeURIComponent(workspaceId)}/reset`; const response = await requestJson({ ...context, method: "POST", path: pathName, body: { confirm: true, updatedByClient: "hwlab-cli" } }); - if (response.ok && response.body?.workspace) await saveWorkspaceState(context, response.body.workspace); + if (responseSucceeded(response) && response.body?.workspace) await saveWorkspaceState(context, response.body.workspace); return responsePayload("client.workbench.reset", response, context, { route: route("POST", pathName), body: responseBodyForCli(response.body, context.parsed) }); } @@ -1631,7 +1631,7 @@ function cookieFromResponse(response: any) { } function responsePayload(action: string, response: any, context: any, extra: Record = {}) { - const success = isHttpSuccess(response) && response.body?.ok !== false; + const success = responseSucceeded(response); return { ok: success, action, @@ -1648,6 +1648,10 @@ function responsePayload(action: string, response: any, context: any, extra: Rec }; } +function responseSucceeded(response: any) { + return isHttpSuccess(response) && response.body?.ok !== false; +} + function requestVisibility(response: any) { return pruneUndefined({ method: response.method, @@ -1803,7 +1807,7 @@ async function saveCompletedAgentWorkspaceState(context: any, resultBody: any, f body, timeoutMs: DEFAULT_TIMEOUT_MS }); - if (response.ok && response.body?.workspace) await saveWorkspaceState(context, response.body.workspace); + if (responseSucceeded(response) && response.body?.workspace) await saveWorkspaceState(context, response.body.workspace); } async function saveWorkspaceState(context: any, workspaceBody: any) {