Skip to content

Slack: support Block Kit payloads in agent replies#44592

Merged
vincentkoc merged 4 commits intomainfrom
vincentkoc-code/slack-block-kit-agent-replies
Mar 13, 2026
Merged

Slack: support Block Kit payloads in agent replies#44592
vincentkoc merged 4 commits intomainfrom
vincentkoc-code/slack-block-kit-agent-replies

Conversation

@vincentkoc
Copy link
Member

Summary

  • Problem: Slack transport already supports Block Kit, but agent replies only flowed through the plain text/media outbound path.
  • Why it matters: issue [Feature]: Slack Block Kit support for agent messages #12602 asks for richer Slack agent output, and plain-text-only replies block the first usable slice.
  • What changed: ReplyPayload.channelData.slack.blocks is now parsed and forwarded through the Slack outbound adapter to sendMessageSlack.
  • What did NOT change (scope boundary): this PR does not add generic interactive action handling for arbitrary Block Kit buttons/selects.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

User-visible / Behavior Changes

Agent replies can now send Slack Block Kit payloads by populating channelData.slack.blocks on the reply payload. Plain text Slack replies are unchanged.

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No
  • If any Yes, explain risk + mitigation:

Repro + Verification

Environment

  • OS: macOS
  • Runtime/container: Node 22 + pnpm workspace
  • Model/provider: N/A
  • Integration/channel (if any): Slack outbound adapter
  • Relevant config (redacted): N/A

Steps

  1. Create a Slack reply payload with channelData.slack.blocks.
  2. Deliver it through the standard outbound Slack adapter path.
  3. Verify the adapter forwards blocks to sendMessageSlack and rejects malformed block payloads.

Expected

  • Slack Block Kit payloads are sent through the standard agent reply path.
  • Invalid block payloads fail before sending.

Actual

  • Matches expected in targeted tests.

Evidence

Attach at least one:

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios: targeted Vitest coverage for Slack outbound payloads, Slack outbound hook wiring, and low-level Slack block sending.
  • Edge cases checked: channelData-only payloads, JSON-encoded blocks, invalid blocks.
  • What you did not verify: live Slack workspace delivery.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

If a bot review conversation is addressed by this PR, resolve that conversation yourself. Do not leave bot review conversation cleanup for maintainers.

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No
  • If yes, exact upgrade steps:

Failure Recovery (if this breaks)

  • How to disable/revert this change quickly: revert this PR or stop emitting channelData.slack.blocks.
  • Files/config to restore: src/channels/plugins/outbound/slack.ts
  • Known bad symptoms reviewers should watch for: Slack replies failing when malformed channelData.slack.blocks is produced upstream.

Risks and Mitigations

  • Risk: upstream reply producers can now hand malformed Slack block payloads into the adapter.
    • Mitigation: block payloads are validated via existing Slack block parsing/validation before send.

AI Assistance

  • AI-assisted: yes
  • Testing: targeted tests run locally

@vincentkoc vincentkoc self-assigned this Mar 13, 2026
@openclaw-barnacle openclaw-barnacle bot added size: S maintainer Maintainer-authored PR labels Mar 13, 2026
@vincentkoc vincentkoc marked this pull request as ready for review March 13, 2026 03:17
@aisle-research-bot
Copy link

aisle-research-bot bot commented Mar 13, 2026

🔒 Aisle Security Analysis

✅ We scanned this PR and did not find any security vulnerabilities.

Aisle supplements but does not replace security review.


Analyzed PR: #44592 at commit 6579b9d

Last updated on: 2026-03-13T04:36:05Z

@vincentkoc vincentkoc merged commit 42efd98 into main Mar 13, 2026
6 checks passed
@vincentkoc vincentkoc deleted the vincentkoc-code/slack-block-kit-agent-replies branch March 13, 2026 03:19
@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 13, 2026

Greptile Summary

This PR extends the Slack outbound adapter to forward Block Kit payloads when channelData.slack.blocks is populated on a ReplyPayload, closing the gap identified in issue #12602. When no blocks are present the existing sendTextMediaPayload path is used unchanged, making the change fully backward compatible.

Key changes:

  • resolveSlackBlocks extracts and validates blocks from channelData.slack using the existing parseSlackBlocksInput helper, supporting both array and JSON-string forms and throwing early on malformed input.
  • sendPayload in slackOutbound branches on the presence of validated blocks and delegates to sendSlackOutboundMessage (which already supports a blocks parameter) rather than the text/media path.
  • Three new Vitest cases cover the happy path, JSON-string decoding, and invalid block rejection; the missing expect/it imports in the test file are also fixed.
  • One edge case is unguarded: a payload combining channelData.slack.blocks and mediaUrl passes both into sendSlackOutboundMessage, and the resulting incompatibility error is only raised inside sendMessageSlack after the hook runner has already been invoked. An early guard and a matching test would make the constraint explicit.

Confidence Score: 4/5

  • This PR is safe to merge; changes are well-scoped, backward compatible, and covered by targeted tests.
  • The implementation is clean and leans on existing, already-tested validation (parseSlackBlocksInput, validateSlackBlocksArray). The only gap is the blocks + mediaUrl combination, which does ultimately error but at a lower level than ideal and without a test — a minor style concern, not a correctness regression.
  • Pay minor attention to src/channels/plugins/outbound/slack.ts around the blocks + mediaUrl edge case in sendPayload.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: src/channels/plugins/outbound/slack.ts
Line: 115-127

Comment:
**Unhandled `blocks` + `mediaUrl` combination**

When a payload contains both `channelData.slack.blocks` and `mediaUrl`, this branch passes both to `sendSlackOutboundMessage`, which in turn passes both to `sendMessageSlack`. Inside `sendMessageSlack`, the check at the `blocks` handling path throws `"Slack send does not support blocks with mediaUrl"` — but only after the hook runner has already been invoked and any side-effects from hooks have taken place.

Consider detecting and rejecting the conflicting combination early at the adapter level, before any I/O:

```suggestion
    if (ctx.payload.mediaUrl) {
      throw new Error(
        "Slack send does not support blocks with mediaUrl; provide either blocks or mediaUrl",
      );
    }
    return await sendSlackOutboundMessage({
      cfg: ctx.cfg,
      to: ctx.to,
      text: ctx.payload.text ?? "",
      mediaLocalRoots: ctx.mediaLocalRoots,
      blocks,
      accountId: ctx.accountId,
      deps: ctx.deps,
      replyToId: ctx.replyToId,
      threadId: ctx.threadId,
      identity: ctx.identity,
    });
```

This edge case is also not covered by the new tests, so adding a test for `blocks + mediaUrl` would help document the constraint explicitly.

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

Last reviewed commit: 6579b9d

Comment on lines +115 to +127
return await sendSlackOutboundMessage({
cfg: ctx.cfg,
to: ctx.to,
text: ctx.payload.text ?? "",
mediaUrl: ctx.payload.mediaUrl,
mediaLocalRoots: ctx.mediaLocalRoots,
blocks,
accountId: ctx.accountId,
deps: ctx.deps,
replyToId: ctx.replyToId,
threadId: ctx.threadId,
identity: ctx.identity,
});
Copy link
Contributor

Choose a reason for hiding this comment

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

Unhandled blocks + mediaUrl combination

When a payload contains both channelData.slack.blocks and mediaUrl, this branch passes both to sendSlackOutboundMessage, which in turn passes both to sendMessageSlack. Inside sendMessageSlack, the check at the blocks handling path throws "Slack send does not support blocks with mediaUrl" — but only after the hook runner has already been invoked and any side-effects from hooks have taken place.

Consider detecting and rejecting the conflicting combination early at the adapter level, before any I/O:

