diff --git a/CHANGELOG.md b/CHANGELOG.md index dd60b47c65a..ae5e50c9d70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -101,7 +101,7 @@ Docs: https://docs.openclaw.ai - Plugins/Voice Call: start provider STT after Telnyx outbound conversation greetings and pass configured Telnyx voice IDs through to the speak action. Fixes #56091. Thanks @Roshan. - Skills: honor legacy `metadata.clawdbot` requirements and installer hints when `metadata.openclaw` is absent, so older skills no longer appear ready when required binaries are missing. Fixes #71323. Thanks @chen-zhang-cs-code. - Browser/config: expand `~` in `browser.executablePath` before Chromium launch, so home-relative custom browser paths no longer fail with `ENOENT`. Fixes #67264. Thanks @Quratulain-bilal. -- Telegram/streaming: hide tool-progress status updates by default while keeping explicit `streaming.preview.toolProgress` opt-in support for edited preview messages. Fixes #71320. Thanks @neeravmakwana. +- Channels/streaming: keep Telegram tool-progress preview updates enabled by default to match released behavior, document `streaming.preview.toolProgress: false` for disabling only those status lines, and prevent preview progress text from triggering Telegram Markdown links, Discord mentions, or Slack mrkdwn mentions. Fixes #71320. Thanks @neeravmakwana. - Gateway/sessions: copy the oversized `sessions.json` to a rotation backup before the atomic rewrite instead of renaming the live store away, so a crash during rotation keeps the existing session-to-transcript mapping authoritative. Fixes #68229. Thanks @jjjojoj. - Providers/OpenAI-compatible: strip OpenAI-only Completions `store` from proxy payloads and allow `extra_body`/`extraBody` passthrough params for provider-specific request fields. Fixes #61826 and #69717. - Discord/subagents: preserve thread-bound completion delivery by keeping the requester-agent announce path primary and falling back to direct thread sends only when the announce produces no visible output. (#71064) Thanks @DolencLuka. diff --git a/docs/.generated/config-baseline.sha256 b/docs/.generated/config-baseline.sha256 index 3c080dd76cf..18beac9d4a0 100644 --- a/docs/.generated/config-baseline.sha256 +++ b/docs/.generated/config-baseline.sha256 @@ -1,4 +1,4 @@ -5c7709e1686f6ad90beaa8e34ba45e6445e34c48d598407bd837361b58c365ab config-baseline.json +b6d1e53947fcdfbff1b99f8ec79d3814d243385a1750b7fb40b40bb30f2e2975 config-baseline.json 98c83ce8af9ec4703726d7d673add95279be008a801b1d298982cbd9c1785747 config-baseline.core.json -22d7cd6d8279146b2d79c9531a55b80b52a2c99c81338c508104729154fdd02d config-baseline.channel.json +d72032762ab46b99480b57deb81130a0ab5b1401189cfbaf4f7fef4a063a7f6c config-baseline.channel.json 86f615b7d267b03888af0af7ccb3f8232a6b636f8a741d522ff425e46729ba81 config-baseline.plugin.json diff --git a/docs/channels/telegram.md b/docs/channels/telegram.md index efdeb70d648..50c0d1bc773 100644 --- a/docs/channels/telegram.md +++ b/docs/channels/telegram.md @@ -273,9 +273,28 @@ curl "https://api.telegram.org/bot/getUpdates" - `channels.telegram.streaming` is `off | partial | block | progress` (default: `partial`) - `progress` maps to `partial` on Telegram (compat with cross-channel naming) - - `streaming.preview.toolProgress` controls whether tool/progress updates reuse the same edited preview message (default: `false`). Set `true` only when visible Telegram progress updates are desired. + - `streaming.preview.toolProgress` controls whether tool/progress updates reuse the same edited preview message (default: `true` when preview streaming is active) - legacy `channels.telegram.streamMode` and boolean `streaming` values are auto-mapped + Tool-progress preview updates are the short "Working..." lines shown while tools run, for example command execution, file reads, planning updates, or patch summaries. Telegram keeps these enabled by default to match released OpenClaw behavior from `v2026.4.22` and later. To keep the edited preview for answer text but hide tool-progress lines, set: + + ```json + { + "channels": { + "telegram": { + "streaming": { + "mode": "partial", + "preview": { + "toolProgress": false + } + } + } + } + } + ``` + + Use `streaming.mode: "off"` only when you want to disable Telegram preview edits entirely. Use `streaming.preview.toolProgress: false` when you only want to disable the tool-progress status lines. + For text-only replies: - DM: OpenClaw keeps the same preview message and performs a final edit in place (no second message) diff --git a/docs/concepts/streaming.md b/docs/concepts/streaming.md index b61571a0fac..5c80ffc2cf4 100644 --- a/docs/concepts/streaming.md +++ b/docs/concepts/streaming.md @@ -176,10 +176,28 @@ Preview streaming can also include **tool-progress** updates — short status li Supported surfaces: -- **Discord** and **Slack** stream tool-progress into the live preview edit by default. -- **Telegram** only streams tool-progress into the live preview edit when `streaming.preview.toolProgress` is explicitly enabled. +- **Discord**, **Slack**, and **Telegram** stream tool-progress into the live preview edit by default when preview streaming is active. +- Telegram has shipped with tool-progress preview updates enabled since `v2026.4.22`; keeping them enabled preserves that released behavior. - **Mattermost** already folds tool activity into its single draft preview post (see above). - Tool-progress edits follow the active preview streaming mode; they are skipped when preview streaming is `off` or when block streaming has taken over the message. +- To keep preview streaming but hide tool-progress lines, set `streaming.preview.toolProgress` to `false` for that channel. To disable preview edits entirely, set `streaming.mode` to `off`. + +Example: + +```json +{ + "channels": { + "telegram": { + "streaming": { + "mode": "partial", + "preview": { + "toolProgress": false + } + } + } + } +} +``` ## Related diff --git a/extensions/discord/src/draft-stream.test.ts b/extensions/discord/src/draft-stream.test.ts index 110395b4007..b23dfce2c28 100644 --- a/extensions/discord/src/draft-stream.test.ts +++ b/extensions/discord/src/draft-stream.test.ts @@ -44,6 +44,7 @@ describe("createDiscordDraftStream", () => { expect(rest.post).toHaveBeenCalledWith(Routes.channelMessages("c1"), { body: { content: "first draft", + allowed_mentions: { parse: [] }, message_reference: { message_id: "parent-1", fail_if_not_exists: false, @@ -51,11 +52,42 @@ describe("createDiscordDraftStream", () => { }, }); expect(rest.patch).toHaveBeenCalledWith(Routes.channelMessage("c1", "m1"), { - body: { content: "second draft" }, + body: { content: "second draft", allowed_mentions: { parse: [] } }, }); expect(stream.messageId()).toBe("m1"); }); + it("suppresses mentions in preview creates and edits", async () => { + const rest = { + post: vi.fn(async () => ({ id: "m1" })), + patch: vi.fn(async () => undefined), + delete: vi.fn(async () => undefined), + }; + const stream = createDiscordDraftStream({ + rest: rest as never, + channelId: "c1", + throttleMs: 250, + }); + + stream.update("working @everyone <@123>"); + await stream.flush(); + stream.update("still working @here"); + await stream.flush(); + + expect(rest.post).toHaveBeenCalledWith(Routes.channelMessages("c1"), { + body: { + content: "working @everyone <@123>", + allowed_mentions: { parse: [] }, + }, + }); + expect(rest.patch).toHaveBeenCalledWith(Routes.channelMessage("c1", "m1"), { + body: { + content: "still working @here", + allowed_mentions: { parse: [] }, + }, + }); + }); + it("stops previewing and warns once text exceeds the configured limit", async () => { const rest = { post: vi.fn(async () => ({ id: "m1" })), diff --git a/extensions/discord/src/draft-stream.ts b/extensions/discord/src/draft-stream.ts index 7cc74eb78e9..5c398169c5d 100644 --- a/extensions/discord/src/draft-stream.ts +++ b/extensions/discord/src/draft-stream.ts @@ -6,6 +6,7 @@ import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime"; /** Discord messages cap at 2000 characters. */ const DISCORD_STREAM_MAX_CHARS = 2000; const DEFAULT_THROTTLE_MS = 1200; +const DISCORD_PREVIEW_ALLOWED_MENTIONS = { parse: [] }; export type DiscordDraftStream = { update: (text: string) => void; @@ -76,7 +77,7 @@ export function createDiscordDraftStream(params: { if (streamMessageId !== undefined) { // Edit existing message await rest.patch(Routes.channelMessage(channelId, streamMessageId), { - body: { content: trimmed }, + body: { content: trimmed, allowed_mentions: DISCORD_PREVIEW_ALLOWED_MENTIONS }, }); return true; } @@ -88,6 +89,7 @@ export function createDiscordDraftStream(params: { const sent = (await rest.post(Routes.channelMessages(channelId), { body: { content: trimmed, + allowed_mentions: DISCORD_PREVIEW_ALLOWED_MENTIONS, ...(messageReference ? { message_reference: messageReference } : {}), }, })) as { id?: string } | undefined; diff --git a/extensions/slack/src/monitor/message-handler/dispatch.preview-fallback.test.ts b/extensions/slack/src/monitor/message-handler/dispatch.preview-fallback.test.ts index a24993339d6..7cacbdea7ec 100644 --- a/extensions/slack/src/monitor/message-handler/dispatch.preview-fallback.test.ts +++ b/extensions/slack/src/monitor/message-handler/dispatch.preview-fallback.test.ts @@ -42,13 +42,14 @@ let mockedDispatchSequence: Array<{ mediaUrls?: string[]; }; }> = []; +let mockedProgressEvents: string[] = []; const noop = () => {}; const noopAsync = async () => {}; function createDraftStreamStub() { return { - update: noop, + update: vi.fn(), flush: noopAsync, clear: noopAsync, discardPending: noopAsync, @@ -292,7 +293,10 @@ vi.mock("../reply.runtime.js", () => ({ markDispatchIdle: () => {}, }), dispatchInboundMessage: async (params: { - replyOptions?: { disableBlockStreaming?: boolean }; + replyOptions?: { + disableBlockStreaming?: boolean; + onItemEvent?: (payload: { progressText: string }) => Promise | void; + }; dispatcher: { deliver: ( payload: { @@ -307,6 +311,9 @@ vi.mock("../reply.runtime.js", () => ({ }; }) => { capturedReplyOptions = params.replyOptions; + for (const progressText of mockedProgressEvents) { + await params.replyOptions?.onItemEvent?.({ progressText }); + } for (const entry of mockedDispatchSequence) { await params.dispatcher.deliver(entry.payload, { kind: entry.kind }); } @@ -344,6 +351,7 @@ describe("dispatchPreparedSlackMessage preview fallback", () => { mockedReplyThreadTs = THREAD_TS; mockedReplyThreadTsSequence = undefined; mockedDispatchSequence = [{ kind: "final", payload: { text: FINAL_REPLY_TEXT } }]; + mockedProgressEvents = []; createSlackDraftStreamMock.mockReturnValue(createDraftStreamStub()); finalizeSlackPreviewEditMock.mockRejectedValue(new Error("socket closed")); @@ -406,6 +414,19 @@ describe("dispatchPreparedSlackMessage preview fallback", () => { expect(capturedReplyOptions?.disableBlockStreaming).toBe(true); }); + it("escapes Slack mrkdwn in tool progress preview labels", async () => { + const draftStream = createDraftStreamStub(); + createSlackDraftStreamMock.mockReturnValueOnce(draftStream); + mockedDispatchSequence = []; + mockedProgressEvents = ["ran <@U123> *bold* `code` & done"]; + + await dispatchPreparedSlackMessage(createPreparedSlackMessage()); + + expect(draftStream.update).toHaveBeenCalledWith( + "Working…\n• ran <!here> <@U123> \\*bold\\* \\`code\\` & done", + ); + }); + it("starts native streams in the first-reply thread for top-level channel messages", async () => { mockedNativeStreaming = true; mockedReplyThreadTs = "171234.111"; diff --git a/extensions/slack/src/monitor/message-handler/dispatch.ts b/extensions/slack/src/monitor/message-handler/dispatch.ts index 5306f495f1a..56ed023a47c 100644 --- a/extensions/slack/src/monitor/message-handler/dispatch.ts +++ b/extensions/slack/src/monitor/message-handler/dispatch.ts @@ -47,6 +47,7 @@ import { import { resolveSlackThreadTargets } from "../../threading.js"; import { normalizeSlackAllowOwnerEntry } from "../allow-list.js"; import { resolveStorePath, updateLastRoute } from "../config.runtime.js"; +import { escapeSlackMrkdwn } from "../mrkdwn.js"; import { createSlackReplyDeliveryPlan, deliverReplies, @@ -891,11 +892,12 @@ export async function dispatchPreparedSlackMessage(prepared: PreparedSlackMessag if (!normalized) { return; } + const escaped = escapeSlackMrkdwn(normalized); const previous = previewToolProgressLines.at(-1); - if (previous === normalized) { + if (previous === escaped) { return; } - previewToolProgressLines = [...previewToolProgressLines, normalized].slice(-8); + previewToolProgressLines = [...previewToolProgressLines, escaped].slice(-8); draftStream.update( ["Working…", ...previewToolProgressLines.map((entry) => `• ${entry}`)].join("\n"), ); diff --git a/extensions/telegram/src/bot-message-dispatch.test.ts b/extensions/telegram/src/bot-message-dispatch.test.ts index ed9ce3563c3..ba0b7e2f09d 100644 --- a/extensions/telegram/src/bot-message-dispatch.test.ts +++ b/extensions/telegram/src/bot-message-dispatch.test.ts @@ -499,7 +499,7 @@ describe("dispatchTelegramMessage draft streaming", () => { ); }); - it("suppresses Telegram tool progress by default", async () => { + it("streams Telegram tool progress by default when preview streaming is active", async () => { const draftStream = createDraftStream(); createTelegramDraftStream.mockReturnValue(draftStream); dispatchReplyWithBufferedBlockDispatcher.mockImplementation(async ({ replyOptions }) => { @@ -510,6 +510,33 @@ describe("dispatchTelegramMessage draft streaming", () => { await dispatchWithContext({ context: createContext(), streamMode: "partial" }); + expect(draftStream.update).toHaveBeenCalledWith( + "Working…\n• `tool: exec`\n• `exec ls ~/Desktop`", + ); + expect(dispatchReplyWithBufferedBlockDispatcher).toHaveBeenCalledWith( + expect.objectContaining({ + replyOptions: expect.objectContaining({ + suppressDefaultToolProgressMessages: true, + }), + }), + ); + }); + + it("suppresses Telegram tool progress when explicitly disabled", async () => { + const draftStream = createDraftStream(); + createTelegramDraftStream.mockReturnValue(draftStream); + dispatchReplyWithBufferedBlockDispatcher.mockImplementation(async ({ replyOptions }) => { + await replyOptions?.onToolStart?.({ name: "exec", phase: "start" }); + await replyOptions?.onItemEvent?.({ progressText: "exec ls ~/Desktop" }); + return { queuedFinal: false }; + }); + + await dispatchWithContext({ + context: createContext(), + streamMode: "partial", + telegramCfg: { streaming: { preview: { toolProgress: false } } }, + }); + expect(draftStream.update).not.toHaveBeenCalled(); expect(dispatchReplyWithBufferedBlockDispatcher).toHaveBeenCalledWith( expect.objectContaining({ @@ -520,7 +547,7 @@ describe("dispatchTelegramMessage draft streaming", () => { ); }); - it("streams Telegram tool progress only when explicitly enabled", async () => { + it("keeps Telegram tool progress links inside code formatting", async () => { const draftStream = createDraftStream(); createTelegramDraftStream.mockReturnValue(draftStream); dispatchReplyWithBufferedBlockDispatcher.mockImplementation(async ({ replyOptions }) => { @@ -532,7 +559,6 @@ describe("dispatchTelegramMessage draft streaming", () => { await dispatchWithContext({ context: createContext(), streamMode: "partial", - telegramCfg: { streaming: { preview: { toolProgress: true } } }, }); const lastPreviewText = draftStream.update.mock.calls.at(-1)?.[0]; @@ -566,11 +592,32 @@ describe("dispatchTelegramMessage draft streaming", () => { const progressLine = lastPreviewText.split("\n").at(1) ?? ""; expect(lastPreviewText.length).toBeLessThan(340); - expect(progressLine).toMatch(/^• `{10}/); + expect(progressLine).toMatch(/^• `'{10}/); expect(progressLine).toContain("…"); expect(renderTelegramHtmlText(lastPreviewText)).not.toContain(" { + const draftStream = createDraftStream(); + createTelegramDraftStream.mockReturnValue(draftStream); + const breakoutProgress = `${"`".repeat(10)} [label](tg://user?id=123)`; + dispatchReplyWithBufferedBlockDispatcher.mockImplementation(async ({ replyOptions }) => { + await replyOptions?.onItemEvent?.({ progressText: breakoutProgress }); + return { queuedFinal: false }; + }); + + await dispatchWithContext({ + context: createContext(), + streamMode: "partial", + telegramCfg: { streaming: { preview: { toolProgress: true } } }, + }); + + const lastPreviewText = draftStream.update.mock.calls.at(-1)?.[0] ?? ""; + + expect(lastPreviewText).toContain(`• \`'''''''''' [label](tg://user?id=123)\``); + expect(renderTelegramHtmlText(lastPreviewText)).not.toContain(" { dispatchReplyWithBufferedBlockDispatcher.mockImplementation(async ({ dispatcherOptions }) => { await dispatcherOptions.deliver({ text: "Hello" }, { kind: "final" }); diff --git a/extensions/telegram/src/bot-message-dispatch.ts b/extensions/telegram/src/bot-message-dispatch.ts index 90fb2800afc..09ae28434fe 100644 --- a/extensions/telegram/src/bot-message-dispatch.ts +++ b/extensions/telegram/src/bot-message-dispatch.ts @@ -208,7 +208,6 @@ function resolveTelegramReasoningLevel(params: { } const MAX_PROGRESS_MARKDOWN_TEXT_CHARS = 300; -const MAX_PROGRESS_MARKDOWN_FENCE_CHARS = 10; function clipProgressMarkdownText(text: string): string { if (text.length <= MAX_PROGRESS_MARKDOWN_TEXT_CHARS) { @@ -219,12 +218,8 @@ function clipProgressMarkdownText(text: string): string { function formatProgressAsMarkdownCode(text: string): string { const clipped = clipProgressMarkdownText(text); - const maxBacktickRun = Math.max( - 0, - ...Array.from(clipped.matchAll(/`+/g), (match) => match[0].length), - ); - const fence = "`".repeat(Math.min(maxBacktickRun + 1, MAX_PROGRESS_MARKDOWN_FENCE_CHARS)); - return `${fence}${clipped}${fence}`; + const safe = clipped.replaceAll("`", "'"); + return `\`${safe}\``; } export const dispatchTelegramMessage = async ({ @@ -408,7 +403,7 @@ export const dispatchTelegramMessage = async ({ const answerLane = lanes.answer; const reasoningLane = lanes.reasoning; const previewToolProgressEnabled = - Boolean(answerLane.stream) && resolveChannelStreamingPreviewToolProgress(telegramCfg, false); + Boolean(answerLane.stream) && resolveChannelStreamingPreviewToolProgress(telegramCfg); let previewToolProgressSuppressed = false; let previewToolProgressLines: string[] = []; const pushPreviewToolProgress = (line?: string) => { diff --git a/extensions/telegram/src/config-ui-hints.ts b/extensions/telegram/src/config-ui-hints.ts index f8b1b2879f4..8444fe36047 100644 --- a/extensions/telegram/src/config-ui-hints.ts +++ b/extensions/telegram/src/config-ui-hints.ts @@ -63,7 +63,7 @@ export const telegramChannelConfigUiHints = { }, "streaming.preview.toolProgress": { label: "Telegram Draft Tool Progress", - help: "Show tool/progress activity in the live draft preview message (default: false). Enable only when visible Telegram progress updates are desired.", + help: "Show tool/progress activity in the live draft preview message (default: true when preview streaming is active). Set false to keep tool updates out of the edited Telegram preview.", }, "retry.attempts": { label: "Telegram Retry Attempts", diff --git a/src/config/bundled-channel-config-metadata.generated.ts b/src/config/bundled-channel-config-metadata.generated.ts index ea62fa27754..f113f03dcfc 100644 --- a/src/config/bundled-channel-config-metadata.generated.ts +++ b/src/config/bundled-channel-config-metadata.generated.ts @@ -893,6 +893,9 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ }, additionalProperties: false, }, + toolProgress: { + type: "boolean", + }, }, additionalProperties: false, }, @@ -2066,6 +2069,9 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ }, additionalProperties: false, }, + toolProgress: { + type: "boolean", + }, }, additionalProperties: false, }, @@ -3081,6 +3087,10 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ label: "Discord Draft Chunk Break Preference", help: "Preferred breakpoints for Discord draft chunks (paragraph | newline | sentence). Default: paragraph.", }, + "streaming.preview.toolProgress": { + label: "Discord Draft Tool Progress", + help: "Show tool/progress activity in the live draft preview message (default: true). Set false to keep tool updates as separate messages.", + }, "retry.attempts": { label: "Discord Retry Attempts", help: "Max retry attempts for outbound Discord API calls (default: 3).", @@ -9417,6 +9427,27 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ ], }, }, + groupAllowFrom: { + type: "array", + items: { + anyOf: [ + { + type: "string", + }, + { + type: "number", + }, + ], + }, + }, + dmPolicy: { + type: "string", + enum: ["open", "allowlist", "disabled"], + }, + groupPolicy: { + type: "string", + enum: ["open", "allowlist", "disabled"], + }, systemPrompt: { type: "string", }, @@ -9479,42 +9510,41 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ }, ], }, - tts: { + execApprovals: { type: "object", properties: { enabled: { - type: "boolean", + anyOf: [ + { + type: "boolean", + }, + { + type: "string", + const: "auto", + }, + ], }, - provider: { - type: "string", - }, - baseUrl: { - type: "string", - }, - apiKey: { - type: "string", - }, - model: { - type: "string", - }, - voice: { - type: "string", - }, - authStyle: { - type: "string", - enum: ["bearer", "api-key"], - }, - queryParams: { - type: "object", - propertyNames: { - type: "string", - }, - additionalProperties: { + approvers: { + type: "array", + items: { type: "string", }, }, - speed: { - type: "number", + agentFilter: { + type: "array", + items: { + type: "string", + }, + }, + sessionFilter: { + type: "array", + items: { + type: "string", + }, + }, + target: { + type: "string", + enum: ["dm", "channel", "both"], }, }, additionalProperties: false, @@ -9637,6 +9667,27 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ ], }, }, + groupAllowFrom: { + type: "array", + items: { + anyOf: [ + { + type: "string", + }, + { + type: "number", + }, + ], + }, + }, + dmPolicy: { + type: "string", + enum: ["open", "allowlist", "disabled"], + }, + groupPolicy: { + type: "string", + enum: ["open", "allowlist", "disabled"], + }, systemPrompt: { type: "string", }, @@ -9699,6 +9750,45 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ }, ], }, + execApprovals: { + type: "object", + properties: { + enabled: { + anyOf: [ + { + type: "boolean", + }, + { + type: "string", + const: "auto", + }, + ], + }, + approvers: { + type: "array", + items: { + type: "string", + }, + }, + agentFilter: { + type: "array", + items: { + type: "string", + }, + }, + sessionFilter: { + type: "array", + items: { + type: "string", + }, + }, + target: { + type: "string", + enum: ["dm", "channel", "both"], + }, + }, + additionalProperties: false, + }, }, additionalProperties: {}, }, @@ -10866,6 +10956,9 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ }, additionalProperties: false, }, + toolProgress: { + type: "boolean", + }, }, additionalProperties: false, }, @@ -11775,6 +11868,9 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ }, additionalProperties: false, }, + toolProgress: { + type: "boolean", + }, }, additionalProperties: false, }, @@ -12311,6 +12407,10 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ label: "Slack Native Streaming", help: "Enable native Slack text streaming (chat.startStream/chat.appendStream/chat.stopStream) when channels.slack.streaming.mode is partial (default: true). Requires a reply thread target; top-level DMs stay on the non-thread fallback path.", }, + "streaming.preview.toolProgress": { + label: "Slack Draft Tool Progress", + help: "Show tool/progress activity in the live draft preview message (default: true). Set false to keep tool updates as separate messages.", + }, "thread.historyScope": { label: "Slack Thread History Scope", help: 'Scope for Slack thread history context ("thread" isolates per thread; "channel" reuses channel history).', @@ -13058,6 +13158,9 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ }, additionalProperties: false, }, + toolProgress: { + type: "boolean", + }, }, additionalProperties: false, }, @@ -14096,6 +14199,9 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ }, additionalProperties: false, }, + toolProgress: { + type: "boolean", + }, }, additionalProperties: false, }, @@ -14498,6 +14604,10 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ label: "Telegram Draft Chunk Break Preference", help: "Preferred breakpoints for Telegram draft chunks (paragraph | newline | sentence).", }, + "streaming.preview.toolProgress": { + label: "Telegram Draft Tool Progress", + help: "Show tool/progress activity in the live draft preview message (default: true when preview streaming is active). Set false to keep tool updates out of the edited Telegram preview.", + }, "retry.attempts": { label: "Telegram Retry Attempts", help: "Max retry attempts for outbound Telegram API calls (default: 3).",