Skip to content

feat(channels): stream tool progress into preview edits#69611

Merged
thewilloftheshadow merged 2 commits into
openclaw:mainfrom
thewilloftheshadow:shadow/stream-verbose
Apr 21, 2026
Merged

feat(channels): stream tool progress into preview edits#69611
thewilloftheshadow merged 2 commits into
openclaw:mainfrom
thewilloftheshadow:shadow/stream-verbose

Conversation

@thewilloftheshadow
Copy link
Copy Markdown
Member

No description provided.

@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot Bot commented Apr 21, 2026

🔒 Aisle Security Analysis

We found 2 potential security issue(s) in this PR:

# Severity Title
1 🟡 Medium Discord streamed preview tool-progress can trigger unwanted mentions (@​everyone/@​here) due to missing allowed_mentions suppression
2 🟡 Medium Slack streamed preview tool-progress can inject special mentions (<!channel>/<!here>/@​channel) due to lack of mrkdwn escaping
1. 🟡 Discord streamed preview tool-progress can trigger unwanted mentions (@​everyone/@​here) due to missing allowed_mentions suppression
Property Value
Severity Medium
CWE CWE-116
Location extensions/discord/src/draft-stream.ts:86-91

Description

In Discord preview streaming, tool/progress payload fields are concatenated into a preview message and sent via createDiscordDraftStream without any mention-suppression.

  • Inputs: payload.name, payload.progressText, payload.summary, payload.title, etc. from tool/progress events
  • Transformation: only whitespace normalization (replace(/\s+/g, " ").trim()), no escaping/neutralization of @​everyone, @​here, <@...>, <@&...>
  • Sink: Discord REST API POST /channels/{channelId}/messages and PATCH /channels/{channelId}/messages/{messageId} with body: { content: trimmed } and no allowed_mentions field

This allows attacker-controlled or tool-controlled strings to inject mentions into streamed preview updates, potentially causing notification spam or social-engineering content in channels where previews are enabled.

Recommendation

Suppress mentions for preview/draft messages (and ideally for all bot messages unless explicitly needed).

For Discord REST payloads, set allowed_mentions to an empty parse list when sending/editing preview content:

await rest.post(Routes.channelMessages(channelId), {
  body: {
    content: trimmed,
    allowed_mentions: { parse: [] },
    ...(messageReference ? { message_reference: messageReference } : {}),
  },
});

await rest.patch(Routes.channelMessage(channelId, streamMessageId), {
  body: {
    content: trimmed,
    allowed_mentions: { parse: [] },
  },
});

Additionally consider neutralizing @​everyone/@​here/<@ sequences in the tool-progress lines themselves as a defense-in-depth measure.

