Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/hunvreus/heypi/llms.txt

Use this file to discover all available pages before exploring further.

Heypi’s Slack adapter connects your agent to Slack workspaces using either Socket Mode (a persistent WebSocket connection ideal for local development) or HTTP Mode (a standard webhook receiver for production deployments). Both modes are powered by Slack Bolt, and the same access control, streaming, and approval features apply to either.

Create a Slack App

1

Open the Slack API dashboard

Go to https://api.slack.com/apps and click Create New App.
2

Choose From scratch

Select From scratch, give your app a name, and choose the target workspace.
3

Add bot token scopes

Under OAuth & Permissions → Scopes → Bot Token Scopes, add the following:
app_mentions:read
channels:history
channels:read
chat:write
chat:write.public
files:read
files:write
im:history
reactions:write
4

Install the app to your workspace

Click Install to Workspace and copy the Bot User OAuth Token:
SLACK_BOT_TOKEN=xoxb-...

Socket Mode vs HTTP Mode

Socket Mode connects to Slack over a persistent WebSocket. No public URL is required, making it ideal for local development.
1

Enable Socket Mode

In your Slack app settings, go to Socket Mode and toggle it on.
2

Create an app-level token

Under Basic Information → App-Level Tokens, create a token with the connections:write scope. Copy the token:
SLACK_APP_TOKEN=xapp-...
3

Configure the adapter

import { createHeypi, runHeypi, slack } from "@hunvreus/heypi";

const app = createHeypi({
  state: { root: "./state" },
  adapters: [
    slack({
      botToken: process.env.SLACK_BOT_TOKEN!,
      mode: "socket",
      appToken: process.env.SLACK_APP_TOKEN!,
      allow: { channels: ["C123456"] },
      trigger: "mention",
      reply: "thread",
      streaming: true,
    }),
  ],
  // agent: ...
});

await runHeypi(app);
4

Subscribe to bot events

Under Event Subscriptions, enable events and subscribe to:
app_mention
message.channels
message.im

Access Control

Slack decides which events heypi receives through your app installation, OAuth scopes, event subscriptions, and channel membership. Heypi’s allow config filters those events after Slack delivers them — it does not replace Slack’s own delivery rules.
slack({
  // ...tokens and mode
  allow: {
    teams: ["T123"],       // Slack workspace/team IDs
    channels: ["C123"],    // Channel IDs (non-DM channels only)
    users: ["U123"],       // Restrict to specific users
    dms: true,             // Allow direct messages
  },
  trigger: "mention",        // Only respond when @mentioned
  threadTrigger: "message",  // In existing threads, respond to any message
});
allow.channels applies to non-DM channels only. For private channels, invite the bot to the channel, grant groups:read, reinstall the app, and then use heypi slack channels --private to discover channel IDs.

Streaming

Enable token-by-token streaming with streaming: true, or tune the behavior with an object:
slack({
  // ...
  streaming: true,

  // Or fine-tuned:
  streaming: {
    intervalMs: 1000,   // How often to update the draft message (default: 1000 ms)
    minChars: 40,       // Minimum characters before first update (default: 40)
    maxFailures: 3,     // Stop streaming after this many edit errors (default: 3)
  },
});

Progress Indicator

By default, heypi posts a “Working…” placeholder message in the thread (after a short delay) and reacts with 👀 on the original message while a turn is running. Customize or disable this with progress:
slack({
  // ...
  progress: {
    reaction: "hourglass",  // Emoji reaction on the original message (default: "eyes"); set false to disable
    message: "On it...",    // Placeholder message text (default: "Working..."); set false to disable
    delayMs: 1000,          // Delay before showing the placeholder (default: 750 ms)
  },
  // Or disable entirely:
  // progress: false,
});

Reply Placement

Control where replies land in Slack channels:
slack({
  // ...
  reply: "thread",   // Default: reply in a new thread (or existing thread)
  // reply: "same",  // Reply in the same thread the message was in
  // reply: "channel", // Reply directly to the channel (no thread)
});

CLI Commands

Use the heypi CLI to validate your setup and discover IDs:

heypi slack check

Validates SLACK_BOT_TOKEN and prints the workspace and bot identity.
pnpm exec heypi slack check --env .env

heypi slack channels