Suggested change
return await sendSlackOutboundMessage({
cfg: ctx.cfg,
to: ctx.to,
text: ctx.payload.text ?? "",
mediaUrl: ctx.payload.mediaUrl,
mediaLocalRoots: ctx.mediaLocalRoots,
blocks,
accountId: ctx.accountId,
deps: ctx.deps,
replyToId: ctx.replyToId,
threadId: ctx.threadId,
identity: ctx.identity,
});
if (ctx.payload.mediaUrl) {
throw new Error(
"Slack send does not support blocks with mediaUrl; provide either blocks or mediaUrl",
);
}
return await sendSlackOutboundMessage({
cfg: ctx.cfg,
to: ctx.to,
text: ctx.payload.text ?? "",
mediaLocalRoots: ctx.mediaLocalRoots,
blocks,
accountId: ctx.accountId,
deps: ctx.deps,
replyToId: ctx.replyToId,
threadId: ctx.threadId,
identity: ctx.identity,
});

This edge case is also not covered by the new tests, so adding a test for blocks + mediaUrl would help document the constraint explicitly.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/channels/plugins/outbound/slack.ts
Line: 115-127

Comment:
**Unhandled `blocks` + `mediaUrl` combination**

When a payload contains both `channelData.slack.blocks` and `mediaUrl`, this branch passes both to `sendSlackOutboundMessage`, which in turn passes both to `sendMessageSlack`. Inside `sendMessageSlack`, the check at the `blocks` handling path throws `"Slack send does not support blocks with mediaUrl"` — but only after the hook runner has already been invoked and any side-effects from hooks have taken place.

Consider detecting and rejecting the conflicting combination early at the adapter level, before any I/O:

```suggestion
    if (ctx.payload.mediaUrl) {
      throw new Error(
        "Slack send does not support blocks with mediaUrl; provide either blocks or mediaUrl",
      );
    }
    return await sendSlackOutboundMessage({
      cfg: ctx.cfg,
      to: ctx.to,
      text: ctx.payload.text ?? "",
      mediaLocalRoots: ctx.mediaLocalRoots,
      blocks,
      accountId: ctx.accountId,
      deps: ctx.deps,
      replyToId: ctx.replyToId,
      threadId: ctx.threadId,
      identity: ctx.identity,
    });
```

This edge case is also not covered by the new tests, so adding a test for `blocks + mediaUrl` would help document the constraint explicitly.

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

Copy link

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

ℹ️ 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 +118 to +120
text: ctx.payload.text ?? "",
mediaUrl: ctx.payload.mediaUrl,
mediaLocalRoots: ctx.mediaLocalRoots,

Choose a reason for hiding this comment

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

P2 Badge Preserve media attachments in blocks sendPayload path

When channelData.slack.blocks is present, sendPayload bypasses sendTextMediaPayload and only forwards ctx.payload.mediaUrl, so payloads that use mediaUrls (the normal multi-attachment shape) silently drop all attachments and send only the blocks/text message. This is a regression from the prior path where mediaUrls were iterated and delivered, and it will break replies that include both Block Kit metadata and attachment lists.

Useful? React with 👍 / 👎.

frankekn pushed a commit to jmcte/openclaw that referenced this pull request Mar 13, 2026
* Slack: route reply blocks through outbound adapter

* Slack: cover Block Kit outbound payloads

* Changelog: add Slack Block Kit agent reply entry
@theDanielJLewis
Copy link

THANK YOU!

dvrshil pushed a commit to JosepLee/openclaw that referenced this pull request Mar 14, 2026
* Slack: route reply blocks through outbound adapter

* Slack: cover Block Kit outbound payloads

* Changelog: add Slack Block Kit agent reply entry
sbezludny pushed a commit to sbezludny/openclaw that referenced this pull request Mar 14, 2026
* Slack: route reply blocks through outbound adapter

* Slack: cover Block Kit outbound payloads

* Changelog: add Slack Block Kit agent reply entry
Interstellar-code pushed a commit to Interstellar-code/operator1 that referenced this pull request Mar 16, 2026
* Slack: route reply blocks through outbound adapter

* Slack: cover Block Kit outbound payloads

* Changelog: add Slack Block Kit agent reply entry

(cherry picked from commit 42efd98)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

maintainer Maintainer-authored PR size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants