From 164ecfd7c8fe729cc201b4b58374a0d3715a8ea8 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 8 May 2026 01:37:11 +0100 Subject: [PATCH] fix: show web search queries in progress drafts --- CHANGELOG.md | 2 +- docs/channels/discord.md | 2 +- docs/concepts/progress-drafts.md | 10 ++-- src/agents/tool-display-common.ts | 65 ++++++++++++++++++++++-- src/agents/tool-display.test.ts | 27 ++++++++++ src/plugin-sdk/channel-streaming.test.ts | 7 +++ 6 files changed, 102 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4588e6bd95d..b74c1a61d2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ Docs: https://docs.openclaw.ai ### Changes - Agents/failover: harden state-aware lane suspension by persisting quota resume transitions, restoring configured lane concurrency, preserving non-quota failure reasons, and exporting model failover events through diagnostics OTLP. Thanks @BunsDev. -- Channels/streaming: make progress draft labels scroll away with other progress lines, render structured tool rows as compact emoji/details, and skip empty Discord apply-patch starts until a patch summary exists. (#79146) +- Channels/streaming: make progress draft labels scroll away with other progress lines, render structured tool rows as compact emoji/details, show web-search queries from provider-native argument shapes, and skip empty Discord apply-patch starts until a patch summary exists. (#79146) - Telegram: preserve the channel-specific 10-option poll cap in the unified outbound adapter so over-limit polls are rejected before send. (#78762) Thanks @obviyus. - Runtime/install: raise the supported Node 22 floor to `22.16+` so native SQLite query handling can rely on the `node:sqlite` statement metadata API while continuing to recommend Node 24. (#78921) - Discord/voice: include a bounded one-line STT transcript preview in verbose voice logs so live voice debugging shows what speakers said before the agent reply. diff --git a/docs/channels/discord.md b/docs/channels/discord.md index 495458bbf69..de3a0d853a0 100644 --- a/docs/channels/discord.md +++ b/docs/channels/discord.md @@ -687,7 +687,7 @@ Default slash command settings: - `block` emits draft-sized chunks (use `draftChunk` to tune size and breakpoints, clamped to `textChunkLimit`). - Media, error, and explicit-reply finals cancel pending preview edits. - `streaming.preview.toolProgress` (default `true`) controls whether tool/progress updates reuse the preview message. - - Tool/progress rows render as compact emoji + detail when available, for example `🛠️ run tests`, and omit repeated tool names unless no clearer detail exists. + - Tool/progress rows render as compact emoji + title + detail when available, for example `🛠️ Bash: run tests` or `🔎 Web Search: for "query"`. - `streaming.preview.commandText` / `streaming.progress.commandText` controls command/exec detail in compact progress lines: `raw` (default) or `status` (tool label only). Hide raw command/exec text while keeping compact progress lines: diff --git a/docs/concepts/progress-drafts.md b/docs/concepts/progress-drafts.md index 4ce3440015b..ccfbf271fc9 100644 --- a/docs/concepts/progress-drafts.md +++ b/docs/concepts/progress-drafts.md @@ -19,8 +19,8 @@ into the final answer when the channel can do that safely. ```text Shelling... 📖 from docs/concepts/progress-drafts.md -🔎 for "discord edit message" -🛠️ run tests +🔎 Web Search: for "discord edit message" +🛠️ Bash: run tests ``` Use progress drafts when you want one tidy status message during tool-heavy work @@ -60,9 +60,9 @@ The label appears after the agent starts meaningful work and either remains busy for five seconds or emits a second work event. It is part of the rolling progress line list, so the starter status scrolls away once enough concrete work appears. Plain text-only replies do not show a progress draft. Progress lines are added -only when the agent emits useful work updates, for example `🛠️ run tests`, -`🔎 for "discord edit message"`, or `✍️ to /tmp/file`. By default they use the -same compact explain mode as `/verbose`; set +only when the agent emits useful work updates, for example `🛠️ Bash: run tests`, +`🔎 Web Search: for "discord edit message"`, or `✍️ Write: to /tmp/file`. +By default they use the same compact explain mode as `/verbose`; set `agents.defaults.toolProgressDetail: "raw"` when debugging and you also want raw commands/details appended. The final answer replaces the draft when possible; otherwise diff --git a/src/agents/tool-display-common.ts b/src/agents/tool-display-common.ts index 023c4784ac6..8cba8d1e6d3 100644 --- a/src/agents/tool-display-common.ts +++ b/src/agents/tool-display-common.ts @@ -280,19 +280,76 @@ function resolveWebSearchDetail(args: unknown): string | undefined { return undefined; } - const query = normalizeOptionalString(record.query); + const queries = collectWebSearchQueries(record); const count = typeof record.count === "number" && Number.isFinite(record.count) && record.count > 0 ? Math.floor(record.count) - : undefined; + : typeof record.max_results === "number" && + Number.isFinite(record.max_results) && + record.max_results > 0 + ? Math.floor(record.max_results) + : typeof record.num_results === "number" && + Number.isFinite(record.num_results) && + record.num_results > 0 + ? Math.floor(record.num_results) + : typeof record.limit === "number" && Number.isFinite(record.limit) && record.limit > 0 + ? Math.floor(record.limit) + : typeof record.top_k === "number" && Number.isFinite(record.top_k) && record.top_k > 0 + ? Math.floor(record.top_k) + : undefined; - if (!query) { + if (queries.length === 0) { return undefined; } - return count !== undefined ? `for "${query}" (top ${count})` : `for "${query}"`; + const displayedQueries = queries.slice(0, 3).map((query) => `"${query}"`); + const queryText = + queries.length > displayedQueries.length + ? `${displayedQueries.join(", ")}…` + : displayedQueries.join(", "); + + return count !== undefined ? `for ${queryText} (top ${count})` : `for ${queryText}`; } +function collectWebSearchQueries(record: Record): string[] { + const queries: string[] = []; + const seen = new Set(); + const add = (value: unknown) => { + const normalized = normalizeOptionalString(value); + if (!normalized || seen.has(normalized)) { + return; + } + seen.add(normalized); + queries.push(normalized); + }; + + add(record.query); + add(record.q); + add(record.search); + add(record.input); + + for (const key of ["search_query", "image_query", "queries"]) { + const value = record[key]; + if (!Array.isArray(value)) { + continue; + } + for (const entry of value) { + if (typeof entry === "string") { + add(entry); + continue; + } + const entryRecord = asRecord(entry); + if (!entryRecord) { + continue; + } + add(entryRecord.query); + add(entryRecord.q); + add(entryRecord.search); + } + } + + return queries; +} function resolveWebFetchDetail(args: unknown): string | undefined { const record = asRecord(args); if (!record) { diff --git a/src/agents/tool-display.test.ts b/src/agents/tool-display.test.ts index c0d38409d7f..bfab642c594 100644 --- a/src/agents/tool-display.test.ts +++ b/src/agents/tool-display.test.ts @@ -88,6 +88,33 @@ describe("tool display details", () => { expect(detail).toBe('for "OpenClaw docs" (top 3)'); }); + it("formats web_search provider query shapes", () => { + expect( + formatToolDetail( + resolveToolDisplay({ + name: "web_search", + args: { q: "Codex OAuth API key", max_results: 5 }, + }), + ), + ).toBe('for "Codex OAuth API key" (top 5)'); + + expect( + formatToolDetail( + resolveToolDisplay({ + name: "web_search", + args: { + search_query: [ + { q: "latest Kimi model" }, + { q: "latest Gemini model" }, + { q: "latest Claude model" }, + { q: "latest OpenAI model" }, + ], + }, + }), + ), + ).toBe('for "latest Kimi model", "latest Gemini model", "latest Claude model"…'); + }); + it("summarizes exec commands with context", () => { const detail = formatToolDetail( resolveToolDisplay({ diff --git a/src/plugin-sdk/channel-streaming.test.ts b/src/plugin-sdk/channel-streaming.test.ts index 12caa55fb09..815dd71550c 100644 --- a/src/plugin-sdk/channel-streaming.test.ts +++ b/src/plugin-sdk/channel-streaming.test.ts @@ -332,6 +332,13 @@ describe("channel-streaming", () => { args: { command: "sed -n '1,80p' extensions/discord/src/draft-stream.ts" }, }), ).toBe("🛠️ Bash: print lines 1-80 from extensions/discord/src/draft-stream.ts"); + expect( + formatChannelProgressDraftLine({ + event: "tool", + name: "web_search", + args: { search_query: [{ q: "Codex OAuth API key" }], response_length: "short" }, + }), + ).toBe('🔎 Web Search: for "Codex OAuth API key"'); expect( formatChannelProgressDraftLine({ event: "item",