v2: Slack channel + events — port to the v2 stack - #162
Open
yourbuddyconner wants to merge 4 commits into
Open
Conversation
- SlackTransport (webhook + Socket Mode poll) + TriggerDefs in plugin-slack - dedicated verify-once/fan-out ingress at /api/channels/slack/webhook - ephemeral (match-gated) event persistence for slack.message - ChannelHost: transport-owned thread-key codec, external-webhook ingress, restart-surviving gate ids, openDirectConversation attention DMs - provider-parameterized identity links + Slack DM-code flow (api + web) - slack org credential validated/enriched via auth.test at save time
- triggers: drop bot_id/non-file_share message subtypes so slack.message subscriptions can't self-trigger a workflow loop - verify: byte-length guard on the signature compare (crafted multibyte header threw RangeError -> was an unauthenticated 500) - slack-webhook route: throttle drop-log writes, wrap verifyWebhook against throws, require team_id to match when known - transport: fail fast on empty metadata.teamId (was silently one-way) - host: correct the gate-callback safety comment; sanitize channel-label provenance header - identity-links: rate-limit /slack/start bot DMs - restore clean pnpm-lock (subagent's non-frozen install forked pi-ai across zod peers, sending faux-provider test turns to real Anthropic) - scope slack-webhook event assertions by dedupeKey (fire-and-forget fan-out bleeds across the shared-PGlite reset otherwise) - spec: document Socket Mode = channel-only and gate restart semantics
- host: isolate per-transport setup in start() so a factory that throws on a bad credential skips only itself, not the whole loop + startOutbound (regression from the teamId fail-fast); add isolation test - format: escape &/< in non-code mrkdwn text so agent output can't inject <!channel>/<@U…>/<url|label> control sequences (mass ping / link spoof) - triggers: use shared SKIP_SUBTYPES, not everything-but-file_share, so thread_broadcast/me_message human messages still ingest - identity-links: send the /slack/start DM before recording the cooldown and 502 cleanly on failure; server-side min-length gate on the user typeahead - credentials: reject a non-bot (xoxp) token at org connect - transport: cap listWorkspaceMembers page scan; dwell-gate Socket Mode reconnect backoff; report thumbnail mime for served image thumbnails - web: 'Use a different account' reset on the Slack link code step
2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Ports the Slack channel from the legacy worker stack to v2 and wires Slack webhook events into the generic event system, implementing
docs/specs/2026-07-21-slack-channel-events-design.md(included, alongside the companionslack-userspec whose implementation is a follow-on PR).plugin-slack (bot actions already lived here; this adds the missing halves):
SlackTransport(ingress: "external-webhook"): conversation keysslack:{teamId}:{channelId}[:{threadTs}], ported signature verification (v0:HMAC, 300s replay) + mrkdwn conversion, Block Kit gate prompts withg|{gateId}|{actionId}button values (gates survive api restarts), v2 external-upload media, DM/app_mentionparsing with bot-echo + subtype suppression, and Socket Mode as thepoll()implementation whenmetadata.appTokenis set — tunnel-free local dev, like Telegram long-poll.slackTriggerDefs: 6 defs / 11 catalog keys (reaction, member, channel lifecycle, file, team_join, message),ephemeral: trueonslack.message.api:
POST /api/channels/slack/webhook: url_verification challenge, retry-drop, verify-once against the credential-metadata signing secret, ack inside Slack's 3s window, then fan out to BOTH the channel host and event ingestion.ephemeral) persistence iningestEvent:slack.messageevents are matched against enabled subscriptions before insert and never persisted unless something wants them.ChannelHostsecond-transport refactors (recorded pre-reqs from the Telegram spec): transport-owned thread-key codec both directions, external-webhook ingress mode, explicit-gateId resolution path, feature-detectedopenDirectConversationfor attention DMs.0000_app.sqledit (identity_link_codes.external_id, pre-1.0 rule — reset~/.valet/pg).PUT /api/credentials/slack?scope=orgnow requiresmetadata.webhookSecretand validates/enriches viaauth.test(teamId/teamName/botUserId).engine (all additive):
EventCatalogEntry.ephemeral,InboundChannelEvent.context+gateCallback.gateId,ChannelTransport.threadKeyFromConversationKey/conversationKeyFromThreadKey,ChannelTransportFactory.ingress.web: connected-accounts page gains the Slack block (search → send code → verify), generic notify/disconnect per provider.
Test plan
Deferred per spec non-goals: channel↔session bindings,
triggerMode: all, slash commands, OAuth bot install, suggested prompts.plugin-slack-userport is the companion spec's follow-on PR.