Lists channel IDs visible to the bot. Add --private for private channels (requires groups:read scope).
pnpm exec heypi slack channels --env .env
pnpm exec heypi slack channels --env .env --private

heypi slack manifest

Generates a starter Slack app manifest for HTTP mode with the correct event subscriptions and redirect URLs.
pnpm exec heypi slack manifest \
  --url https://<host>/slack/acme/events

Full Example

The following is the complete examples/slack-devops entry point — a DevOps agent with Socket Mode, streaming, access control, approval gates, and scheduled jobs:
import { existsSync } from "node:fs";
import { loadEnvFile } from "node:process";
import { agentFrom, consoleLogger, coreTools, createHeypi, runHeypi, slack, workspace } from "@hunvreus/heypi";
import { createHostContext, createHostTools } from "./tools/host.js";
import { createRunbookTools } from "./tools/runbook.js";

loadEnv(".env");

function loadEnv(path: string): void {
  if (existsSync(path)) loadEnvFile(path);
}

function required(name: string): string {
  const value = process.env[name];
  if (!value) throw new Error(`Missing env var: ${name}`);
  return value;
}

function optional(name: string): string | undefined {
  return process.env[name]?.trim() || undefined;
}

function list(name: string): string[] {
  return (process.env[name] ?? "")
    .split(",")
    .map((value) => value.trim())
    .filter(Boolean);
}

const stateRoot = "./state";
const log = consoleLogger({ level: "debug", format: "pretty" });
const jobChannel = optional("HEYPI_SLACK_JOB_CHANNEL");

const app = createHeypi({
  state: { root: stateRoot },
  logger: log,
  admin: true,
  adapters: [
    slack({
      botToken: required("SLACK_BOT_TOKEN"),
      mode: "socket",
      appToken: required("SLACK_APP_TOKEN"),
      allow: {
        teams: list("HEYPI_SLACK_TEAMS"),
        channels: list("HEYPI_SLACK_CHANNELS"),
        users: list("HEYPI_SLACK_USERS"),
      },
      trigger: "mention",
      reply: "thread",
      streaming: true,
    }),
  ],
  agent: agentFrom("./agent", {
    id: "slack-devops",
    model: "openai/gpt-5-mini",
    context: [createHostContext({ root: stateRoot })],
    tools: [
      ...coreTools({ bash: true }),
      ...createRunbookTools({ root: "./agent/runbooks" }),
      ...createHostTools({ root: stateRoot, timeoutMs: 60_000 }),
    ],
  }),
  approval: {
    approvers: list("HEYPI_APPROVERS"),
    expiresInMs: 10 * 60 * 1000,
  },
  jobs: jobChannel
    ? [
        {
          id: "daily-health-check",
          schedule: { cron: "0 9 * * *", timezone: "UTC" },
          targets: { slack: { channels: [jobChannel] } },
          prompt: "Run a daily infrastructure health check and summarize anything that needs attention.",
          state: "active",
        },
        {
          id: "idle-incident-follow-up",
          kind: "heartbeat",
          everyMs: 6 * 60 * 60 * 1000,
          idleMs: 30 * 60 * 1000,
          scope: { slack: { channels: [jobChannel] } },
          prompt: "If an incident thread has gone quiet, ask whether follow-up is still needed.",
          state: "paused",
        },
      ]
    : [],
  runtime: {
    root: workspace("./workspace"),
    scope: "channel",
  },
});

await runHeypi(app);

Common Failures

SLACK_BOT_TOKEN is wrong, expired, or the app was reinstalled since the token was last copied. Re-copy the bot token from OAuth & Permissions and update your .env file.
The token was not sent or was loaded from the wrong .env file. Confirm the file path passed to --env matches where you stored the token, and that the variable name is exactly SLACK_BOT_TOKEN.
Check that:
  • app_mentions:read and message.channels scopes are both added.
  • The bot is invited to the channel (/invite @yourbot).
  • Event Subscriptions are enabled and pointed at the correct URL (HTTP mode).
Interactivity is disabled or the Interactivity URL is not set. In your Slack app settings, enable Interactivity & Shortcuts and set the same URL as your Event Subscriptions endpoint.
Verify SLACK_APP_TOKEN starts with xapp- and has the connections:write scope. The bot token (SLACK_BOT_TOKEN) and app token (SLACK_APP_TOKEN) are different — ensure you haven’t swapped them.

Build docs developers (and LLMs) love