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
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ SERVICE_AUTH_SECRET_WEB=
SERVICE_AUTH_SECRET_SLACK_BOT=
SERVICE_AUTH_SECRET_GITHUB_BOT=
SERVICE_AUTH_SECRET_LINEAR_BOT=
SERVICE_AUTH_SECRET_DISCORD_BOT=
# Bot token for agent-initiated Slack messages (optional). Cloudflare: secret, var.slack_bot_token
SLACK_BOT_TOKEN=

Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ on:
- "package-lock.json"
- "package.json"
- "packages/control-plane/**"
- "packages/discord-bot/**"
- "packages/github-bot/**"
- "packages/linear-bot/**"
- "packages/opencomputer-infra/**"
Expand Down Expand Up @@ -46,6 +47,7 @@ on:
- "package-lock.json"
- "package.json"
- "packages/control-plane/**"
- "packages/discord-bot/**"
- "packages/github-bot/**"
- "packages/linear-bot/**"
- "packages/opencomputer-infra/**"
Expand Down Expand Up @@ -289,3 +291,6 @@ jobs:

- name: Run linear-bot tests
run: npm test -w @open-inspect/linear-bot

- name: Run discord-bot tests
run: npm test -w @open-inspect/discord-bot
20 changes: 20 additions & 0 deletions .github/workflows/terraform.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ on:
- "packages/slack-bot/**"
- "packages/github-bot/**"
- "packages/linear-bot/**"
- "packages/discord-bot/**"
- "packages/modal-infra/**"
- "packages/daytona-infra/**"
- "packages/opencomputer-infra/**"
Expand All @@ -29,6 +30,7 @@ on:
- "packages/slack-bot/**"
- "packages/github-bot/**"
- "packages/linear-bot/**"
- "packages/discord-bot/**"
- "packages/modal-infra/**"
- "packages/daytona-infra/**"
- "packages/opencomputer-infra/**"
Expand Down Expand Up @@ -183,6 +185,7 @@ jobs:
npm run build -w @open-inspect/slack-bot
npm run build -w @open-inspect/github-bot
npm run build -w @open-inspect/linear-bot
npm run build -w @open-inspect/discord-bot

- name: Setup Terraform
uses: hashicorp/setup-terraform@v4
Expand Down Expand Up @@ -274,6 +277,14 @@ jobs:
TF_VAR_linear_webhook_secret: ${{ secrets.LINEAR_WEBHOOK_SECRET }}
TF_VAR_linear_api_key: ${{ secrets.LINEAR_API_KEY }}
TF_VAR_linear_bot_default_model: "${{ vars.LINEAR_BOT_DEFAULT_MODEL || secrets.LINEAR_BOT_DEFAULT_MODEL || 'claude-sonnet-4-6' }}"
TF_VAR_enable_discord_bot: "${{ vars.ENABLE_DISCORD_BOT || secrets.ENABLE_DISCORD_BOT || 'false' }}"
TF_VAR_discord_application_id: ${{ vars.DISCORD_APPLICATION_ID || secrets.DISCORD_APPLICATION_ID }}
TF_VAR_discord_public_key: ${{ vars.DISCORD_PUBLIC_KEY || secrets.DISCORD_PUBLIC_KEY }}
TF_VAR_discord_bot_token: ${{ secrets.DISCORD_BOT_TOKEN }}
TF_VAR_discord_allowed_role_ids: ${{ vars.DISCORD_ALLOWED_ROLE_IDS || secrets.DISCORD_ALLOWED_ROLE_IDS }}
TF_VAR_discord_allowed_channel_ids: ${{ vars.DISCORD_ALLOWED_CHANNEL_IDS || secrets.DISCORD_ALLOWED_CHANNEL_IDS }}
TF_VAR_discord_bot_default_model: "${{ vars.DISCORD_BOT_DEFAULT_MODEL || secrets.DISCORD_BOT_DEFAULT_MODEL || 'anthropic/claude-opus-5' }}"
TF_VAR_discord_bot_harness: "${{ vars.DISCORD_BOT_HARNESS || secrets.DISCORD_BOT_HARNESS || 'claude' }}"
TF_VAR_web_platform: "${{ vars.WEB_PLATFORM || secrets.WEB_PLATFORM || 'vercel' }}"
TF_VAR_enable_durable_object_bindings: "${{ vars.ENABLE_DURABLE_OBJECT_BINDINGS || secrets.ENABLE_DURABLE_OBJECT_BINDINGS || 'true' }}"
TF_VAR_enable_service_bindings: "${{ vars.ENABLE_SERVICE_BINDINGS || secrets.ENABLE_SERVICE_BINDINGS || 'true' }}"
Expand Down Expand Up @@ -372,6 +383,7 @@ jobs:
npm run build -w @open-inspect/slack-bot
npm run build -w @open-inspect/github-bot
npm run build -w @open-inspect/linear-bot
npm run build -w @open-inspect/discord-bot

- name: Setup uv
uses: astral-sh/setup-uv@v8.2.0
Expand Down Expand Up @@ -464,6 +476,14 @@ jobs:
TF_VAR_linear_webhook_secret: ${{ secrets.LINEAR_WEBHOOK_SECRET }}
TF_VAR_linear_api_key: ${{ secrets.LINEAR_API_KEY }}
TF_VAR_linear_bot_default_model: "${{ vars.LINEAR_BOT_DEFAULT_MODEL || secrets.LINEAR_BOT_DEFAULT_MODEL || 'claude-sonnet-4-6' }}"
TF_VAR_enable_discord_bot: "${{ vars.ENABLE_DISCORD_BOT || secrets.ENABLE_DISCORD_BOT || 'false' }}"
TF_VAR_discord_application_id: ${{ vars.DISCORD_APPLICATION_ID || secrets.DISCORD_APPLICATION_ID }}
TF_VAR_discord_public_key: ${{ vars.DISCORD_PUBLIC_KEY || secrets.DISCORD_PUBLIC_KEY }}
TF_VAR_discord_bot_token: ${{ secrets.DISCORD_BOT_TOKEN }}
TF_VAR_discord_allowed_role_ids: ${{ vars.DISCORD_ALLOWED_ROLE_IDS || secrets.DISCORD_ALLOWED_ROLE_IDS }}
TF_VAR_discord_allowed_channel_ids: ${{ vars.DISCORD_ALLOWED_CHANNEL_IDS || secrets.DISCORD_ALLOWED_CHANNEL_IDS }}
TF_VAR_discord_bot_default_model: "${{ vars.DISCORD_BOT_DEFAULT_MODEL || secrets.DISCORD_BOT_DEFAULT_MODEL || 'anthropic/claude-opus-5' }}"
TF_VAR_discord_bot_harness: "${{ vars.DISCORD_BOT_HARNESS || secrets.DISCORD_BOT_HARNESS || 'claude' }}"
TF_VAR_web_platform: "${{ vars.WEB_PLATFORM || secrets.WEB_PLATFORM || 'vercel' }}"
TF_VAR_enable_durable_object_bindings: "${{ vars.ENABLE_DURABLE_OBJECT_BINDINGS || secrets.ENABLE_DURABLE_OBJECT_BINDINGS || 'true' }}"
TF_VAR_enable_service_bindings: "${{ vars.ENABLE_SERVICE_BINDINGS || secrets.ENABLE_SERVICE_BINDINGS || 'true' }}"
Expand Down
7 changes: 5 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,15 @@ Three tiers connected by WebSockets:
- `slack-bot` — Slack messages → coding sessions
- `github-bot` — PR review assignments and @mention commands
- `linear-bot` — Linear agent webhooks → coding sessions
- `discord-bot` — Discord `/task` slash command → coding sessions

**Data flow**: User prompt → web client → control plane DO (WebSocket) → Modal sandbox → streaming
events back through the same WebSocket chain.

### Package Dependency Graph

```
@open-inspect/shared ← control-plane, web, slack-bot, github-bot, linear-bot
@open-inspect/shared ← control-plane, web, slack-bot, github-bot, linear-bot, discord-bot
```

**Build `@open-inspect/shared` first** whenever you change shared types. Other packages import from
Expand All @@ -44,6 +45,7 @@ it at build time.
| `slack-bot` | TypeScript / CF Workers + Hono | Slack event handler, session creation |
| `github-bot` | TypeScript / CF Workers + Hono | PR review and @mention webhook handler |
| `linear-bot` | TypeScript / CF Workers + Hono | Linear agent webhook handler |
| `discord-bot` | TypeScript / CF Workers + Hono | Discord `/task` interactions handler |
| `modal-infra` | Python 3.12 / Modal + FastAPI | Sandbox lifecycle, WebSocket bridge to control plane |

## Common Commands
Expand All @@ -66,6 +68,7 @@ npm test -w @open-inspect/web
npm test -w @open-inspect/github-bot
npm test -w @open-inspect/slack-bot
npm test -w @open-inspect/linear-bot
npm test -w @open-inspect/discord-bot

# Tests — Python (pytest)
cd packages/modal-infra && pytest tests/ -v
Expand All @@ -83,7 +86,7 @@ All TypeScript packages use **Vitest**; Python uses **pytest** + pytest-asyncio.
- **control-plane unit**: co-located as `src/**/*.test.ts` — run in Node environment
- **control-plane integration**: separate `test/integration/*.test.ts` — run in workerd via
`@cloudflare/vitest-pool-workers` with real D1 bindings
- **web, slack-bot, linear-bot**: co-located `src/**/*.test.ts`
- **web, slack-bot, linear-bot, discord-bot**: co-located `src/**/*.test.ts`
- **github-bot**: separate `test/*.test.ts`
- **modal-infra**: `tests/test_*.py`

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ ownership, bots, and member suspension.
| [slack-bot](packages/slack-bot) | Slack integration (sessions from messages) |
| [github-bot](packages/github-bot) | GitHub integration (auto-review, @mention) |
| [linear-bot](packages/linear-bot) | Linear integration (issue → coding session) |
| [discord-bot](packages/discord-bot) | Discord integration (`/task` → session) |
| [shared](packages/shared) | Shared types and utilities |

## Getting Started
Expand Down
8 changes: 5 additions & 3 deletions docs/AUTH.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,8 @@ See [Automations](AUTOMATIONS.md) for trigger setup and run behavior.

## Bots and Integrations

Slack, GitHub, and Linear integrations act on behalf of a workspace user when they handle a user
request. Their effective access is limited by both:
Slack, GitHub, Linear, and Discord integrations act on behalf of a workspace user when they handle a
user request. Their effective access is limited by both:

- The acting user's current role
- The integration's fixed set of allowed operations
Expand All @@ -146,7 +146,9 @@ specific integration route explicitly permits that operation.

