Skip to content

feat(gateway): capability-gated outbound Slack — reactions, file upload, thread replies#1851

Merged
kgabryje merged 3 commits into
mainfrom
gateway-slack-outbound-richness
Jul 10, 2026
Merged

feat(gateway): capability-gated outbound Slack — reactions, file upload, thread replies#1851
kgabryje merged 3 commits into
mainfrom
gateway-slack-outbound-richness

Conversation

@kgabryje

@kgabryje kgabryje commented Jul 8, 2026

Copy link
Copy Markdown
Member

Summary

Adds richer OUTBOUND Slack capabilities for gateway agent sessions (#1729), reusing #1848's capability/branch-binding model exactly — no parallel pattern.

1. Capability model (packages/core/src/types/gateway.ts, slack-manifest.ts): adds reactions and file_upload to SlackAgentToolsConfig / SLACK_AGENT_TOOL_DEFAULTS (both default disabled — opt-in, add write scopes) and SLACK_AGENT_TOOL_SCOPES (reactionsreactions:write, file_uploadfiles:write). Wired through agor_gateway_slack_manifest_generate and the create-wizard/edit-form UI toggles, same single-source-of-truth as channel_history.

2. Connector methods (packages/core/src/gateway/connectors/slack.ts): addReaction/removeReaction (reactions.add/reactions.remove) and uploadFile (files.uploadV2), mirroring sendSlackMessage's error-handling style.

3. New MCP tools (apps/agor-daemon/src/mcp/tools/gateway-channels.ts), each capability-gated + branch-bound via a shared resolveGatewaySlackToolTarget resolver:

  • agor_gateway_slack_reaction_add / agor_gateway_slack_reaction_remove (capability reactions)
  • agor_gateway_slack_file_upload (capability file_upload) — path is constrained to either the daemon upload directory or a path relative to the calling session's branch workspace (reusing resolveBranchWorkspacePath's containment checks, same pattern as other branch-workspace file tools). No arbitrary host filesystem reads.

