Add Slack
The shop's mechanics live in Slack all day. Making them open a browser tab to ask the dispatcher anything seems rude. So let's meet them where they already are.
Adding Slack should leave the tools, skill, state, and approval logic untouched. A channel normalizes incoming messages, tracks how to resume the conversation, and sends replies. One file in channels/ makes Slack another client of the dispatcher.
Credentials are the new concern here. Vercel Connect manages them, so your code never handles a SLACK_BOT_TOKEN.
Outcome
The dispatcher answers @mentions in Slack, in threads, with no changes to any tool, skill, or state.
Hands-on exercise
Add the Slack channel. Slack delivers events to a public URL and needs a verified bot token to reply. Vercel Connect brokers both, so there's no signing secret or bot token in your code. eve 0.41.0 guides the whole setup:
npx eve add channel/slackThe command links or creates a Vercel project, creates or reuses a Slack connector, opens Slack authorization in your browser, registers /eve/v1/slack as the trigger destination, installs @vercel/connect, and writes agent/channels/slack.ts. Follow the prompts and finish the workspace authorization in the browser.
Inspect and extend the channel. The generated agent/channels/slack.ts defines where credentials come from, when to dispatch a turn, and how to deliver the reply. Replace it with the version in the solution below to make the dispatch and final-thread delivery explicit.
- Credentials:
connectSlackCredentials(process.env.SLACK_CONNECTOR ?? "slack/spoke-and-mirror")returns the bot token and webhook verifier, both managed by Connect. Reading the uid fromSLACK_CONNECTORkeeps it out of code; the fallback matches the connector you just named, so it works without setting the variable. - Dispatch:
onAppMentiondecides whether a mention becomes a turn. UsedefaultSlackAuthto stamp trusted Slack identity and ignore bot chatter. - Delivery: on
message.completed, post the final reply to the thread, skipping interim tool-call narration.
Because Slack delivers over the public internet, you can't exercise this one on localhost. You'll deploy to get a URL. We cover deployment properly in Section 5; for now, ship it with npx eve deploy, which wraps vercel deploy --prod, installs dependencies, and pulls your environment:
npx eve deployTry It
In a Slack workspace where the app is installed, mention the bot in a channel:
@dispatcher my commuter's front brake is rubbing, what's that cost to fix?The bot replies in a thread, runs lookup_service, and quotes the catalog price. A reply in the thread continues the session. The approval gate also carries over: ask it to book the Full Overhaul and Slack renders the approve/deny prompt as buttons.
Bot shows up in Slack but never replies? Re-run npx eve add channel/slack; it inspects the current project, connector, Slack installation, and trigger destination without blindly creating another app. Confirm the destination is /eve/v1/slack. By default, the channel gives the model the triggering mention rather than the earlier thread backlog. Opt into thread context if you need that history.
Done-When
- A Connect Slack client is attached with trigger path
/eve/v1/slack. agent/channels/slack.tsexportsslackChannelwithconnectSlackCredentials.@mentioningthe bot returns a real, tool-backed answer in a thread.- An expensive booking renders approve/deny as Slack buttons.
Solution
import { connectSlackCredentials } from "@vercel/connect/eve";
import { defaultSlackAuth, slackChannel } from "eve/channels/slack";
export default slackChannel({
// The connector uid lives in SLACK_CONNECTOR (set it on the project, or leave
// it unset). The fallback matches the connector you named with `vercel connect
// create slack --name spoke-and-mirror`, so this works out of the box.
credentials: connectSlackCredentials(
process.env.SLACK_CONNECTOR ?? "slack/spoke-and-mirror",
),
// Answer @mentions from a real user; ignore bot chatter. defaultSlackAuth
// stamps Slack identity, but does not invent a shop membership tier.
onAppMention: (ctx, message) =>
message.author ? { auth: defaultSlackAuth(message, ctx) } : null,
events: {
// Post the final reply to the thread, skipping interim tool-call narration.
// Event handlers receive (eventData, channel, ctx); Slack handles live on `channel`.
"message.completed"(eventData, channel, ctx) {
if (eventData.finishReason === "tool-calls") return;
if (eventData.message) channel.thread.post(eventData.message);
},
},
});The same agent now has web and Slack entrypoints. Before shipping, we'll replace the test identity with authenticated customer data.
Was this helpful?