Merge pull request #728 from pikasTech/fix/issue723-cli-output-workspace
fix: keep hwlab cli workspace output current
This commit is contained in:
@@ -645,6 +645,81 @@ test("hwlab-cli client agent send restores workspace when completed patch omits
|
||||
assert.equal(session.workspace.threadId, "thread-restore-workspace");
|
||||
});
|
||||
|
||||
test("hwlab-cli client agent send output uses completed result when restore is stale", async () => {
|
||||
const cwd = await mkdtemp(path.join(os.tmpdir(), "hwlab-cli-client-agent-stale-restore-"));
|
||||
const calls: any[] = [];
|
||||
const result = await runHwlabCli([
|
||||
"client",
|
||||
"agent",
|
||||
"send",
|
||||
"--base-url",
|
||||
"http://web.test",
|
||||
"--cookie",
|
||||
"hwlab_session=session-a",
|
||||
"--message",
|
||||
"start a fresh conversation",
|
||||
"--trace-id",
|
||||
"trc_stale_restore",
|
||||
"--conversation-id",
|
||||
"cnv_stale_restore",
|
||||
"--wait",
|
||||
"--poll-interval-ms",
|
||||
"1",
|
||||
"--timeout-ms",
|
||||
"1000"
|
||||
], {
|
||||
cwd,
|
||||
fetchImpl: async (url, init) => {
|
||||
calls.push({ url: String(url), init, body: init?.body ? JSON.parse(String(init.body)) : null });
|
||||
if (String(url).endsWith("/v1/workbench/workspace?projectId=prj_device_pod_workbench")) {
|
||||
return new Response(JSON.stringify({
|
||||
ok: true,
|
||||
workspace: {
|
||||
workspaceId: "wsp_stale_restore",
|
||||
revision: 21,
|
||||
selectedConversationId: "cnv_previous_restore",
|
||||
selectedAgentSessionId: "ses_previous_restore",
|
||||
selectedConversation: {
|
||||
conversationId: "cnv_previous_restore",
|
||||
sessionId: "ses_previous_restore",
|
||||
threadId: "thread-previous-restore"
|
||||
},
|
||||
workspace: {
|
||||
sessionStatus: "idle",
|
||||
threadId: "thread-previous-restore"
|
||||
}
|
||||
}
|
||||
}), { status: 200 });
|
||||
}
|
||||
if (String(url).endsWith("/v1/agent/chat")) {
|
||||
return new Response(JSON.stringify({ accepted: true, status: "running", traceId: "trc_stale_restore", resultUrl: "/v1/agent/chat/result/trc_stale_restore" }), { status: 202 });
|
||||
}
|
||||
if (String(url).endsWith("/v1/agent/chat/result/trc_stale_restore")) {
|
||||
return new Response(JSON.stringify({ status: "completed", traceId: "trc_stale_restore", conversationId: "cnv_stale_restore", sessionId: "ses_stale_restore", reply: { role: "assistant", content: "stale restore ready" } }), { status: 200 });
|
||||
}
|
||||
if (String(url).endsWith("/v1/workbench/workspace/wsp_stale_restore")) {
|
||||
return new Response(JSON.stringify({ ok: true }), { status: 200 });
|
||||
}
|
||||
return new Response(JSON.stringify({ ok: false, error: { code: "unexpected_test_route" } }), { status: 404 });
|
||||
},
|
||||
sleep: async () => {}
|
||||
});
|
||||
|
||||
assert.equal(result.exitCode, 0);
|
||||
assert.equal(calls[4].url, "http://web.test/v1/workbench/workspace?projectId=prj_device_pod_workbench");
|
||||
assert.equal(result.payload.workspace.selectedConversationId, "cnv_stale_restore");
|
||||
assert.equal(result.payload.workspace.selectedAgentSessionId, "ses_stale_restore");
|
||||
assert.equal(Object.hasOwn(result.payload.workspace, "threadId"), false);
|
||||
assert.equal(result.payload.workspace.selectedConversation.conversationId, "cnv_stale_restore");
|
||||
assert.equal(result.payload.workspace.selectedConversation.sessionId, "ses_stale_restore");
|
||||
assert.equal(Object.hasOwn(result.payload.workspace.selectedConversation, "threadId"), false);
|
||||
|
||||
const session = JSON.parse(await readFile(path.join(cwd, ".state/hwlab-cli/session.json"), "utf8"));
|
||||
assert.equal(session.workspace.selectedConversationId, "cnv_stale_restore");
|
||||
assert.equal(session.workspace.selectedAgentSessionId, "ses_stale_restore");
|
||||
assert.equal(Object.hasOwn(session.workspace, "threadId"), false);
|
||||
});
|
||||
|
||||
test("hwlab-cli client agent send preserves Web continuation fields", async () => {
|
||||
const cwd = await mkdtemp(path.join(os.tmpdir(), "hwlab-cli-client-agent-continuation-"));
|
||||
await writeFile(path.join(cwd, "prompt.txt"), "看看有什么device-pod能用?", "utf8");
|
||||
|
||||
@@ -1088,7 +1088,7 @@ async function agentSend(context: any) {
|
||||
return responsePayload("client.agent.send", accepted, context, { route: route("POST", "/v1/agent/chat"), traceId, conversationId, continuation, replay: replay.summary, workspace: workspaceSummaryFromSession(await loadStoredState({ parsed, env: context.env, cwd: context.cwd ?? process.cwd() })), waited: false, waitPolicy: agentSendWaitPolicy(traceId) });
|
||||
}
|
||||
const result = await pollAgentResult(context, traceId, accepted.body);
|
||||
await saveCompletedAgentWorkspaceState(context, result.response?.body, { traceId, conversationId, sessionId, threadId, providerProfile: requestBody.providerProfile });
|
||||
const completedWorkspace = await saveCompletedAgentWorkspaceState(context, result.response?.body, { traceId, conversationId, sessionId, threadId, providerProfile: requestBody.providerProfile });
|
||||
return ok("client.agent.send", {
|
||||
status: result.final ? "succeeded" : "timeout",
|
||||
route: route("POST", "/v1/agent/chat"),
|
||||
@@ -1098,7 +1098,7 @@ async function agentSend(context: any) {
|
||||
replay: replay.summary,
|
||||
accepted: compactBody(accepted.body),
|
||||
result: result.response ? compactResponse(result.response) : null,
|
||||
workspace: workspaceSummaryFromSession(await loadStoredState({ parsed, env: context.env, cwd: context.cwd ?? process.cwd() })),
|
||||
workspace: completedWorkspace ?? workspaceSummaryFromSession(await loadStoredState({ parsed, env: context.env, cwd: context.cwd ?? process.cwd() })),
|
||||
polls: result.polls,
|
||||
timeoutMs: result.timeoutMs,
|
||||
resultUrl: result.resultPath
|
||||
@@ -1781,8 +1781,12 @@ async function saveAcceptedAgentWorkspaceState(context: any, acceptedBody: any,
|
||||
async function saveCompletedAgentWorkspaceState(context: any, resultBody: any, fallback: any) {
|
||||
const session = await loadStoredState({ parsed: context.parsed, env: context.env, cwd: context.cwd ?? process.cwd() }) ?? {};
|
||||
const existing = session.workspace && typeof session.workspace === "object" ? session.workspace : {};
|
||||
const resultWorkspace = resultBody ? workspaceWithAgentResult(existing, resultBody, fallback) : null;
|
||||
if (resultWorkspace) {
|
||||
await saveSession(context, { ...session, baseUrl: baseUrl(context.parsed, context.env), workspace: resultWorkspace, updatedAt: context.now() });
|
||||
}
|
||||
const workspaceId = text(existing.workspaceId);
|
||||
if (!workspaceId) return;
|
||||
if (!workspaceId) return resultWorkspace;
|
||||
const status = text(resultBody?.status);
|
||||
const terminal = status && status !== "running";
|
||||
const pathName = `/v1/workbench/workspace/${encodeURIComponent(workspaceId)}`;
|
||||
@@ -1807,12 +1811,39 @@ async function saveCompletedAgentWorkspaceState(context: any, resultBody: any, f
|
||||
body,
|
||||
timeoutMs: DEFAULT_TIMEOUT_MS
|
||||
});
|
||||
if (!responseSucceeded(response)) return;
|
||||
if (!responseSucceeded(response)) return resultWorkspace;
|
||||
if (response.body?.workspace) {
|
||||
await saveWorkspaceState(context, response.body.workspace);
|
||||
return;
|
||||
const workspace = workspaceWithAgentResult(response.body.workspace, resultBody, fallback);
|
||||
await saveWorkspaceState(context, workspace);
|
||||
return workspace;
|
||||
}
|
||||
await restoreWorkbenchWorkspace(context, { quiet: true });
|
||||
const restored = await restoreWorkbenchWorkspace(context, { quiet: true });
|
||||
const workspace = workspaceWithAgentResult(restored, resultBody, fallback);
|
||||
if (workspace) await saveWorkspaceState(context, workspace);
|
||||
return workspace ?? resultWorkspace;
|
||||
}
|
||||
|
||||
function workspaceWithAgentResult(workspace: any, resultBody: any, fallback: any) {
|
||||
const base = normalizeWorkbenchWorkspace(workspace) ?? {};
|
||||
const conversationId = text(resultBody?.conversationId) || text(fallback.conversationId) || text(base.selectedConversationId);
|
||||
const sessionId = text(resultBody?.sessionId) || text(fallback.sessionId) || text(base.selectedAgentSessionId);
|
||||
const resultThreadId = text(resultBody?.threadId ?? resultBody?.session?.threadId ?? resultBody?.providerTrace?.threadId ?? fallback.threadId);
|
||||
const baseConversationMatches = !conversationId || text(base.selectedConversationId) === conversationId;
|
||||
const baseSessionMatches = !sessionId || text(base.selectedAgentSessionId) === sessionId;
|
||||
const baseThreadId = baseConversationMatches && baseSessionMatches ? text(base.threadId) : "";
|
||||
const selectedConversation = baseConversationMatches && baseSessionMatches && base.selectedConversation
|
||||
? base.selectedConversation
|
||||
: clean({ conversationId, sessionId, threadId: resultThreadId || baseThreadId });
|
||||
return clean({
|
||||
...base,
|
||||
selectedConversationId: conversationId,
|
||||
selectedAgentSessionId: sessionId,
|
||||
providerProfile: text(fallback.providerProfile) || text(base.providerProfile),
|
||||
sessionStatus: text(resultBody?.status) || text(base.sessionStatus),
|
||||
threadId: resultThreadId || baseThreadId,
|
||||
selectedConversation,
|
||||
updatedByClient: "hwlab-cli"
|
||||
});
|
||||
}
|
||||
|
||||
async function saveWorkspaceState(context: any, workspaceBody: any) {
|
||||
|
||||
Reference in New Issue
Block a user