Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion starter/slack-agent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ in Slack:
bun run start
```

Invite `@interchange` to a channel, then mention it. Each Slack thread gets its
Invite `@corbits` to a channel, then mention it. Each Slack thread gets its
own durable Interchange context under `tmp/slack-agent/context/`. Reinstall the
Slack app whenever its OAuth scopes change.

Expand Down
6 changes: 3 additions & 3 deletions starter/slack-agent/manifest.slack.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"display_information": {
"name": "interchange",
"description": "Ask an Interchange agent from Slack",
"name": "corbits",
"description": "Ask a Corbits agent from Slack",
"background_color": "#000000"
},
"features": {
"bot_user": {
"display_name": "interchange",
"display_name": "corbits",
"always_online": true
}
},
Expand Down
2 changes: 1 addition & 1 deletion starter/slack-agent/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@corbits/example-slack-agent",
"version": "0.1.0",
"license": "MIT",
"license": "LGPL-2.1-only",
"private": true,
"type": "module",
"packageManager": "[email protected]",
Expand Down
6 changes: 3 additions & 3 deletions starter/slack-agent/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { resolveSource, type Source } from "./source";

const EXAMPLE_NAME = "slack-agent";
const DEMO_AGENT_PROMPT =
"You are interchange, a concise Corbits demo agent replying in Slack. Keep replies useful, direct, and easy to read in a thread.";
"You are Corbits, a concise demo agent replying in Slack. Keep replies useful, direct, and easy to read in a thread.";

type Write = (text: string) => void;

Expand Down Expand Up @@ -78,7 +78,7 @@ export async function main(
try {
const app = new Hono();
const { path } = mountSlackTag(app, {
userName: "interchange",
userName: "corbits",
state: createMemoryState(),
slack: {
botToken: config.botToken,
Expand All @@ -91,7 +91,7 @@ export async function main(

Bun.serve({ port: config.port, fetch: app.fetch });
stdout(
`Interchange agent listening on http://localhost:${config.port}${path}\n`,
`Corbits agent listening on http://localhost:${config.port}${path}\n`,
);
return 0;
} catch (error) {
Expand Down
17 changes: 11 additions & 6 deletions starter/slack-approval-flow/.env.example
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
PORT=3001
PUBLIC_BASE_URL=https://your-public-tunnel.example

# Keep these secret. Do not commit real values.
# PUBLIC_BASE_URL is a setup convenience, not a runtime input. Use it for
# both Slack request URLs:
# Events API: ${PUBLIC_BASE_URL}/api/tag/slack/webhook
# Interactivity: ${PUBLIC_BASE_URL}/api/tag/slack/webhook

# Slack ingress. Keep these secret. Do not commit real values.
SLACK_SIGNING_SECRET=
SLACK_APP_TOKEN=xapp-...
SLACK_BOT_TOKEN=xoxb-...

# Pick whichever model provider you have configured.
ANTHROPIC_API_KEY=sk-...
# OPENAI_API_KEY=sk-...
# Set one model-provider key. Empty values are ignored.
ANTHROPIC_API_KEY=
OPENAI_API_KEY=
# OPENAI_BASE_URL=
# GOOGLE_API_KEY=
GOOGLE_API_KEY=
# GEMINI_API_KEY=

# Optional overrides.
# INTX_PROVIDER=anthropic
Expand Down
127 changes: 67 additions & 60 deletions starter/slack-approval-flow/README.md
Original file line number Diff line number Diff line change
@@ -1,87 +1,94 @@
# slack-approval-flow

A human-in-the-loop approval workflow driven from Slack:
A human-in-the-loop Interchange workflow driven from Slack through Corbits Tag:

```text
Slack message -> draft -> approval buttons -> approve/reject -> final reply
Slack mention or DM -> draft -> approval card -> approve/reject -> final reply
```

An app mention, DM, or Slack Assistant message starts a run. The draft is
posted back into the thread with Block Kit Approve / Reject buttons.
Approve delivers `run.signal("approve", payload)` and the publish step
executes; Reject calls `run.cancel(...)`. The published result — or the
terminal status if the run ended some other way — lands in the same
thread.
`mountSlackTag` owns Slack signature verification, HTTP webhook routing, and
event normalization. This starter owns the workflow policy and session state:
it posts Chat SDK cards, handles `Button` actions with `onAction`, signals an
approval with `run.signal("approve", payload)`, and cancels a rejection with a
supported Interchange cancellation actor.

This directory is self-contained. Every dependency comes from npm —
`@intx/*` at `0.2.2` and nothing here imports from anywhere else in
this repository. Copy the directory anywhere, `bun install`, and it
runs.
This starter builds on the Corbits Tag dependency introduced by the Slack agent
starter. It uses npm `@intx/*` packages at `0.2.2` and consumes the shared,
pinned Corbits Tag checkout at `../slack-agent/vendor/corbits-tag` as a Bun
workspace. The stacked change does not register or clone a second submodule.

## What's here
## Setup

| Path | Purpose |
| --- | --- |
| `src/cli.ts` | Entry point |
| `src/config.ts` | Slack + provider configuration from the environment |
| `src/adapter.ts` | Routes Slack events and button clicks into the session store |
| `src/session.ts` | The approval lifecycle: one run per Slack thread |
| `src/blocks.ts` | The approval UI — Block Kit payloads for each state |
| `src/workflow.ts` | The workflow itself: `draft -> awaitSignal("approve") -> publish` |
| `src/source.ts` | Provider resolution from the environment |
| `src/slack/` | The Slack transport: connection config, Bolt wiring, event parsing, message helpers, thread session store, generic workflow adapter |
1. Clone the repository with its submodules and install this starter:

## Setup
```bash
git clone --recurse-submodules https://github.com/corbitsdev/examples.git
cd examples/starter/slack-approval-flow
bun install
cp .env.example .env
```

```bash
cd starter/slack-approval-flow
bun install
cp .env.example .env
```
If the repository is already cloned, initialize the shared Corbits Tag
submodule from the repository root:

Fill in:
```bash
git submodule update --init --recursive starter/slack-agent/vendor/corbits-tag
```

```bash
SLACK_SIGNING_SECRET=...
SLACK_APP_TOKEN=xapp-...
SLACK_BOT_TOKEN=xoxb-...
ANTHROPIC_API_KEY=...
```
2. Expose port `3001` through an HTTPS tunnel. Replace both
`https://your-public-tunnel.example` placeholders in
[`manifest.slack.json`](./manifest.slack.json) with the tunnel's HTTPS URL.
Both request URLs must end in `/api/tag/slack/webhook`.

3. Create a Slack app from the edited manifest and install it in the workspace.

4. Populate `.env` with `SLACK_SIGNING_SECRET`, `SLACK_BOT_TOKEN`, and one
provider key: `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, or `GOOGLE_API_KEY`.

Import [`manifest.slack.json`](./manifest.slack.json) in Slack, enable
Socket Mode, install (or reinstall) the app, then:
5. Start the HTTP server:

```bash
bun run start
```bash
bun run start
```

6. In Slack's app settings, verify that both Event Subscriptions and
Interactivity accept the same HTTPS webhook URL.

The server listens on:

```text
POST /api/tag/slack/webhook
```

Socket Mode needs no public tunnel. Without it, point Slack Event
Subscriptions, Slash Commands, and Interactivity at
`https://your-public-tunnel.example/slack/events` and leave
`SLACK_APP_TOKEN` unset.
## Use it

## Using it
Mention the bot in a channel or send it a DM:

```text
@interchange-workflow write a short launch note for the approval workflow demo
Open the Slack Assistant pane and ask interchange-workflow for an approval draft
@corbits-workflow write a short launch note for the approval workflow demo
```

Step context is written under `tmp/slack-approval-flow/`; delete that
directory for a fresh start.
The bot posts the draft with Approve and Reject buttons. Approval resumes the
workflow and publishes the result in the same Slack thread; rejection cancels
the run.

## Providers
Step context is written under `tmp/slack-approval-flow/`. Delete that directory
for a fresh start. Type-check with `bun run typecheck`.

Use whichever provider key you have: `ANTHROPIC_API_KEY`,
`OPENAI_API_KEY`, or `GOOGLE_API_KEY`. Set `INTX_PROVIDER` and
`INTX_MODEL` to force a provider or model. See
[`src/source.ts`](./src/source.ts).
## Providers

Type-check with `bun run typecheck`.
Set one of `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, or `GOOGLE_API_KEY`.
[`src/source.ts`](./src/source.ts) selects the configured provider and also
supports `GEMINI_API_KEY` plus OpenAI-compatible endpoints through
`OPENAI_BASE_URL`. Use `INTX_PROVIDER` and `INTX_MODEL` to select explicitly.

## A note on duplication
## Files

`src/slack/`, `src/workflow.ts` and `src/source.ts` are duplicated with
the other starters rather than shared. That is on purpose: a starter is
only useful if you can copy the one directory you are reading and have
working code. Do not factor these back into a common package.
| Path | Purpose |
| --- | --- |
| `src/cli.ts` | HTTP server, Corbits Tag mount, and Chat SDK action registration |
| `src/session.ts` | Consumer-owned approval lifecycle and run state |
| `src/cards.ts` | Chat SDK cards and buttons |
| `src/workflow.ts` | `draft -> awaitSignal("approve") -> publish` workflow |
| `src/source.ts` | Provider selection from environment variables |
| `../slack-agent/vendor/corbits-tag` | Shared pinned Corbits Tag workspace introduced by the base change |
Loading