2. 🟡 Slack streamed preview tool-progress can inject special mentions (//@​channel) due to lack of mrkdwn escaping
Property Value
Severity Medium
CWE CWE-116
Location extensions/slack/src/monitor/message-handler/dispatch.ts:677-694

Description

In Slack preview streaming, tool/progress payload fields are concatenated into a preview message and sent/edited via SlackDraftStream (sendMessageSlack / editSlackMessage) as plain text.

  • Inputs: payload.name, payload.progressText, payload.summary, payload.title, etc. from tool/progress events
  • Transformation: only whitespace normalization, no escaping of Slack mrkdwn entities (e.g., <!channel>, <!here>, <@​U…>, <#C…>), links, or formatting
  • Sink: chat.postMessage / chat.update through sendMessageSlack/editSlackMessage

This can be abused to trigger channel-wide notifications or craft misleading formatted preview updates if an attacker can influence these strings (directly or indirectly via tool output).

Recommendation

Escape/neutralize Slack mrkdwn and special mention tokens before calling draftStream.update for tool-progress previews.

For example, reuse an escaping helper (there is already escapeSlackMrkdwn):

import { escapeSlackMrkdwn } from "../mrkdwn.js";

const normalized = escapeSlackMrkdwn(line?.replace(/\s+/g, " ").trim() ?? "");

Additionally, explicitly neutralize <!...> patterns:

const safe = normalized.replace(/<!/g, "<\\!");

Apply the same escaping when editing existing preview messages (draft stream).


Analyzed PR: #69611 at commit 170293f

Last updated on: 2026-04-21T16:37:55Z

@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation channel: discord Channel integration: discord channel: slack Channel integration: slack channel: telegram Channel integration: telegram size: M maintainer Maintainer-authored PR labels Apr 21, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 21, 2026

Greptile Summary

This PR streams live tool/activity progress into channel preview draft messages (Discord, Slack, Telegram) that support edit-in-place streaming. A new streaming.preview.toolProgress boolean config field (default true) controls the feature. When enabled and a draft stream is active, tool-start, plan-update, approval, command-output, and patch-summary events are funnelled into a pushPreviewToolProgress function that edits the draft to show Working… • tool: X • …, replacing the previous status. Once actual LLM content begins streaming, the previewToolProgressSuppressed flag prevents further progress updates and the draft switches to the real reply text. suppressDefaultToolProgressMessages is set to true so the legacy "Working: X" text messages are not also emitted.

Confidence Score: 5/5

Safe to merge; no P0/P1 issues found and the feature logic is sound across all three channel implementations.

State management (previewToolProgressSuppressed, previewToolProgressLines, hasStreamedMessage) is correctly reset in onAssistantMessageStart and onReasoningEnd. The config opt-out path gracefully falls back to default progress messages. The only finding is a P2 style concern about code duplication in the three pushPreviewToolProgress implementations.

No files require special attention; the duplicated helper logic across the three channel dispatch files is a minor maintenance concern but not a bug.

Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/discord/src/monitor/message-handler.process.ts
Line: 608-634

Comment:
**Duplicated `pushPreviewToolProgress` across all three channel implementations**

The logic in `pushPreviewToolProgress` is nearly identical across Discord (`message-handler.process.ts`), Slack (`dispatch.ts`), and Telegram (`bot-message-dispatch.ts`): normalize the label, deduplicate against the previous entry, cap the list at 8, format as `"Working…\n• item1\n• item2"`, and call `draftStream.update(...)`. The only channel-specific differences are how `hasStreamedMessage` and `draftText`/`lastPartialText` are reset.

Extracting a shared `buildPreviewToolProgressText(lines: string[]): string` helper (or the full queue-management logic as a factory) into `channel-streaming.ts` would reduce the surface area for divergence over time.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "feat(channels): stream tool progress int..." | Re-trigger Greptile

Comment on lines +608 to +634
const previewToolProgressEnabled =
Boolean(draftStream) && resolveChannelStreamingPreviewToolProgress(discordConfig);
let previewToolProgressSuppressed = false;
let previewToolProgressLines: string[] = [];

const pushPreviewToolProgress = (line?: string) => {
if (!draftStream || !previewToolProgressEnabled || previewToolProgressSuppressed) {
return;
}
const normalized = line?.replace(/\s+/g, " ").trim();
if (!normalized) {
return;
}
const previous = previewToolProgressLines.at(-1);
if (previous === normalized) {
return;
}
previewToolProgressLines = [...previewToolProgressLines, normalized].slice(-8);
const previewText = ["Working…", ...previewToolProgressLines.map((entry) => `• ${entry}`)].join(
"\n",
);
lastPartialText = previewText;
draftText = previewText;
hasStreamedMessage = true;
draftChunker?.reset();
draftStream.update(previewText);
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Duplicated pushPreviewToolProgress across all three channel implementations

The logic in pushPreviewToolProgress is nearly identical across Discord (message-handler.process.ts), Slack (dispatch.ts), and Telegram (bot-message-dispatch.ts): normalize the label, deduplicate against the previous entry, cap the list at 8, format as "Working…\n• item1\n• item2", and call draftStream.update(...). The only channel-specific differences are how hasStreamedMessage and draftText/lastPartialText are reset.

Extracting a shared buildPreviewToolProgressText(lines: string[]): string helper (or the full queue-management logic as a factory) into channel-streaming.ts would reduce the surface area for divergence over time.

Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/discord/src/monitor/message-handler.process.ts
Line: 608-634

Comment:
**Duplicated `pushPreviewToolProgress` across all three channel implementations**

The logic in `pushPreviewToolProgress` is nearly identical across Discord (`message-handler.process.ts`), Slack (`dispatch.ts`), and Telegram (`bot-message-dispatch.ts`): normalize the label, deduplicate against the previous entry, cap the list at 8, format as `"Working…\n• item1\n• item2"`, and call `draftStream.update(...)`. The only channel-specific differences are how `hasStreamedMessage` and `draftText`/`lastPartialText` are reset.

Extracting a shared `buildPreviewToolProgressText(lines: string[]): string` helper (or the full queue-management logic as a factory) into `channel-streaming.ts` would reduce the surface area for divergence over time.

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fe88adcd5e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +928 to +931
const hasExecApproval =
execApproval && typeof execApproval === "object" && !Array.isArray(execApproval);
if (!hasMedia && !hasExecApproval && deliveryPayload.isError !== true) {
return;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve approval-unavailable tool payloads

Do not drop every text-only tool payload when suppressDefaultToolProgressMessages is enabled: this branch only preserves media, errors, or payloads with channelData.execApproval, but buildExecApprovalUnavailableReplyPayload(...) produces plain text guidance without execApproval. In preview-enabled channels (where suppression is now enabled by default), users can lose the only actionable “how to enable approvals” message and only see transient preview status instead.

Useful? React with 👍 / 👎.

@thewilloftheshadow thewilloftheshadow self-assigned this Apr 21, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 170293f6b3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

draftStream.update(
["Working…", ...previewToolProgressLines.map((entry) => `• ${entry}`)].join("\n"),
);
hasStreamedMessage = true;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid rotating Slack draft after tool-progress-only updates

Marking tool-progress preview writes as hasStreamedMessage makes the existing onDraftBoundary handler call forceNewMessage() on the next onAssistantMessageStart/onReasoningEnd, which leaves the prior "Working…" preview behind and posts the answer in a second message. In a normal tool-call turn (tool events before answer text) with streaming.preview.toolProgress enabled (default), this regresses from a single evolving preview to noisy duplicate messages in the thread.

Useful? React with 👍 / 👎.

@thewilloftheshadow thewilloftheshadow merged commit 38aaa23 into openclaw:main Apr 21, 2026
93 checks passed
@thewilloftheshadow
Copy link
Copy Markdown
Member Author

thewilloftheshadow commented Apr 21, 2026

Landed via temp rebase onto main.

  • Gate: CI checks on PR head (used instead of local pnpm lint/build/test)
  • Land commit: 170293f
  • Merge commit: 38aaa23

Thanks @thewilloftheshadow!

gdibble pushed a commit to gdibble/openclaw that referenced this pull request Apr 21, 2026
medikoo pushed a commit to medikoo/openclaw that referenced this pull request Apr 24, 2026
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
zhonghe0615 pushed a commit to zhonghe0615/openclaw that referenced this pull request May 7, 2026
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
globalcaos pushed a commit to globalcaos/tinkerclaw that referenced this pull request May 13, 2026
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: discord Channel integration: discord channel: slack Channel integration: slack channel: telegram Channel integration: telegram docs Improvements or additions to documentation maintainer Maintainer-authored PR size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant