Add Slack connector - #1
Conversation
Bridge Codex into Slack alongside Telegram: DMs stream progress by editing a single message, channel mentions open threaded conversations, approvals arrive as buttons, files flow both ways, and a /telex slash command carries the bridge commands. Enabled by setting SLACK_BOT_TOKEN, SLACK_APP_TOKEN, and SLACK_ALLOWED_USER_IDS together; docs/slack.md documents the app manifest and setup.
- Deliver caption-less file uploads to Codex with attachment descriptions instead of silently dropping them after download. - Reject conversation-scoped /telex subcommands in channels with guidance (each thread is its own conversation; a slash command carries no thread), instead of acting on a conversation key no message flow creates. - Anchor thread replies to the latest scheduled-run notification published in that thread so reply context resolves despite Slack's flat threads. - Escape Slack entities in approval prompts, selection updates, and attachment-failure notices; raw < > & mangled approval text and could ping @channel through Codex-controlled content. - Deduplicate redelivered Socket Mode envelopes by envelope_id so slash commands and button clicks cannot execute twice after a reconnect. - Send only unposted chunks through the slash-command response webhook. - Refresh active-thread recency when scheduled results are published.
Telegram credentials become optional: each connector is an all-or-nothing env group and at least one must be configured. Without Telegram, the Telegram channel, the settings Mini App, and the quick tunnel stay off; the Slack connector runs standalone.
Multi-stage image running Telex as the unprivileged telex user with all state under a /data volume. The entrypoint seeds Codex config with sandbox_mode danger-full-access on fresh volumes: Codex's bubblewrap sandbox needs user namespaces that Docker's default confinement blocks, so the container itself is the isolation boundary. Includes a Compose example and docs/docker.md.
SLACK_ALLOWED_USER_IDS=* authorizes any regular member of the installed workspace. Membership is verified through users.info against the bot's team and cached for ten minutes: bots, deactivated accounts, guests, and Slack Connect participants from other workspaces stay rejected, and deactivating someone locks them out without a restart. Scheduled-run owner re-checks go through the same gate.
gh reads GH_TOKEN from the environment; when set, the entrypoint also wires git's HTTPS credential helper through gh so clones and fetches work headlessly.
/telex config in the bot DM now renders interactive Codex settings from Slack blocks (model, reasoning effort, speed tier, approvals, sandbox, web search) through CodexConfigService with optimistic versioning — replacing the Telegram-only Mini App pointer. SLACK_ADMIN_USER_IDS optionally restricts instance-wide commands (config, login, logout, reload, restart, update) to listed users; the gate also covers the mention-text command form and config buttons.
Slack rejects chat.update/postMessage payloads far below the documented 40k ceiling (msg_too_long observed at 12k), which both truncated long answers and, because delivery shared one try block, dropped every remaining chunk after the first failure. The message limit drops to 3,900 characters and each chunk now posts independently, with a notice when parts fail. The final answer no longer silently edits the thinking message — the progress message freezes without the streaming cursor and the answer arrives as fresh messages that actually notify. Markdown tables render as aligned monospace blocks instead of raw pipes.
A mention inside an existing thread calls the bot into a running discussion. The channel now fetches the earlier thread messages through conversations.replies (up to 100, oldest dropped over an 8k character budget) and prefixes them as context for Codex, with display names resolved and the triggering message excluded. Commands and already active threads skip the fetch, and a fetch failure degrades to the bare message.
Follow-ups in a thread the bot already answered no longer trigger it: with workspace-wide access, humans discussing inside such a thread had every message routed to Codex. A mention is now required each time; the engaged-thread set survives only to skip re-reading thread history on repeat mentions, and thread context still arrives on the first one.
Every inbound message logs the sender (user ID and display name), conversation, command, and text; per-turn child loggers carry that identity into the stream, which now mirrors the run: each Codex tool call once, reasoning summaries as they change, and the delivered answer. Rollouts never recorded who triggered a turn, so operators had no way to attribute usage.
Codex referenced workspace files as markdown links to container paths, which render as dead links in Slack and Telegram. The remote-client context now instructs Codex to cite code as repo-relative inline paths or full repository URLs, and the Slack converter renders any non-URL link target as inline code instead of a broken <path|label> link.
- Comment out the Telegram samples in .env.example: uncommented placeholders fail validation in a Slack-only copy. - Let the entrypoint start as root only to chown freshly created volumes (non-recursively), then drop to the telex user via runuser; fresh named volumes are root-owned and previously broke first start. - Install gh for the image architecture instead of hard-coded amd64. - Cache display names only on successful lookups so a transient users.info failure does not pin the raw ID until restart. - Derive slash-command isDirect from the channel ID prefix; a channel literally named directmessage could spoof channel_name. - Require https in the Slack file-host check, cap downloads at 100 MB up front, and count streamed bytes so a wrong size cannot bypass it. - Clamp the streaming preview and the frozen progress text so entity escaping cannot push a chat.update past the message limit. - Drop unencodable command buttons with a warning instead of failing the whole scheduled delivery; bound both response webhooks with a 10s abort timeout; resolve thread-context names concurrently. - Extend the fenced-code and final-delivery tests per review.
Greptile SummaryThe PR adds a complete Slack Socket Mode connector alongside Telegram, including authorization, threaded conversations, commands, approvals, file transfer, settings, and scheduled notifications. It also generalizes shared channel and bridge behavior for multi-provider operation.
Confidence Score: 4/5The scheduled-result restart fallback should be fixed before merging because it can inject unrelated automation output into ordinary Slack thread conversations. After restart, Slack supplies only a thread-root reply reference, and the new fallback interprets any reply in a matching conversation as a reply to its newest delivered scheduled notification. Files Needing Attention: src/automations/engine.ts and src/channels/slack/channel.ts
|
| Filename | Overview |
|---|---|
| src/channels/slack/channel.ts | Implements the Slack connector lifecycle, authorization, routing, interactions, thread handling, and provider API adapter. |
| src/channels/slack/reply.ts | Implements bounded, entity-safe Slack replies, streaming progress, action blocks, choices, and attachment uploads. |
| src/channels/slack/file.ts | Adds bounded authenticated file downloads with Slack-host validation and partial-file cleanup. |
| src/automations/engine.ts | Adds restart recovery for Slack notification context, but the fallback incorrectly applies the latest scheduled result to unrelated replies in the same conversation. |
| src/config/env.ts | Adds connector-dependent Slack and Telegram configuration validation. |
| src/core/bridge.ts | Generalizes bridge behavior and lifecycle plumbing for multiple messaging providers. |
| src/index.ts | Initializes configured Telegram and Slack connectors and supports Slack-only operation. |
Sequence Diagram
sequenceDiagram
participant U as Slack user
participant S as Slack Socket Mode
participant C as SlackChannel
participant B as CodexBridge
participant A as ScheduledRunsEngine
participant X as Codex
U->>S: Message, command, or button action
S->>C: Socket envelope
C-->>S: Acknowledge
C->>C: Authorize and route conversation
C->>A: Resolve scheduled-result reply context
C->>B: InboundMessage
B->>X: Start or continue Codex turn
X-->>B: Progress and final response
B-->>C: Stream and attachments
C-->>U: Threaded Slack messages
Reviews (1): Last reviewed commit: "Remove GitHub auth bootstrap" | Re-trigger Greptile
Summary
Ports the Slack connector from sadfun/telex#1 into Wirebot while retaining the original PR ancestry.
/wirebotcommandsReview follow-ups
Three independent review passes covered correctness/security, architecture/simplification, and integration/operations. The resulting fixes include:
Validation
Before the repository's temporary connector tests were removed at maintainer request:
bun run checkbun run compile0.1.0)The final no-tests tree was then revalidated with
bun run check,bun run compile, the compiled binary smoke test, entrypoint syntax validation, a full ARM64 Docker rebuild, the container version smoke test, andgit diff --check. The test files, package script, CI test step, and README test claims are intentionally absent pending the project-wide testing framework.