agor_gateway_slack_channel_history_get (#1848) was also refactored onto this same shared resolver — it previously had its own inline copy of the identical capability-gate + branch-binding logic; that duplication is now gone and all four Slack conversation-targeting tools enforce the rules from one place. Verified risk-free: every existing channel_history_get test only asserts error-message substrings, all of which are shared between the old and new error text (confirmed by the full 66-test suite passing unchanged).

4. Thread replies on emit: agor_gateway_emit_message gains an optional threadTs param, plumbed straight to the connector's existing thread_ts support — pure exposure, no new scope, no new capability. Omitting it keeps current behavior (fresh thread/DM per emit).

5. Input hardening (post-review): slackChannelId/ts/emoji on the reaction tools now get cheap regex format validation (^[A-Z0-9]+$, ^\d+\.\d+$, ^[a-z0-9_+'-]+$) so malformed input is rejected by the schema before any Slack call, instead of just a non-empty check.

Out of scope

Interactive Block Kit components (buttons/select menus) are deferred — they need Socket Mode interactivity handling, a separate feature. Reactions cover #1729's reaction/voting use case. A raw blocks passthrough on emit was also skipped to keep this PR's surface tight.

Stack

Stacked on #1848#1847. Base is gateway-slack-read-tools-capabilities, not main — this branch will be rebased onto main once both merge.

Test plan

  • pnpm typecheck / pnpm lint / pnpm check:shortid — green
  • turbo run build — green
  • Full test suite (daemon, core, ui, cli, client) — green; added capability-gating, branch-binding, path-constraint, manifest-scope, threadTs-plumbing, and format-validation tests mirroring feat(gateway): capability-gated Slack read tools (channel history) + capability model #1848's coverage
  • file_upload escape rejections (relative .. traversal, absolute-path symlink escape, null-byte path) are exercised end-to-end through the real validation code (not mocked) — added per Codex review
  • Pre-existing check:multitenancy-boundaries 5-file baseline drift (register-hooks.ts, health-monitor.test.ts, branches.test.ts, tenant-db-scope.test.ts, widgets/env-vars/index.ts) and one pre-existing unrelated packages/executor OpenCode MCP-header test failure — neither touches files changed by this PR

🤖 Generated with Claude Code

kgabryje added a commit that referenced this pull request Jul 8, 2026
…oad escape rejections, dedupe channel_history onto shared resolver

Addresses Codex review on #1851:
- Add direct (non-mocked) escape tests for agor_gateway_slack_file_upload:
  relative ".." traversal through the real branch-workspace resolver,
  an absolute-path symlink escaping the upload directory, and a null-byte
  path — proving the rejections hold end-to-end through the tool.
- Add cheap regex format validation for slackChannelId/ts/emoji on the
  reaction tools so malformed input is rejected by the schema before any
  Slack call.
- Refactor agor_gateway_slack_channel_history_get onto the shared
  resolveGatewaySlackToolTarget resolver, removing its now-duplicate inline
  capability-gate + branch-binding logic (verified risk-free: existing tests
  only assert error-message substrings shared by both code paths).

@mistercrunch mistercrunch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@kgabryje kgabryje force-pushed the gateway-slack-read-tools-capabilities branch from fd491e4 to 5962fd0 Compare July 10, 2026 16:54
Base automatically changed from gateway-slack-read-tools-capabilities to main July 10, 2026 17:04
kgabryje added 2 commits July 10, 2026 17:08
…ad, thread replies

Extends the Slack agent-tool capability model (#1848) with reactions and
file_upload, mirroring channel_history's pattern exactly: opt-in toggles
on SlackAgentToolsConfig, matching OAuth scopes in SLACK_AGENT_TOOL_SCOPES,
and capability-gated + branch-bound MCP tools (agor_gateway_slack_reaction_add/
_remove, agor_gateway_slack_file_upload). file_upload constrains its path
argument to the daemon upload directory or the calling session's branch
workspace, reusing resolveBranchWorkspacePath's containment checks — no
arbitrary host filesystem reads. Also exposes threadTs on
agor_gateway_emit_message so agents can reply into existing Slack threads
(pure exposure of the connector's existing thread_ts support, no new scope).

Interactive Block Kit components are out of scope (#1729) — deferred to a
follow-up needing Socket Mode interactivity handling.
…oad escape rejections, dedupe channel_history onto shared resolver

Addresses Codex review on #1851:
- Add direct (non-mocked) escape tests for agor_gateway_slack_file_upload:
  relative ".." traversal through the real branch-workspace resolver,
  an absolute-path symlink escaping the upload directory, and a null-byte
  path — proving the rejections hold end-to-end through the tool.
- Add cheap regex format validation for slackChannelId/ts/emoji on the
  reaction tools so malformed input is rejected by the schema before any
  Slack call.
- Refactor agor_gateway_slack_channel_history_get onto the shared
  resolveGatewaySlackToolTarget resolver, removing its now-duplicate inline
  capability-gate + branch-binding logic (verified risk-free: existing tests
  only assert error-message substrings shared by both code paths).
@kgabryje kgabryje force-pushed the gateway-slack-outbound-richness branch from 7f23788 to e07df58 Compare July 10, 2026 17:17
…M exemption

Addresses dual-review (Codex + Fable) on #1851:
- Major: agor_gateway_slack_reaction_add/_remove and
  agor_gateway_slack_file_upload now honor the gateway channel's
  allowed_channel_ids whitelist, mirroring the inbound listening semantics —
  DMs (Slack IDs starting with "D") are always exempt, since these tools
  default to the calling session's own conversation, which is frequently a
  DM. New isSlackDirectMessageId/isSlackWriteTargetAllowed helpers in
  slack.ts, enforced once in the shared resolveGatewaySlackToolTarget
  resolver, scoped to the 'reactions'/'file_upload' capabilities only —
  channel_history's existing (already-reviewed) connector-level enforcement
  is untouched.
- Minor: slackFileUploadSchema's slackChannelId/threadTs and
  agor_gateway_emit_message's threadTs now use the same regex format
  validation the reaction tools already had.
- Minor: the file-upload escape-rejection error no longer echoes the
  resolved daemon upload root's absolute path.
@kgabryje kgabryje added ai-reviewed-claude-fable-5 Reviewed by Claude Fable 5 ai-reviewed-gpt-5-6-sol Reviewed by Codex GPT-5.6-sol labels Jul 10, 2026
@kgabryje kgabryje merged commit afef3db into main Jul 10, 2026
2 checks passed
@kgabryje kgabryje deleted the gateway-slack-outbound-richness branch July 10, 2026 18:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-reviewed-claude-fable-5 Reviewed by Claude Fable 5 ai-reviewed-gpt-5-6-sol Reviewed by Codex GPT-5.6-sol

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants