From e58e8e391cd5fe59da035fc3d32a9785776b0bd1 Mon Sep 17 00:00:00 2001 From: lyon Date: Wed, 17 Jun 2026 12:51:52 +0800 Subject: [PATCH] fix(workbench): preserve message lifecycle on reload --- internal/cloud/access-control.test.ts | 16 +++++++++++++--- internal/cloud/access-control.ts | 12 ++++++++++-- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/internal/cloud/access-control.test.ts b/internal/cloud/access-control.test.ts index 8b74c133..7d53e42f 100644 --- a/internal/cloud/access-control.test.ts +++ b/internal/cloud/access-control.test.ts @@ -1544,6 +1544,8 @@ test("access controller preserves user message when owner evidence updates with const conversationId = "cnv_issue853_owner_merge"; const sessionId = "ses_issue853_owner_merge"; const traceId = "trc_issue853_owner_merge"; + const userMessageId = "msg_issue853_owner_merge_user"; + const assistantMessageId = "msg_issue853_owner_merge_agent"; const threadId = "thread-issue-853-owner-merge"; const nowValue = "2026-06-04T10:45:00.000Z"; const userText = "请执行 hwpod inspect,并在最终回复里原样包含 HWLAB-853-FINAL-owner-merge"; @@ -1563,8 +1565,8 @@ test("access controller preserves user message when owner evidence updates with sessionStatus: "running", lastTraceId: traceId, messages: [ - { id: "msg_issue853_owner_user", role: "user", title: "用户", text: userText, status: "sent", traceId, conversationId, sessionId, threadId, createdAt: nowValue }, - { id: "msg_issue853_owner_agent_pending", role: "agent", title: "Code Agent 处理中", text: "Code Agent 仍在处理,可以继续 steer 或等待 trace 完成。", status: "running", traceId, conversationId, sessionId, threadId, createdAt: nowValue } + { id: userMessageId, messageId: userMessageId, role: "user", title: "用户", text: userText, status: "sent", traceId, turnId: traceId, conversationId, sessionId, threadId, createdAt: nowValue }, + { id: assistantMessageId, messageId: assistantMessageId, role: "agent", title: "Code Agent 处理中", text: "Code Agent 仍在处理,可以继续 steer 或等待 trace 完成。", status: "running", traceId, turnId: traceId, conversationId, sessionId, threadId, createdAt: nowValue } ], messageCount: 2, firstUserMessagePreview: userText, @@ -1587,7 +1589,7 @@ test("access controller preserves user message when owner evidence updates with status: "failed", sessionStatus: "failed", messages: [ - { id: "msg_issue853_owner_agent_result", role: "agent", title: "Code Agent result", text: "HyueAPI 403 INSUFFICIENT_BALANCE", status: "failed", traceId, conversationId, sessionId, threadId, createdAt: nowValue } + { id: assistantMessageId, messageId: assistantMessageId, role: "agent", title: "Code Agent result", text: "HyueAPI 403 INSUFFICIENT_BALANCE", status: "failed", traceId, turnId: traceId, conversationId, sessionId, threadId, createdAt: nowValue } ], messageCount: 1, firstUserMessagePreview: userText, @@ -1604,6 +1606,14 @@ test("access controller preserves user message when owner evidence updates with assert.equal(conversation.messages[0].text, userText); assert.equal(session.session.messages[0].traceId, traceId); assert.equal(conversation.messages[0].traceId, traceId); + assert.equal(session.session.messages[0].messageId, userMessageId); + assert.equal(session.session.messages[0].turnId, traceId); + assert.equal(conversation.messages[0].messageId, userMessageId); + assert.equal(conversation.messages[0].turnId, traceId); + assert.equal(session.session.messages[1].messageId, assistantMessageId); + assert.equal(session.session.messages[1].turnId, traceId); + assert.equal(conversation.messages[1].messageId, assistantMessageId); + assert.equal(conversation.messages[1].turnId, traceId); assert.equal(session.session.messages[1].text, "HyueAPI 403 INSUFFICIENT_BALANCE"); assert.equal(conversation.messages[1].status, "failed"); assert.equal(session.session.messageCount, 2); diff --git a/internal/cloud/access-control.ts b/internal/cloud/access-control.ts index 3f3aa10b..3cea5557 100644 --- a/internal/cloud/access-control.ts +++ b/internal/cloud/access-control.ts @@ -2549,13 +2549,16 @@ function mergeTerminalConversationMessages(existingMessages = [], result = {}, c }).filter(Boolean); let addedAgentMessage = false; if (traceId && !next.some((message) => message.traceId === traceId && message.role === "agent")) { + const messageId = result.reply?.messageId ?? result.messageId ?? `msg_${traceId.slice(4)}_agent`; next.push(redactConversationMessage({ - id: result.reply?.messageId ?? result.messageId ?? `msg_${traceId.slice(4)}`, + id: messageId, + messageId, role: "agent", title: "Code Agent result", text: assistantText, status: terminalStatus, traceId, + turnId: traceId, conversationId: context.conversationId, sessionId: context.sessionId, threadId: context.threadId, @@ -2577,13 +2580,16 @@ function ensureTerminalUserMessage(messages = [], context = {}) { const userText = conversationText(context.firstUserPreview, 12000); if (!userText) return messages; const traceId = safeTraceIdLocal(context.traceId); + const messageId = traceId ? `msg_${traceId.slice(4)}_user` : `msg_terminal_user_${messages.length}`; const userMessage = redactConversationMessage({ - id: traceId ? `msg_${traceId.slice(4)}_user` : `msg_terminal_user_${messages.length}`, + id: messageId, + messageId, role: "user", title: "用户", text: userText, status: "sent", traceId, + turnId: traceId, conversationId: context.conversationId, sessionId: context.sessionId, threadId: context.threadId, @@ -2617,11 +2623,13 @@ function redactConversationMessage(message) { const text = conversationText(message.text ?? message.content ?? message.message, 12000); return pruneEmpty({ id: textOr(message.id, ""), + messageId: textOr(message.messageId ?? message.id, ""), role: textOr(message.role, ""), title: textOr(message.title, ""), text, status: textOr(message.status, ""), traceId: textOr(message.traceId, ""), + turnId: textOr(message.turnId ?? message.traceId, ""), conversationId: textOr(message.conversationId, ""), sessionId: textOr(message.sessionId, ""), threadId: textOr(message.threadId, ""),