The group-mentions ack gate returned false whenever a group did not require mentions, conflating group policy with whether this message mentioned the agent. Under the default scope, mentioning the agent in a group configured to answer everything produced no ack at all, so the user had no signal the turn was picked up until the reply arrived.
Behavior change on a default path: those groups now show the 👀 ack (and the lifecycle status reactions when messages.statusReactions.enabled is true) on mentions. Mention-required groups, group-all, unmentioned messages, and off/none are unchanged. The gate no longer reads requireMention, so the parameter is gone from the shared type and all channel call sites.
Proven live on Telegram across three configurations, including a no-mention control confirming group-mentions did not become group-all.
resolveChannelStreamingPreviewToolProgress guessed a mode when streaming.mode
was unset, so it could never be right for every channel: guessing "partial"
dropped Discord and Telegram's explicit progress.toolProgress opt-out, and my
earlier switch to "progress" broke it the other way for Slack, Mattermost, and
Teams, whose real default is partial. With both keys configured and disagreeing,
the wrong one won.
The helper has no channel identity, so it stops guessing and takes the mode the
caller already resolved. All nine call sites pass theirs. Omitting it keeps the
previous configured-mode reading, so untouched callers behave exactly as before.
Reported by ClawSweeper on #116143.
A status headline replaced the rolling tool lines instead of sitting above
them, so a default Discord draft showed one preamble sentence for an entire
tool-heavy turn. Operators reached for `/verbose` to see any activity, which
delivers durable per-tool-call messages and floods the channel.
- Render the headline above the lines; both stay visible in one message.
- Shorten the start gate from 5s to 1.5s. The gate only creates the draft
when the timer fires and finalize cancels it, so quick answers still post
no draft while a 3s tool turn stops being silent.
- Drop Discord's label-gated tool-progress default so
resolveChannelStreamingPreviewToolProgress is the single owner. An explicit
`toolProgress: false` still silences the lines.
- Resolve that toggle against a "progress" mode guess when `streaming.mode`
is unset, so the progress-draft channels stop ignoring an explicit
`progress.toolProgress` opt-out.
Telegram now defaults to `streaming.mode: "progress"` like Discord; set
`"partial"` to keep streamed answer text. Its renderer draws work lines from
the compositor's structured lines, so `rendersRollingLinesNatively` keeps
them out of the composed text rather than printing every line twice.
Track full dispatch completion separately for error handling and shutdown drain while allowing same-session follow-ups to steer active runs. Fixes#113180.
Co-authored-by: Taksh <takshkothari09@gmail.com>
* fix(slack): make MPIM app mentions type-safe
Resolve typeless app mentions through explicit metadata, the scoped event cache, then conversations.info so modern C-prefixed MPIMs cannot be misclassified by event ordering. Add a real Slack QA scenario that requires one reply and cleans up its temporary MPIM.
Co-authored-by: moeealii <75953662+moeealii@users.noreply.github.com>
* fix(slack): type unresolved mention metadata
* fix(slack): satisfy MPIM type contracts
---------
Co-authored-by: moeealii <75953662+moeealii@users.noreply.github.com>
app_mention events carry no channel_type, and modern Slack group DMs
(mpims) use C-prefixed channel ids, so prefix inference classifies them
as channels. Mentions in group DMs then bypass the handler's im/mpim
dedupe guard and are processed down the channel path — misrouting
replies, and double-handling alongside message.mpim when dm.groupEnabled
is on.
Confirm against the event-carried type cache and, on a miss, the cached
conversations.info lookup before treating a channel_type-less mention as
a channel mention. Only an authoritative "mpim" answer changes behavior;
a C-prefixed id can never be an im.
Fixes#115463
Co-authored-by: moeealii <>
* fix(slack): cap chat.update edit text at the 4000-char limit, not the 8000 send limit
updateMessageSlack truncated the edit text to SLACK_TEXT_LIMIT (8000), but Slack chat.update
rejects text longer than 4000 characters with msg_too_long (documented in limits.ts). Every
other edit path (actions.ts, edit-text.ts, message-action-dispatch.ts, preview-finalize.ts)
uses SLACK_EDIT_TEXT_LIMIT (4000); updateMessageSlack was the lone outlier, so a long
question-delivery status edit failed instead of landing. Use the edit limit.
* fix(slack): enforce edit text byte limits
Co-authored-by: MatthewSynthia <matthewsynthia@users.noreply.github.com>
* fix(slack): preserve prepared edit text within limits
Co-authored-by: MatthewSynthia <matthewsynthia@users.noreply.github.com>
---------
Co-authored-by: MatthewSynthia <matthewsynthia@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Preserve the current dedicated Slack read-client architecture and stop probe retries or HTTP 429 delays from surviving the shared health deadline. Verify stalled sockets, dropped-request background retries, immediate rate-limit rejection, and trickling response bodies with real loopback transport. Fixes#106565.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: zhangqueping <3436352+zhangqueping@users.noreply.github.com>
* fix(slack): preserve rendered presentations across cloning
* test(slack): cover serialized presentation provenance
* test(slack): prove restart fallback for rendered payloads
- Reload the Slack adapter to simulate a runtime restart.
- Verify stale rendered provenance falls back to text without blocks.
* fix(slack): fail closed on invalid rendered metadata
---------
Co-authored-by: Pavonis <pavonis@martian.engineering>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* refactor(slack): retire inline interactive directives
Release note: Slack configs using channels.slack.capabilities.interactiveReplies, including per-account settings, are removed by openclaw doctor --fix. Slack-only [[slack_buttons:...]] and [[slack_select:...]] markup is no longer parsed; emit typed presentation buttons and selects instead.
* docs: refresh Slack docs map
* refactor(channels): declare thread addressing as a channel trait
* fix(tasks): require declared thread capability for direct parent-review delivery
* docs(tasks): record the no-target-parsing tradeoff at the delivery gate
* refactor(prompt): plain inbound context labels with a provenance marker
Replaces trust-worded inbound context labels ("(untrusted metadata)",
"(untrusted, for context)") with plain labels plus a fixed provenance
marker suffix appended to every OpenClaw-injected context header.
Detection keys on the marker, not label text, so strippers stay correct
across UI, TUI, replay, /trace segmentation, memory recall, and the Swift
chat preprocessor. Drops sanitizeInboundSystemTags in favor of the marker
boundary plus trusted system-prompt narration.
Renames the untrusted-named plugin SDK context identifiers to
channel-provenance names, keeping deprecated aliases registered for
removal after 2026-09-08.
Adds `openclaw doctor --fix` migrations that rewrite legacy inbound
labels in stored SQLite transcripts and purge legacy envelope-
contaminated LanceDB recall rows.
* fix(ci): resolve gate failures for plain inbound context labels
- doctor sqlite readers: open read-only connections via openNodeSqliteDatabase
so the Kysely connection-boundary guardrail holds; unexport the now-internal
transcript snapshot type (Knip unused-export gate).
- compat registry: split the record table into registry-records.ts and
plugin-sdk-subpath-records.ts. The new compat record pushed registry.ts past
the 700-line oxlint cap; suppressions are disallowed, so follow the existing
sibling record-module pattern. Public exports and PluginCompatCode literals
unchanged.
- acp-runtime test: assert current finalization behavior (newline normalization
only). The bracket de-fang and System: rewrite it expected were removed with
sanitizeInboundSystemTags; forged system lines are neutralized at the
system-event queue, the single chokepoint feeding the System:-per-line render.
- regenerate docs_map and the plugin SDK API baseline manifest.
* fix(prompt): harden inbound context label migration and drop in-band sanitizer
Review follow-ups on the plain-label + provenance-marker change:
- Remove src/security/system-tags.ts. Rewriting inbound text to neutralize
look-alike `System:`/`[System]` markers corrupted legitimate user text and is
not a real injection boundary; role separation plus external-content wrapping
is. Explicit product decision, recorded at the system-event queue.
- Narrow the LanceDB legacy-row purge so it cannot delete benign memories. It
now requires a complete known legacy sentinel line, a legacy label followed by
a fenced JSON body, or the complete legacy external-content header. The prior
predicates matched ordinary prose such as `Notes (untrusted metadata):`, and
deletion is irreversible.
- Make explicit-empty canonical ChannelStructuredContext win over the deprecated
alias via a present/absent result instead of collapsing `[]` to undefined.
- Keep `\r?` in the active-memory doctor rule. It is the only rule spanning the
header's line break, migrated assistant rows skip newline normalization, and
without it the marked-header replace wins and the body strips to empty. Added
a CRLF regression test.
- Fix stale comments that described removed behavior, and cover the Swift
prose-block strip path.
Claude-Session: https://claude.ai/code/session_01WNzsPddQmxy9Y7jKD4wAxH