fix: show web search queries in progress drafts

This commit is contained in:
Peter Steinberger
2026-05-08 01:37:11 +01:00
parent accf774591
commit 164ecfd7c8
6 changed files with 102 additions and 11 deletions

View File

@@ -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.

View File

@@ -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:

View File

@@ -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

View File

@@ -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, unknown>): string[] {
const queries: string[] = [];
const seen = new Set<string>();
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) {

View File

@@ -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({

View File

@@ -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",