From fcd15b49d1f4fa96b781b8d39980d5265acafd3f Mon Sep 17 00:00:00 2001 From: Codex Agent Date: Fri, 5 Jun 2026 23:12:18 +0800 Subject: [PATCH] fix: trim message-only folded progress --- internal/cloud/access-control.test.ts | 72 +++++++++++++++++++++++++++ internal/cloud/access-control.ts | 19 ++++++- 2 files changed, 89 insertions(+), 2 deletions(-) diff --git a/internal/cloud/access-control.test.ts b/internal/cloud/access-control.test.ts index a0461814..e2f5d77e 100644 --- a/internal/cloud/access-control.test.ts +++ b/internal/cloud/access-control.test.ts @@ -1934,6 +1934,78 @@ test("cloud api trims folded progress from historical multi-turn final response } }); +test("cloud api trims folded progress when only conversation messages preserve the clean final text (#934)", async () => { + const nowValue = "2026-06-05T15:08:00.000Z"; + const projectId = "prj_hwpod_workbench"; + const conversationId = "cnv_issue934_message_only_history"; + const sessionId = "ses_issue934_message_only_history"; + const firstTraceId = "trc_issue934_message_only_first"; + const secondTraceId = "trc_issue934_message_only_second"; + const threadId = "thread-issue-934-message-only"; + const finalText = "当前工作区没有 `.hwlab/hwpod-spec.yaml`,也没有 `.hwlab` 目录或现成的 hwpod-spec 文件。这意味着这个 AgentRun 会话还没有绑定具体的 HWPOD。\n\n目前可用的 HWPOD:**0 个**(需要先创建 spec 并绑定)。\n\n如果你有已知的 HWPOD 信息(例如节点 ID、target 或 spec 模板),可以给我,我可以帮你生成 `.hwlab/hwpod-spec.yaml`。"; + const progressText = "Let me check the available HWPODs using the `hwpod` CLI tool.当前工作区还没有 `.hwlab/hwpod-spec.yaml` 文件,所以 `hwpod inspect` 无法获取可用 HWPOD 信息。 让我看看仓库里有没有现成的 spec 示例或相关配置:"; + const accessController = createAccessController({ + env: { HWLAB_ACCESS_CONTROL_REQUIRED: "1", HWLAB_BOOTSTRAP_ADMIN_PASSWORD: "admin-pass" }, + now: () => nowValue + }); + const server = createCloudApiServer({ env: { HWLAB_ACCESS_CONTROL_REQUIRED: "1" }, accessController, now: () => nowValue }); + await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); + try { + const { port } = server.address(); + const login = await postJson(port, "/auth/login", { username: "admin", password: "admin-pass" }); + assert.equal(login.status, 200); + const ownerUserId = login.body.actor.id; + + accessController.store.agentSessions.set(sessionId, { + id: sessionId, + projectId, + agentId: "hwlab-code-agent", + status: "completed", + startedAt: nowValue, + endedAt: null, + ownerUserId, + conversationId, + threadId, + lastTraceId: firstTraceId, + updatedAt: nowValue, + session: { + source: "legacy-message-only-progress-polluted-snapshot", + sessionStatus: "completed", + lastTraceId: firstTraceId, + messages: [ + { id: "msg_issue934_message_only_first_user", role: "user", title: "用户", text: "你好", status: "sent", traceId: firstTraceId, conversationId, sessionId, threadId, createdAt: nowValue }, + { id: "msg_issue934_message_only_first_agent", role: "agent", title: "Code Agent 回复", text: finalText, status: "completed", traceId: firstTraceId, conversationId, sessionId, threadId, createdAt: nowValue }, + { id: "msg_issue934_message_only_second_user", role: "user", title: "用户", text: "看看有几个hwpod可用?", status: "sent", traceId: secondTraceId, conversationId, sessionId, threadId, createdAt: nowValue }, + { id: "msg_issue934_message_only_second_agent", role: "agent", title: "Code Agent 回复", text: `${progressText}${finalText}`, status: "completed", traceId: secondTraceId, conversationId, sessionId, threadId, createdAt: nowValue } + ], + messageCount: 4, + firstUserMessagePreview: "你好", + valuesRedacted: true, + secretMaterialStored: false + } + }); + + const direct = await getJson(port, `/v1/agent/conversations/${conversationId}?projectId=${projectId}`, login.cookie); + assert.equal(direct.status, 200); + assert.equal(direct.body.conversation.messages.length, 4); + assert.equal(direct.body.conversation.messages[1].text, finalText); + assert.equal(direct.body.conversation.messages[3].text, finalText); + assert.equal(direct.body.conversation.messages[3].traceId, secondTraceId); + assert.doesNotMatch(direct.body.conversation.messages[3].text, /Let me check|当前工作区还没有|让我看看/u); + + const list = await getJson(port, `/v1/agent/conversations?projectId=${projectId}`, login.cookie); + assert.equal(list.status, 200); + const listed = list.body.conversations.find((conversation) => conversation.conversationId === conversationId); + assert.ok(listed, "expected message-only legacy conversation to be listed"); + assert.equal(listed.messages[3].text, finalText); + assert.doesNotMatch(listed.messages[3].text, /Let me check|当前工作区还没有|让我看看/u); + } finally { + await new Promise((resolve, reject) => { + server.close((error) => (error ? reject(error) : resolve())); + }); + } +}); + test("access controller restores AgentRun mapping by Code Agent traceId", async () => { const accessController = createAccessController({ now: () => "2026-06-01T00:00:00.000Z" }); await accessController.recordAgentSessionOwner({ diff --git a/internal/cloud/access-control.ts b/internal/cloud/access-control.ts index 3d69ce49..5c7572fe 100644 --- a/internal/cloud/access-control.ts +++ b/internal/cloud/access-control.ts @@ -1866,7 +1866,7 @@ function mergeAgentSessionOwnerEvidence(nextValue, existingValue) { } else if (existingMessages?.length) { merged.messages = existingMessages; } - merged.messages = normalizeFinalResponseConversationMessages(merged.messages, merged.finalResponse); + merged.messages = normalizeFoldedProgressConversationMessages(normalizeFinalResponseConversationMessages(merged.messages, merged.finalResponse)); const existingChatMessages = Array.isArray(existing.chatMessages) ? existing.chatMessages : null; const nextChatMessages = Array.isArray(next.chatMessages) ? next.chatMessages : null; if (existingChatMessages?.length && (!nextChatMessages || nextChatMessages.length === 0)) merged.chatMessages = existingChatMessages; @@ -1911,6 +1911,21 @@ function isFoldedProgressBeforeFinalResponse(messageText, finalText) { if (prefix.length < 20) return false; return /\bLet me (?:check|inspect|look)\b|\bI(?:'|’)?ll (?:check|inspect|look)\b|我(?:先|来|再)?(?:确认|检查|查看|看看)|让我(?:确认|检查|查看|看看)|正在(?:确认|检查|查看)|当前工作区还没有|hwpod inspect/iu.test(prefix); } +function normalizeFoldedProgressConversationMessages(messages) { + if (!Array.isArray(messages) || messages.length < 2) return messages; + const cleanAgentTexts = []; + return messages.map((message) => { + if (message?.role !== "agent") return message; + const text = textOr(message.text, ""); + const replacementText = cleanAgentTexts.find((candidate) => isFoldedProgressBeforeFinalResponse(text, candidate)); + const normalized = replacementText ? redactConversationMessage({ ...message, text: replacementText }) : message; + const normalizedText = textOr(normalized?.text, ""); + if (normalizedText.length >= 80 && !cleanAgentTexts.includes(normalizedText) && !cleanAgentTexts.some((candidate) => isFoldedProgressBeforeFinalResponse(normalizedText, candidate))) { + cleanAgentTexts.push(normalizedText); + } + return normalized; + }).filter(Boolean); +} function mergeAgentSessionOwnerMessages(nextMessages = [], existingMessages = [], context = {}) { const next = dedupeConversationMessages(nextMessages); if (next.some((message) => message.role === "user")) return ensureTerminalUserMessage(next, context).slice(-50); @@ -2380,7 +2395,7 @@ function publicAgentConversation(session) { now: session.updatedAt, firstUserPreview: snapshot.firstUserMessagePreview })); - const displayMessages = normalizeFinalResponseConversationMessages(messages, snapshot.finalResponse); + const displayMessages = normalizeFoldedProgressConversationMessages(normalizeFinalResponseConversationMessages(messages, snapshot.finalResponse)); const status = resolvedConversationStatus(session.status, snapshot, displayMessages); const lastTraceId = resolvedConversationLastTraceId(session.lastTraceId, status, displayMessages, snapshot); return {