# @mosaicstack/discord-plugin

Official Discord channel adapter for Mosaic Stack.

## Behavior

- Runs independently of the bound agent's Claude, Codex, Pi, OpenCode, or future harness.
- Routes authorized untagged messages in configured agent channels and replies in-channel.
- Creates a public Discord thread when the bot is mentioned in a parent channel, or reuses the thread already attached to that same message.
- Keeps follow-ups in existing threads without requiring repeated mentions.
- Keeps `/approve` and `/stop <approval>` on the current durable session.
- Applies guild, parent-channel, user, pairing, role, and per-minute abuse limits before thread creation or gateway dispatch.
- Authenticates to the gateway and signs ingress envelopes with the injected service token.

## Required configuration

| Variable                                | Purpose                                                              |
| --------------------------------------- | -------------------------------------------------------------------- |
| `DISCORD_BOT_TOKEN`                     | Discord bot credential                                               |
| `DISCORD_SERVICE_TOKEN`                 | High-entropy plugin-to-gateway credential                            |
| `DISCORD_SERVICE_USER_ID`               | Provisioned Mosaic service principal                                 |
| `DISCORD_ALLOWED_GUILD_IDS`             | Comma-separated guild allowlist                                      |
| `DISCORD_ALLOWED_CHANNEL_IDS`           | Comma-separated parent-channel allowlist                             |
| `DISCORD_ALLOWED_USER_IDS`              | Comma-separated Discord user allowlist                               |
| `DISCORD_INTERACTION_BINDINGS`          | JSON channel→logical-agent bindings and paired-user roles            |
| `DISCORD_GATEWAY_URL`                   | Gateway base URL; defaults to the gateway's local development URL    |
| `DISCORD_MESSAGE_RATE_LIMIT_PER_MINUTE` | Authorized turns per guild/channel/user per minute; default `30`     |
| `DISCORD_THREAD_RATE_LIMIT_PER_MINUTE`  | Mention-thread routes per guild/channel/user per minute; default `5` |

Supply credentials through the approved runtime secret mechanism. Never commit tokens or binding data containing secrets.

Example binding shape (identifiers are placeholders):

```json
[
  {
    "instanceId": "interaction-agent",
    "agentConfigId": "agent-config-id",
    "guildId": "guild-id",
    "channelId": "channel-id",
    "pairedUsers": {
      "discord-user-id": {
        "role": "operator",
        "mosaicUserId": "mosaic-user-id"
      }
    }
  }
]
```

Each binding's trusted `agentConfigId` must identify a provisioned database agent configuration whose name exactly matches its `instanceId`. Roles are `viewer`, `operator`, and `admin`. `viewer` cannot send agent turns. Runtime approval and stop operations require an `admin` pairing with a provisioned `mosaicUserId`.

The bot needs Discord permissions to view/send messages in configured channels and create/send in public threads. A channel category is not an authorization boundary; threads inherit authorization only from their configured parent text channel.

## Shared contract

The adapter implements `OfficialChannelAdapter` from `@mosaicstack/types`. `ChannelConversationRouteDto` carries only stable logical-agent/channel identity and a response target. Gateway durable-session and provider layers own runtime selection and handoff; Discord code must not import a harness SDK.

## Development

```bash
pnpm --filter @mosaicstack/types build
pnpm --filter @mosaicstack/discord-plugin typecheck
pnpm --filter @mosaicstack/discord-plugin lint
pnpm --filter @mosaicstack/discord-plugin test
pnpm --filter @mosaicstack/discord-plugin build
```
