feat(agent): open, watch and resume Slack channels from agent runs

GI
2mo ago
+4993-206

Opened automatically when feat/agent-run-visibility was pushed.

The title is written from the diff and rewritten as you push, because this is squashed onto main and the title becomes the commit subject and the changelog line. Retitle it yourself and it is yours — the automation stops touching it.


Summary by cubic

Implements CRM-1: an agent run can now open its own Slack channel, invite people, and post into it, and gets woken up when messages or joins arrive in that channel. Previously Slack actions could only target a destination chosen when the agent was saved; now a run can create and claim a channel, and inbound Slack events resume that same parked run instead of starting a second one.

Behavior

  • New open_slack_channel and invite_to_slack_channel tools are manifest-approved actions, with the same dependency checks and idempotency as existing actions; reopening a name already in use joins the existing channel, and the run keeps the channel it already owns.
  • Reusing a channel refuses an unavailable one (archived, kicked bot, or a private channel the bot can no longer see) and rejoins before handing it back; invitations stop when a run ends.
  • Slack message posts can use resolution: "run-channel", which resolves to the channel the run opened.
  • The new /webhooks/slack/events endpoint verifies signatures and stores events in slackEventInbox; the agent drains them on the cron, in order, retrying undelivered resumes for five minutes.
  • Inbound Slack is off by default: without SLACK_SIGNING_SECRET, the endpoint rejects every request, and answers 200 to bodies it cannot parse so Slack stops retrying.
  • invite_to_slack_channel returns invited and refused arrays, and throws only when Slack refuses every address; a paused agent's Slack message is kept for replay.
  • Run action results and the agent's final summary now appear in run history.

Setup / migration

  • Requires SLACK_SIGNING_SECRET and the new app_mentions:read Slack scope; existing Slack connections must be re-authorized.
  • Migrations add agentRun.slackChannelId, slackEventInbox, and agentAction.result.
  • Slack destinations now require a resolution field (chosen or run-channel); saved agents with older Slack destinations need that field set.
  • bun run tunnel:slack provides a stable local hostname for Slack event delivery; it follows PORT unless SLACK_TUNNEL_PORT overrides it.