Merge pull request #726 from pikasTech/fix/issue723-cli-save-workspace
fix: 持久化 HWLAB CLI workspace 更新
This commit is contained in:
@@ -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");
|
||||
|
||||
@@ -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<string, unknown> = {}) {
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user