Some integrations also apply their own ingress rules. For example, the GitHub integration may
require an allowed trigger user or sufficient repository collaborator access before it sends a
request to Open-Inspect.
request to Open-Inspect. The Discord integration admits only members holding an allowed Discord
role; each one acts as a workspace Member (`discord:<user id>`), so the role is the admission gate.
See [Discord Integration](integrations/DISCORD.md#who-can-submit-tasks).

## Suspension

Expand Down
57 changes: 57 additions & 0 deletions docs/GETTING_STARTED.md
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,20 @@ client-credentials tokens; authorization-code refresh tokens are not stored as r

---

## Step 4c: Create a Discord Application (Optional)

Skip this step if you don't need the Discord `/task` command.

Create an application in the
[Discord Developer Portal](https://discord.com/developers/applications), record its **Application
ID**, **Public Key**, and **Bot Token**, add the bot to your server, and copy the IDs of the role
and channel allowed to submit tasks. The full walkthrough, including the bot permissions, is in
[Discord Integration → Setup](./integrations/DISCORD.md#setup).

The interactions endpoint and the slash command are configured after deployment in **Step 7e**.

---

## Step 5: Generate Security Secrets

Generate these random secrets (you'll need them for `terraform.tfvars`):
Expand Down Expand Up @@ -591,6 +605,14 @@ linear_client_id = "" # From Step 4b (required if enabled)
linear_client_secret = "" # From Step 4b (required if enabled)
linear_webhook_secret = "" # From Step 4b (required if enabled)

# Discord Bot (set enable_discord_bot = true to deploy the interactions worker)
enable_discord_bot = false
discord_application_id = "" # From Step 4c (required if enabled)
discord_public_key = "" # From Step 4c (required if enabled)
discord_bot_token = "" # From Step 4c (required if enabled)
discord_allowed_role_ids = "" # Comma-separated role IDs (required if enabled)
discord_allowed_channel_ids = "" # Comma-separated channel IDs; empty accepts any channel

# API Keys. Optional: leave blank to add model credentials as secrets in the web
# app instead. Required only when the Slack/Linear classifier runs on Anthropic.
anthropic_api_key = "sk-ant-..."
Expand Down Expand Up @@ -920,6 +942,30 @@ For configuration and troubleshooting, see [Linear Integration](./integrations/L

---

## Step 7e: Connect Discord (If Using Discord)

1. In the Discord Developer Portal, set **General Information → Interactions Endpoint URL** to the
worker's interactions URL. Discord verifies it when you save.

```bash
terraform output -raw discord_bot_interactions_url
```

2. Register the `/task` command with your server:

```bash
DISCORD_APPLICATION_ID=... DISCORD_BOT_TOKEN=... DISCORD_GUILD_ID=... \
npm run register-commands -w @open-inspect/discord-bot
```

3. So Discord sessions can run on a Claude subscription instead of an API key, connect a Claude
account in **Settings → Accounts** and set it as the default for **Automated sessions**.

Test it with `/task prompt: <something small> repo: <owner/name>` in an allowed channel. For usage
and troubleshooting, see [Discord Integration](./integrations/DISCORD.md).

---

## Step 8: Deploy the Web App

### If using Cloudflare (`web_platform = "cloudflare"`)
Expand Down Expand Up @@ -1056,6 +1102,11 @@ ENABLE_GITHUB_BOT
GH_BOT_USERNAME
ENABLE_LINEAR_BOT
LINEAR_CLIENT_ID
ENABLE_DISCORD_BOT
DISCORD_APPLICATION_ID
DISCORD_PUBLIC_KEY
DISCORD_ALLOWED_ROLE_IDS
DISCORD_ALLOWED_CHANNEL_IDS

# Access control and branding
ALLOWED_USERS
Expand Down Expand Up @@ -1160,6 +1211,12 @@ Secrets for credentials:
| `LINEAR_CLIENT_SECRET` | Linear OAuth application client secret (required if Linear enabled) |
| `LINEAR_WEBHOOK_SECRET` | Linear webhook signing secret (required if Linear enabled) |
| `LINEAR_API_KEY` | Optional Linear API key used as a comment-posting fallback |
| `ENABLE_DISCORD_BOT` | `true` to deploy Discord bot, `false` to skip (default: `false`) |
| `DISCORD_APPLICATION_ID` | Discord application ID (required if Discord enabled) |
| `DISCORD_PUBLIC_KEY` | Discord application public key (required if Discord enabled) |
| `DISCORD_BOT_TOKEN` | Discord bot token (required if Discord enabled) |
| `DISCORD_ALLOWED_ROLE_IDS` | Comma-separated role IDs allowed to run `/task` (required if Discord enabled) |
| `DISCORD_ALLOWED_CHANNEL_IDS` | Optional comma-separated channel IDs where `/task` is accepted |
| `ANTHROPIC_API_KEY` | Optional; reaches Modal and OpenComputer sandboxes; required by an Anthropic classifier |
| `CLASSIFICATION_OPENAI_API_KEY` | Classifier OpenAI key (required when `classification_model` is an OpenAI id) |
| `OPENAI_API_KEY` | Optional OpenAI API key used when a session selects API-key authentication |
Expand Down
2 changes: 2 additions & 0 deletions docs/HOW_IT_WORKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,8 @@ can make HTTP requests and maintain WebSocket connections can participate.
attachments, classifies repos, and posts results
- **GitHub**: Bot that reviews PRs and responds to PR `@mentions`
- **Linear**: Agent workflow that starts sessions from Linear issue activity
- **Discord**: `/task` slash command that starts a session, streams progress into a thread, and
posts the result

All clients see the same session state. Send a prompt from Slack or GitHub, watch the results on
web. This works because state lives in the control plane, not the client.
Expand Down
Loading
Loading