From 6cd4cb292d6476ef842876ebb7366bf7b437b811 Mon Sep 17 00:00:00 2001 From: Codex Date: Wed, 3 Jun 2026 01:14:08 +0800 Subject: [PATCH] fix: restore hwlab cli workspace after completion --- tools/hwlab-cli/client.test.ts | 93 ++++++++++++++++++++++++++++++++++ tools/src/hwlab-cli-lib.ts | 7 ++- 2 files changed, 99 insertions(+), 1 deletion(-) diff --git a/tools/hwlab-cli/client.test.ts b/tools/hwlab-cli/client.test.ts index 34c0be8d..972938c0 100644 --- a/tools/hwlab-cli/client.test.ts +++ b/tools/hwlab-cli/client.test.ts @@ -552,6 +552,99 @@ test("hwlab-cli client agent send saves completed workspace response for new con 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", + "--wait", + "--poll-interval-ms", + "1", + "--timeout-ms", + "1000" + ], { + cwd, + fetchImpl: async (url, init) => { + calls.push({ url: String(url), init, body: init?.body ? JSON.parse(String(init.body)) : null }); + if (String(url).endsWith("/v1/workbench/workspace?projectId=prj_device_pod_workbench")) { + const restored = calls.filter((call) => String(call.url).endsWith("/v1/workbench/workspace?projectId=prj_device_pod_workbench")).length > 1; + return new Response(JSON.stringify({ + ok: true, + workspace: restored + ? { + workspaceId: "wsp_restore_workspace", + revision: 12, + selectedConversationId: "cnv_restore_workspace", + selectedAgentSessionId: "ses_restore_workspace", + selectedConversation: { + conversationId: "cnv_restore_workspace", + sessionId: "ses_restore_workspace", + threadId: "thread-restore-workspace" + }, + workspace: { + sessionStatus: "idle", + threadId: "thread-restore-workspace" + } + } + : { + workspaceId: "wsp_restore_workspace", + revision: 11, + selectedConversationId: "cnv_previous_workspace", + selectedAgentSessionId: "ses_previous_workspace", + selectedConversation: { + conversationId: "cnv_previous_workspace", + sessionId: "ses_previous_workspace", + threadId: "thread-previous-workspace" + }, + workspace: { + sessionStatus: "idle", + threadId: "thread-previous-workspace" + } + } + }), { status: 200 }); + } + if (String(url).endsWith("/v1/agent/chat")) { + return new Response(JSON.stringify({ accepted: true, status: "running", traceId: "trc_restore_workspace", resultUrl: "/v1/agent/chat/result/trc_restore_workspace" }), { status: 202 }); + } + if (String(url).endsWith("/v1/agent/chat/result/trc_restore_workspace")) { + return new Response(JSON.stringify({ status: "completed", traceId: "trc_restore_workspace", conversationId: "cnv_restore_workspace", sessionId: "ses_restore_workspace", reply: { role: "assistant", content: "restore workspace ready" } }), { status: 200 }); + } + if (String(url).endsWith("/v1/workbench/workspace/wsp_restore_workspace")) { + return new Response(JSON.stringify({ ok: true }), { status: 200 }); + } + return new Response(JSON.stringify({ ok: false, error: { code: "unexpected_test_route" } }), { status: 404 }); + }, + sleep: async () => {} + }); + + assert.equal(result.exitCode, 0); + assert.equal(calls[1].url, "http://web.test/v1/agent/chat"); + assert.equal(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_restore_workspace"); + assert.equal(calls[4].url, "http://web.test/v1/workbench/workspace?projectId=prj_device_pod_workbench"); + assert.equal(result.payload.workspace.selectedConversationId, "cnv_restore_workspace"); + assert.equal(result.payload.workspace.selectedAgentSessionId, "ses_restore_workspace"); + assert.equal(result.payload.workspace.threadId, "thread-restore-workspace"); + + const session = JSON.parse(await readFile(path.join(cwd, ".state/hwlab-cli/session.json"), "utf8")); + assert.equal(session.workspace.selectedConversationId, "cnv_restore_workspace"); + assert.equal(session.workspace.selectedAgentSessionId, "ses_restore_workspace"); + assert.equal(session.workspace.threadId, "thread-restore-workspace"); +}); + test("hwlab-cli client agent send 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 ca53e1c2..08c5cf11 100644 --- a/tools/src/hwlab-cli-lib.ts +++ b/tools/src/hwlab-cli-lib.ts @@ -1807,7 +1807,12 @@ async function saveCompletedAgentWorkspaceState(context: any, resultBody: any, f body, timeoutMs: DEFAULT_TIMEOUT_MS }); - if (responseSucceeded(response) && response.body?.workspace) await saveWorkspaceState(context, response.body.workspace); + if (!responseSucceeded(response)) return; + if (response.body?.workspace) { + await saveWorkspaceState(context, response.body.workspace); + return; + } + await restoreWorkbenchWorkspace(context, { quiet: true }); } async function saveWorkspaceState(context: any, workspaceBody: any) {