fix: sync terminal workbench conversation

This commit is contained in:
Codex
2026-06-02 02:21:01 +08:00
parent 5f7fa31584
commit bf2bdee5f6
2 changed files with 85 additions and 1 deletions
+6 -1
View File
@@ -397,7 +397,7 @@ test("workbench workspace status clears completed AgentRun active trace on read"
threadId: "thread-issue-664",
sessionStatus: "active",
lastTraceId: "trc_issue664_previous",
messages: [{ role: "agent", text: "previous turn", traceId: "trc_issue664_previous" }]
messages: [{ role: "agent", text: "previous turn still running", status: "running", traceId: "trc_issue664_status_done" }]
}, aliceLogin.cookie);
assert.equal(conversation.status, 200);
@@ -441,6 +441,11 @@ test("workbench workspace status clears completed AgentRun active trace on read"
assert.equal(restored.body.workspace.activeTraceId, null);
assert.equal(restored.body.workspace.workspace.lastTraceId, "trc_issue664_status_done");
assert.equal(restored.body.workspace.workspace.sessionStatus, "idle");
assert.equal(restored.body.workspace.selectedConversation.status, "idle");
assert.equal(restored.body.workspace.selectedConversation.lastTraceId, "trc_issue664_status_done");
assert.equal(restored.body.workspace.selectedConversation.messages.length, 1);
assert.equal(restored.body.workspace.selectedConversation.messages[0].status, "idle");
assert.equal(restored.body.workspace.selectedConversation.messages[0].text, "previous turn completed");
assert.equal(restored.body.workspace.revision, 3);
assert.ok(agentRunCalls.some((call) => call.path === "/api/v1/runs/run_workspace_status/commands/cmd_workspace_status/result"));
} finally {
+79
View File
@@ -871,6 +871,15 @@ class AccessController {
const workspaceJson = normalizeObject(workspace.workspace);
const selectedConversationId = safeConversationIdLocal(result.conversationId) ? result.conversationId : workspace.selectedConversationId;
const selectedAgentSessionId = safeAgentSessionId(result.sessionId ?? result.session?.sessionId ?? result.sessionReuse?.sessionId) || workspace.selectedAgentSessionId;
await this.syncTerminalWorkbenchConversation({
workspace,
actor,
result,
activeTraceId,
selectedConversationId,
selectedAgentSessionId,
now
});
const updated = await this.store.updateWorkspace?.({
workspaceId: workspace.id,
ownerUserId: workspace.ownerUserId,
@@ -913,6 +922,39 @@ class AccessController {
return publicWorkbenchWorkspace(workspace, { conversation, selectedDevicePod });
}
async syncTerminalWorkbenchConversation({ workspace, actor, result, activeTraceId, selectedConversationId, selectedAgentSessionId, now }) {
if (!safeConversationIdLocal(selectedConversationId) || !safeAgentSessionId(selectedAgentSessionId)) return null;
const existing = await this.visibleConversationForActor(actor, selectedConversationId, workspace.projectId);
const sessionStatus = terminalWorkbenchSessionStatus(result);
const threadId = textOr(result.threadId ?? result.session?.threadId ?? result.sessionReuse?.threadId ?? result.providerTrace?.threadId, existing?.threadId ?? null);
const messages = mergeTerminalConversationMessages(existing?.messages, result, {
traceId: activeTraceId,
conversationId: selectedConversationId,
sessionId: selectedAgentSessionId,
threadId,
now
});
return await this.recordAgentSessionOwner({
ownerUserId: actor.id,
sessionId: selectedAgentSessionId,
projectId: workspace.projectId,
agentId: existing?.agentId ?? "hwlab-code-agent",
status: sessionStatus,
conversationId: selectedConversationId,
threadId,
traceId: activeTraceId,
session: normalizeConversationSnapshot({
...(existing?.snapshot ?? {}),
source: "workbench-status-sync",
sessionStatus,
updatedAt: now,
lastTraceId: activeTraceId,
messages
}, actor),
now
});
}
async visibleConversationForActor(actor, conversationId, projectId = "") {
if (!safeConversationIdLocal(conversationId)) return null;
const sessions = await this.store.listAgentSessionsForUser?.({
@@ -1943,6 +1985,43 @@ function canActorReadWorkspaceTrace(result = {}, actor = null) {
if (!ownerUserId || actor?.role === "admin") return true;
return ownerUserId === actor?.id;
}
function mergeTerminalConversationMessages(existingMessages = [], result = {}, context = {}) {
const messages = Array.isArray(existingMessages) ? existingMessages.map(redactConversationMessage).filter(Boolean) : [];
const traceId = safeTraceIdLocal(result.traceId ?? context.traceId);
const terminalStatus = terminalWorkbenchSessionStatus(result);
const assistantText = textOr(result.reply?.content ?? result.assistantText ?? result.message?.content ?? result.error?.message ?? result.userMessage, "");
const now = textOr(context.now, new Date().toISOString());
const next = messages.map((message) => {
if (traceId && message.traceId === traceId && message.role === "agent") {
return redactConversationMessage({
...message,
text: assistantText || message.text,
status: terminalStatus,
conversationId: context.conversationId ?? message.conversationId,
sessionId: context.sessionId ?? message.sessionId,
threadId: context.threadId ?? message.threadId,
updatedAt: now
});
}
return message;
}).filter(Boolean);
if (traceId && !next.some((message) => message.traceId === traceId && message.role === "agent")) {
next.push(redactConversationMessage({
id: result.reply?.messageId ?? result.messageId ?? `msg_${traceId.slice(4)}`,
role: "agent",
title: "Code Agent result",
text: assistantText,
status: terminalStatus,
traceId,
conversationId: context.conversationId,
sessionId: context.sessionId,
threadId: context.threadId,
createdAt: result.reply?.createdAt ?? result.createdAt ?? now,
updatedAt: now
}));
}
return next.slice(-50);
}
function redactConversationMessage(message) {
if (!message || typeof message !== "object") return null;
const runnerTrace = message.runnerTrace && typeof message.runnerTrace === "object" ? message.runnerTrace : null;