diff --git a/CHANGELOG.md b/CHANGELOG.md index ab4602ff1e9..4a4a3905a51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -47,6 +47,7 @@ Docs: https://docs.openclaw.ai - Control UI: point the Appearance tweakcn browse action and docs at the live tweakcn editor route instead of the removed `/themes` page. Fixes #77048. - Control UI: render Dream Diary prose through the sanitized markdown pipeline, so diary bold/italic/header markdown no longer appears as literal source text. Fixes #62413. +- Control UI: render tool results whose output arrives as text-block arrays and give expanded tool output a scrollable block, so read/exec output remains visible in WebChat. Fixes #77054. - Diagnostics: keep webhook/message OTEL attributes and Prometheus delivery labels low-cardinality and omit raw chat/message IDs from spans, so progress-draft and message-tool modes do not leak high-cardinality messaging identifiers. - Google Meet: stop advertising legacy `mode: "realtime"` to agents and config UIs, while keeping it as a hidden compatibility alias for `mode: "agent"`, so new joins use the STT -> OpenClaw agent -> TTS path instead of selecting the direct realtime voice fallback. - Google Meet: add `chrome.audioBufferBytes` for generated command-pair SoX audio commands and lower the default buffer from SoX's 8192 bytes to 4096 bytes to reduce Chrome talk-back latency. diff --git a/ui/src/styles/chat/tool-cards.css b/ui/src/styles/chat/tool-cards.css index 414100bb990..294664d3def 100644 --- a/ui/src/styles/chat/tool-cards.css +++ b/ui/src/styles/chat/tool-cards.css @@ -361,7 +361,15 @@ .chat-tool-card__block-content { color: var(--text); - overflow-x: auto; + overflow: auto; + max-height: min(520px, 60vh); +} + +.chat-tool-card__inline { + margin-top: 10px; + white-space: pre-wrap; + overflow-wrap: anywhere; + word-break: break-word; } .chat-tool-card__block-preview { diff --git a/ui/src/ui/chat/tool-cards.node.test.ts b/ui/src/ui/chat/tool-cards.node.test.ts index 46c1e86e328..c685ac6e28e 100644 --- a/ui/src/ui/chat/tool-cards.node.test.ts +++ b/ui/src/ui/chat/tool-cards.node.test.ts @@ -136,6 +136,35 @@ describe("tool-card extraction", () => { }); }); + it("extracts tool result output from text block content arrays", () => { + const cards = extractToolCards( + { + role: "assistant", + content: [ + { + type: "toolcall", + id: "call-read", + name: "read", + input: { path: "README.md" }, + }, + { + type: "tool_result", + id: "call-read", + name: "read", + content: [ + { type: "text", text: "# Heading" }, + { type: "text", text: "file body" }, + ], + }, + ], + }, + "msg:read", + ); + + expect(cards).toHaveLength(1); + expect(cards[0]?.outputText).toBe("# Heading\nfile body"); + }); + it("builds sidebar content with input and empty output status", () => { const [card] = extractToolCards( { diff --git a/ui/src/ui/chat/tool-cards.ts b/ui/src/ui/chat/tool-cards.ts index 2ef78533242..66d5443db70 100644 --- a/ui/src/ui/chat/tool-cards.ts +++ b/ui/src/ui/chat/tool-cards.ts @@ -48,6 +48,18 @@ function extractToolText(item: Record): string | undefined { if (typeof item.content === "string") { return item.content; } + if (Array.isArray(item.content)) { + const parts = item.content.flatMap((entry) => { + if (!entry || typeof entry !== "object") { + return []; + } + const text = (entry as { text?: unknown }).text; + return typeof text === "string" ? [text] : []; + }); + if (parts.length > 0) { + return parts.join("\n"); + } + } return undefined; }