Skip to content

v2: Slack channel + events — port to the v2 stack - #162

Open
yourbuddyconner wants to merge 4 commits into
dev-v2from
docs/slack-port-design
Open

v2: Slack channel + events — port to the v2 stack#162
yourbuddyconner wants to merge 4 commits into
dev-v2from
docs/slack-port-design

Conversation

@yourbuddyconner

@yourbuddyconner yourbuddyconner commented Jul 22, 2026

Copy link
Copy Markdown
Collaborator

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 companion slack-user spec whose implementation is a follow-on PR).

plugin-slack (bot actions already lived here; this adds the missing halves):

  • SlackTransport (ingress: "external-webhook"): conversation keys slack:{teamId}:{channelId}[:{threadTs}], ported signature verification (v0: HMAC, 300s replay) + mrkdwn conversion, Block Kit gate prompts with g|{gateId}|{actionId} button values (gates survive api restarts), v2 external-upload media, DM/app_mention parsing with bot-echo + subtype suppression, and Socket Mode as the poll() implementation when metadata.appToken is set — tunnel-free local dev, like Telegram long-poll.
  • slackTriggerDefs: 6 defs / 11 catalog keys (reaction, member, channel lifecycle, file, team_join, message), ephemeral: true on slack.message.

api:

  • Dedicated ingress 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.
  • Match-gated (ephemeral) persistence in ingestEvent: slack.message events are matched against enabled subscriptions before insert and never persisted unless something wants them.
  • ChannelHost second-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-detected openDirectConversation for attention DMs.
  • Identity links parameterized by provider + Slack DM-code flow (typeahead → bot DMs code → verify in web). One in-place 0000_app.sql edit (identity_link_codes.external_id, pre-1.0 rule — reset ~/.valet/pg).
  • PUT /api/credentials/slack?scope=org now requires metadata.webhookSecret and validates/enriches via auth.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

  • plugin-slack: 109 tests (verify/format ports, transport parse/keys/gates, triggers, fake Slack API)
  • api: full suite 1491 passed; new suites: slack-webhook route (8 — challenge/retry/bad-sig/foreign-team/reaction ingest/ephemeral both ways/channel fan-out), identity links (12), slack credential validation (3). Only failure = known-environmental llm-providers.e2e (dead local OpenAI key)
  • engine 433, plugin-telegram 28, web 435 passed; root typecheck clean (modulo the known environmental pg-schema/undici-types resolution issue on this machine)
  • Manual: real workspace connect + DM/mention round-trip + Socket Mode with an xapp token (tunnel or deploy)

Deferred per spec non-goals: channel↔session bindings, triggerMode: all, slash commands, OAuth bot install, suggested prompts. plugin-slack-user port is the companion spec's follow-on PR.

- 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
@yourbuddyconner yourbuddyconner changed the title v2: Slack port design specs — channel + events, slack-user plugin v2: Slack channel + events — port to the v2 stack Jul 22, 2026
- 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant