Skip to content

Add Slack and Microsoft Teams as channel adapters#257

Open
AutomationEdgeHQ wants to merge 4 commits into
stephengpope:mainfrom
AutomationEdgeHQ:feature/slack-teams-channels
Open

Add Slack and Microsoft Teams as channel adapters#257
AutomationEdgeHQ wants to merge 4 commits into
stephengpope:mainfrom
AutomationEdgeHQ:feature/slack-teams-channels

Conversation

@AutomationEdgeHQ

Copy link
Copy Markdown

Summary

Adds Slack and Microsoft Teams as channel adapters alongside the existing Telegram + web channels. Both work end-to-end in personal DMs with two-way messaging, JWT/HMAC webhook verification, and channel binding via a non-conflicting !verify <code> command (Slack and Teams both intercept / commands).

Tested locally via ngrok and in production on a DigitalOcean droplet with Cloudflare-issued Let's Encrypt wildcard SSL.

What's included

  • Slack: HMAC-SHA256 signature verification, app_mention + message.im events, url_verification handled before credentials check
  • Microsoft Teams: Bot Framework JWT verification, Single Tenant and Multi Tenant Azure registration support (Single Tenant requires login.microsoftonline.com/{tenantId}/oauth2/v2.0/token instead of botframework.com — common gotcha on first install)
  • !verify <code> binding flow for both
  • In-app setup guides in the admin Secrets UI for both platforms

Files changed (7)

File Change
`api/index.js` Handle Slack `url_verification` before credentials check
`lib/config.js` Add `SLACK_`, `TEAMS_` to `SECRET_KEYS`
`lib/channels/commands/index.js` `parse()` accepts `!` in addition to `/`
`lib/chat/components/profile-page.jsx` Slack + Teams tabs display `!verify`
`lib/chat/components/settings-secrets-page.jsx` Setup guides + Teams Tenant ID field
`lib/tools/teams.js` `getTokenEndpoint()` reads `TEAMS_TENANT_ID`
`lib/chat/actions.js` `TEAMS_TENANT_ID` in `API_KEY_SECRETS`; `getTeamsStatus` returns `tenantIdSet`

Compatibility

  • Zero breaking changes to existing Telegram or web channels
  • `!` prefix is additive — `/verify` still works on platforms that don't intercept it
  • New secrets are optional — bot starts cleanly without Slack/Teams credentials configured

Test plan

  • Local install via ngrok (Slack DM, Teams DM, both verified)
  • Production install on DigitalOcean Ubuntu 24.04 + Cloudflare wildcard SSL
  • Single Tenant Azure registration validated (the 401 fix)
  • Fresh-DB rebind on new domain
  • Bidirectional message flow through both channels
  • Teams agent-creation flow live-tested

🤖 Generated with Claude Code

jaykimelman and others added 3 commits May 23, 2026 20:59
Mirrors the existing Telegram channel pattern to add two new chat
platforms. Reuses the cross-channel infrastructure already in place:
the user_channels table, /verify command, system-message dispatch,
and adapter base class.

Slack
- SlackAdapter (lib/channels/slack.js) handles Events API webhooks,
  signature verification (HMAC-SHA256 of v0:<ts>:<body>),
  url_verification challenges, app_mention/message.im events, file
  attachments (images + audio transcription via AssemblyAI when
  configured), and threaded replies via thread_ts.
- HTTP helpers in lib/tools/slack.js use raw fetch against the Slack
  Web API — no @slack/web-api dependency added.

Microsoft Teams
- TeamsAdapter (lib/channels/teams.js) handles Bot Framework Activity
  webhooks with hand-rolled JWT validation against Microsoft's JWKS
  (cached 24h) — avoids pulling botbuilder (~5MB+).
- Replies sent via the Bot Framework Connector REST API with a
  client-credentials access token (cached + auto-refreshed).
- Typing indicator re-emitted every 8s while AI processes.
- File attachment downloads left as a follow-up (needs Graph
  auth path); text messages fully supported.

API routing
- /slack/events and /teams/events added to PUBLIC_ROUTES and the
  catch-all api/index.js POST switch.
- pushToDefaultChannel extended to dispatch to Slack and Teams adapters.
  Teams pushes require a serviceUrl captured from inbound messages, so
  system notifications only flow after the user has messaged the bot.

Admin UI (mirrors Telegram exactly)
- /profile/slack and /profile/teams tabs for users to link their
  channel via a one-time /verify <code> flow.
- /admin/event-handler/slack and /teams pages for admins to paste
  credentials, see the webhook/messaging URL, and validate.
- New SlackIcon and TeamsIcon SVGs.

Database
- No schema changes. Slack rows use channel='slack', Teams use
  channel='teams' in the existing user_channels table.

Env vars (templates/.env.example)
- SLACK_BOT_TOKEN, SLACK_SIGNING_SECRET
- TEAMS_APP_ID, TEAMS_APP_PASSWORD

Docs
- docs/CHAT_INTEGRATIONS.md extended with end-to-end client setup
  walkthroughs for both platforms (Slack app creation + scopes,
  Azure Bot registration + manifest sideload).
The previous commit added the Slack/Teams adapters, server actions, and
React page components but did not include the consumer-facing Next.js
route files. Without those, /profile/slack, /profile/teams,
/admin/event-handler/slack, and /admin/event-handler/teams all 404.

Adds:
- web/app/profile/slack/page.js
- web/app/profile/teams/page.js
- web/app/admin/event-handler/slack/page.js
- web/app/admin/event-handler/teams/page.js

Each mirrors the existing Telegram route exactly — auth check, fetch
initial state via the matching backend module, render the component.

- lib/chat/components/index.js: export ProfileSlackPage, ProfileTeamsPage,
  ApiKeysSlackPage, ApiKeysTeamsPage so the route files can import them
  from 'thepopebot/chat'.
- lib/chat/components/settings-secrets-layout.jsx: add Slack + Teams tabs
  to EVENT_HANDLER_TABS so the admin sidebar nav exposes them.
- docker/event-handler/Dockerfile: optional dev tarball install. If a
  thepopebot-*.tgz exists alongside (created via `npm pack`), install
  from it instead of the npm registry. Lets contributors test
  working-tree changes in Docker without publishing.
- .gitignore: thepopebot-*.tgz so packed tarballs don't get committed.
…Tenant ID support

- api/index.js: handle url_verification before credential check (Slack challenge arrives before creds are entered)
- lib/config.js: register SLACK_BOT_TOKEN, SLACK_SIGNING_SECRET, TEAMS_APP_ID, TEAMS_APP_PASSWORD, TEAMS_TENANT_ID in SECRET_KEYS
- lib/channels/commands/index.js: accept ! prefix in addition to / (Slack and Teams both intercept /verify natively)
- lib/tools/teams.js: dynamic token endpoint via getTokenEndpoint() — reads TEAMS_TENANT_ID for Single Tenant support, falls back to botframework.com for Multi Tenant
- lib/chat/actions.js: add TEAMS_TENANT_ID to API_KEY_SECRETS; return tenantIdSet from getTeamsStatus
- lib/chat/components/profile-page.jsx: show !verify (not /verify) in Slack and Teams binding flow
- lib/chat/components/settings-secrets-page.jsx: add collapsible setup guides for Slack and Teams; add Tenant ID field (Step 2) to Teams section

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@stephengpope stephengpope left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Really nice work on this — the channel adapters are clean, and I love that there are zero new npm deps (built-in crypto for both) and that everything stays inert without config. No impact on existing Telegram/web users. 🙌

One thing I'd suggest pulling out before merge: the Dockerfile and .gitignore changes. They're a dev-convenience for baking a local tarball, but they're unrelated to the Slack/Teams feature and carry a real risk:

COPY thepopebot-*.tgz* ./ is its own instruction with no concrete fallback file. When the glob matches zero files, BuildKit fails the build (failed to compute cache key: not found) — it never reaches the RUN fallback. The existing COPY package.json package-lock.json* ./ only works because package.json is always present alongside the optional glob.

The catch: CI's build-event-handler job builds from context: . and never runs npm pack, so there's no .tgz in the context. That would break the published event-handler image that everyone upgrades from — not just Slack/Teams users.

Suggestion: drop the Dockerfile + .gitignore changes from this PR and keep the rest (adapters, config keys, routes, UI). That leaves a purely additive feature with no pipeline risk. The tarball workflow could land separately if it's still wanted, with a concrete fallback so the zero-match case can't break the build.

Per @stephengpope review on PR stephengpope#257: the COPY thepopebot-*.tgz* +
fallback branch and matching .gitignore entry are an AE-side dev
convenience for baking working-tree changes into the event-handler image
without publishing. They're unrelated to the Slack/Teams feature itself.

More importantly, the new COPY had no concrete fallback file. When the
glob matches zero files BuildKit fails the build before reaching the
RUN's runtime fallback. Upstream CI's build-event-handler job builds
from context: . and never runs npm pack, so it would always hit the
zero-match case and break the published event-handler image.

Restoring docker/event-handler/Dockerfile and .gitignore to their state
prior to e13bc27 leaves this PR purely additive: adapter code, config
keys, Next.js routes, admin nav, UI. The tarball convenience lives only
in the AE fork; if it's ever wanted upstream it should land as its own
PR with a concrete fallback so the zero-match case can't bite.
@AutomationEdgeHQ

Copy link
Copy Markdown
Author

Thanks for the review @stephengpope — and the catch on the Dockerfile change is dead right. I hadn't traced through the CI implication that context: . never has a tarball, so the zero-match case would have broken build-event-handler for everyone upgrading from the published image.

Pushed 834db90 which restores docker/event-handler/Dockerfile and .gitignore to their state prior to e13bc27. PR is now purely additive: adapter code, config keys, Next.js routes, admin nav, UI — nothing that touches the publish pipeline.

The tarball convenience lives only in my fork now. If it ever makes sense upstream I'll send it as its own PR with a concrete fallback file so the zero-match case can't bite. Ready for re-review whenever you have a minute.

AutomationEdgeHQ pushed a commit to AutomationEdgeHQ/ledger that referenced this pull request Jun 7, 2026
Folds the Slack + Teams channel adapters into the AE mainline alongside MFA,
so ae-main is the single production line (branding + status + MFA +
Slack/Teams). Ends the fragile temp-merge-at-build-time dance that risked
dropping Slack/Teams from the droplet on every build.

feature/slack-teams-channels stays as the record of the still-open upstream
PR stephengpope#257; if Stephen merges it, git reconciles on the next upstream sync.

Conflict: lib/config.js SECRET_KEYS — kept both the SMTP (MFA) keys and the
TEAMS_* keys.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

3 participants