# Discord Bot Setup

> All user, server, channel and role IDs below are synthetic. Replace them with your own IDs.

GoClaw provides full support for Discord, including direct messages, guild channels, slash commands, and file processing.

---

## Quick Setup

### 1. Create a Discord Application and Bot

1. Go to the [Discord Developer Portal](https://discord.com/developers/applications) and click **New Application**.
2. Name your application and agree to the terms.
3. Click **Bot** on the sidebar.
4. Set the bot **Username** and (optionally) an icon.

### 2. Enable Privileged Intents

GoClaw requires specific intents to see and process messages. On the **Bot** page, scroll down to **Privileged Gateway Intents** and enable:

- **Message Content Intent** (Required: to read messages)

### 3. Copy Your Bot Token

On the **Bot** page, click **Reset Token**. Copy and save the token securely. You will need it for the GoClaw configuration.

### 4. Generate an Invite URL

Click **OAuth2** on the sidebar, then **URL Generator**. Enable:

- **Scopes:** `bot`, `applications.commands`
- **Bot Permissions:**
  - View Channels
  - Send Messages
  - Read Message History
  - Embed Links
  - Attach Files
  - Add Reactions (the status reactions on each message the bot works on)
  - Create Public Threads and Send Messages in Threads (for `auto_thread` and for answering inside threads)

Copy the generated URL and open it in your browser to add the bot to your server.

### 5. Enable Developer Mode

In your Discord client: **User Settings** → **Advanced** → toggle on **Developer Mode**. This allows you to right-click on users, roles, or channels to copy their IDs — needed for allowlists and per-guild configuration.

### 6. Configure GoClaw

Edit your `~/.goclaw/config.yaml` to include the Discord section:

```yaml
channels:
  discord:
    enabled: true
    token: "YOUR_BOT_TOKEN"
```

To keep the token out of the file, use a reference: `token: "${DISCORD_BOT_TOKEN}"` reads the environment variable, and `token: "${secret:DISCORD_BOT_TOKEN}"` reads the encrypted vault. GoClaw does not read `DISCORD_BOT_TOKEN` by itself; the channel starts only when `enabled` is true and `token` is set.

### 7. Start and Pair

Start GoClaw and send a DM to your bot. If `dm_policy` is set to `pairing` (the default), it will respond with a pairing code.

Approve the request on the Web UI (**Pairing** tab). The CLI works too, but a running gateway does not see a CLI approval until it restarts — see [Pairing](pairing.md):
```bash
./goclaw pairing approve <CODE>
```

---

## Runtime Behavior

### Message Handling
- **Direct Messages (DMs):** The bot responds to all DMs from approved users.
- **Guild Channels:** By default, the bot responds only when **@mentioned** or **replied to**. The mention text is automatically stripped before the message is sent to the agent. Each person has their own conversation with the bot in each channel.
- **Threads:** Each person has their own conversation with the bot in a thread, and a thread follows its channel's settings unless it has an entry of its own (see [Threads](#threads)).

### Slash Commands
GoClaw automatically registers several slash commands:

| Command | Description |
|---------|-------------|
| `/ask <question>` | Ask the agent a question using a deferred reply (bypasses Discord's 3-second timeout). The question goes into your own conversation with the bot in that channel, the one your messages there are in. |
| `/memory [show\|clear]` | Sends the agent the message "Show me my memory contents. Action: show" (or `clear`) and posts its answer. The command itself clears nothing. |
| `/status` | Replies with a fixed line: "GoClaw is running. Use the web UI for detailed stats." |

Slash commands do not need an @mention. In a server they follow the same guild and channel settings as messages: a channel set to `allow: false`, or a member not on the `users`/`roles` allowlist, gets a private "You cannot use this bot in this channel." and the agent is not asked. In a DM, the DM policy (pairing) applies as it does to messages.

### Text Attachments
Discord file attachments with text-readable MIME types (e.g., `.md`, `.txt`, `.json`, `.py`, `.go`, `.js`, `.ts`, `.sql`, `.csv`, `.yaml`, `.xml`) are automatically downloaded and included in the message context. Only the first 256 KB of a text file is included.

Binary files (images, audio, PDFs) are saved to `workspace/uploads/` and the agent is given the local path to process them.

A text file larger than 10 MB, or a binary file larger than 50 MB, is not read; the agent is told it was attached and could not be read, and the rest of the message is still answered.

### Typing Indicator
A typing indicator is shown while the agent processes the message, refreshing every 8 seconds to ensure visibility.

### Message Chunking
Messages exceeding 2,000 characters are split at line breaks. Code blocks are properly closed and reopened across chunks to maintain formatting.

---

## Access Control

### DM Policy
Controlled via `channels.dm_policy` in `config.yaml`. Despite the name, it applies to guild messages too: an unapproved member who @mentions the bot in a server channel gets the pairing reply in that channel.

| Value | Behavior |
|-------|----------|
| `pairing` (default) | Unknown users receive a one-time pairing code; owner approves via Web UI or CLI |
| `allowlist` | Only user IDs listed in `channels.allow_from`, and users already approved through pairing, get an answer; no codes are issued |
| `open` | Anyone can use the bot without approval |

`allow_from: ["*"]` admits everyone. The guild settings below are checked in addition to this policy, not instead of it.

```yaml
channels:
  dm_policy: "pairing"      # pairing | allowlist | open
  allow_from:
    - "100000000000000103"   # Discord user IDs
```

---

## Guild Channel Restrictions

GoClaw offers a **per-guild** and **per-channel** access control system. This lets you:

- Require or remove the @mention requirement per channel
- Limit access to specific users or roles
- Disable the bot entirely in certain channels

### How Precedence Works

Resolution order (most specific wins):

```
per-channel config  →  per-guild config  →  global default (allow all)
```

1. **Channel-level** (`guilds.<id>.channels.<channel_id>`) is checked first.
2. **Guild-level** (`guilds.<id>.users` / `guilds.<id>.roles`) is the fallback.
3. If neither is configured, all users are allowed.

A channel that is not listed under `channels` follows the guild-level settings, so listing one channel does not keep the bot out of the others.

---

### Per-Guild Configuration

Under `channels.discord.guilds`, add an entry keyed by the Guild (Server) ID:

```yaml
channels:
  discord:
    enabled: true
    token: "YOUR_BOT_TOKEN"
    guilds:
      "100000000000000103":          # Guild ID (right-click server → Copy Server ID)
        require_mention: true        # true = bot must be @mentioned (default for guilds)
        users:                       # Allowlist: only these users can interact
          - "100000000000000101"
          - "100000000000000106"
        roles:                       # Allowlist: only members with these roles can interact
          - "100000000000000111"      # Role ID (right-click role → Copy ID)
        reaction_notifications: "own"  # off | own | all
```

**Guild-level fields:**

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `require_mention` | bool | `true` in guilds | Whether the bot must be @mentioned to respond |
| `users` | []string | — | Discord user IDs allowed to interact. If set, only these users are allowed |
| `roles` | []string | — | Discord role IDs allowed to interact. If set, only members with at least one listed role are allowed |
| `reaction_notifications` | string | `"off"` | Controls emoji reaction forwarding: `off`, `own` (bot's messages only), `all` |
| `channels` | map | — | Per-channel overrides (see below) |

> **Note:** If both `users` and `roles` are empty at the guild level, all users in that guild are permitted (subject to channel-level rules).
>
> A guild entry also accepts `slug`, but GoClaw does not use it.

---

### Per-Channel Configuration

Inside a guild entry, add a `channels` map keyed by Discord channel ID:

```yaml
guilds:
  "100000000000000103":
    channels:
      "100000000000000110":          # Channel ID (right-click channel → Copy ID)
        allow: true                  # true = enabled, false = disabled entirely
        require_mention: false       # override: respond to all messages, no @mention needed
        auto_thread: true            # Create a thread for each message automatically
        users:                       # Channel-level user allowlist
          - "100000000000000101"
        roles:                       # Channel-level role allowlist
          - "100000000000000108"
```

**Per-channel fields:**

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `allow` | *bool | nil (inherit) | `false` disables the bot entirely in this channel regardless of guild config |
| `require_mention` | *bool | nil (inherit guild or global) | `false` lets the bot respond to all messages without @mention; `true` enforces it |
| `auto_thread` | bool | `false` | Automatically creates a Discord thread per message |
| `users` | []string | — | Channel-specific user allowlist (overrides guild allowlist) |
| `roles` | []string | — | Channel-specific role allowlist (overrides guild allowlist) |

A channel entry also accepts `system_prompt`, but GoClaw does not use it: nothing is added to the agent's prompt.

### Threads

Inside a thread, GoClaw uses the thread's own entry under `channels` when it has one, and otherwise its channel's. A channel's `allow`, `require_mention`, `users` and `roles` therefore apply in its threads too: a channel set to `allow: false` is closed in its threads as well, and in a channel with `require_mention: false` its threads need no @mention either.

With `auto_thread: true`, the bot answers each new message in the channel in a thread it creates from that message. The conversation belongs to that thread from the first message, so the agent sees the whole exchange when the person goes on writing in the thread.

---

### Common Patterns

#### Public server — bot responds anywhere it's mentioned

```yaml
channels:
  discord:
    enabled: true
    token: "YOUR_BOT_TOKEN"
    # No guilds config needed: bot responds to @mentions everywhere by default
```

#### Answer every message in one channel, stay out of another

```yaml
channels:
  discord:
    enabled: true
    token: "YOUR_BOT_TOKEN"
    guilds:
      "100000000000000103":
        channels:
          "100000000000000110":
            allow: true
            require_mention: false   # Respond to every message in this channel
          "100000000000000101":
            allow: false             # Explicitly disable bot in this channel
```

Channels not listed here still answer @mentions. GoClaw has no "only this channel" switch: to keep the bot to one channel, list every other channel with `allow: false`, or deny the bot the **View Channels** permission in the other channels in Discord's own channel settings.

#### Allow only specific roles (e.g., "Admin", "Staff")

```yaml
channels:
  discord:
    enabled: true
    token: "YOUR_BOT_TOKEN"
    guilds:
      "100000000000000103":
        roles:
          - "100000000000000107"     # Admin role ID
          - "100000000000000108"     # Staff role ID
```

#### Per-channel role override — different access per channel

```yaml
guilds:
  "100000000000000103":
    roles:
      - "100000000000000107"         # Guild default: Admin only
    channels:
      "100000000000000110":          # This channel
        roles:
          - "100000000000000109"     # Allowed for a different role (e.g., "Support Team")
          - "100000000000000107"
```

#### Auto-thread channel

```yaml
guilds:
  "100000000000000103":
    channels:
      "100000000000000110":
        allow: true
        require_mention: false
        auto_thread: true
```

Follow-ups inside the created threads need an @mention or a reply to the bot. See [Threads](#threads) for how the conversation is split between the channel and the thread.

#### Reaction notifications — bot forwards reactions as messages

```yaml
guilds:
  "100000000000000103":
    reaction_notifications: "own"   # Only reactions on bot's own messages
```

---

## Bot Presence & Activity

Control the bot's online status and activity text:

```yaml
channels:
  discord:
    status: "online"         # online | dnd | idle | invisible
    activity: "tasks"
    activity_type: 3         # 0=Playing, 1=Streaming, 2=Listening, 3=Watching, 4=Custom, 5=Competing
```

Presence is set only when `activity` is set: without it, `status` is ignored too and the bot shows as plain online. An unset `activity_type` is `0` (Playing). Avoid `4` (Custom): Discord shows a custom status's separate state text, which GoClaw does not set.

---

## Streaming Mode

By default, the bot sends a complete reply after processing.

```yaml
channels:
  discord:
    streaming: "partial"     # off (default) | partial
```

In `partial` mode, a `_thinking…_` placeholder message is posted right away, shows the answer as it is written (updated at most every 1.5 seconds), and is replaced by the full reply when the agent finishes. In this mode the status reactions are not shown, and the reply is split at 2,000 characters whatever `text_chunk_limit` and `max_lines_per_message` say.

---

## Advanced Options

```yaml
channels:
  discord:
    enabled: true
    token: "YOUR_BOT_TOKEN"

    text_chunk_limit: 2000       # Max characters per message (Discord hard cap: 2000)
    max_lines_per_message: 17    # Soft line limit; set to avoid collapsed walls of text
    allow_bots: false            # Whether other bot messages trigger responses
    ack_reaction: ""             # Override status emoji (e.g. "✅"). Empty = animated sequence
    ack_reaction_scope: ""       # "off" turns status reactions off; any other value leaves them on
    streaming: "off"             # "off" | "partial"
    status: "online"             # online | dnd | idle | invisible (needs activity)
    activity: "tasks"            # Presence text
    activity_type: 3             # 0=Playing 1=Streaming 2=Listening 3=Watching 5=Competing
```

`ack_reaction_scope` has one effective value, `off`. `group-mentions` and `all` behave like the empty default: reactions are shown on every message the bot works on.

---

## Configuration Reference Summary

```yaml
channels:
  dm_policy: "pairing"          # pairing | allowlist | open
  allow_from:                   # User IDs for allowlist DM policy
    - "100000000000000103"

  discord:
    enabled: true
    token: "YOUR_BOT_TOKEN"
    text_chunk_limit: 2000
    max_lines_per_message: 0    # 0 = unlimited
    streaming: "off"
    allow_bots: false
    status: "online"
    activity: ""
    activity_type: 0
    ack_reaction: ""
    ack_reaction_scope: ""

    guilds:
      "<GUILD_ID>":
        require_mention: true
        reaction_notifications: "off"   # off | own | all
        users:
          - "<USER_ID>"
        roles:
          - "<ROLE_ID>"
        channels:
          "<CHANNEL_ID>":
            allow: true                 # false = disable bot in this channel
            require_mention: false
            auto_thread: false
            users:
              - "<USER_ID>"
            roles:
              - "<ROLE_ID>"
```

---

## Troubleshooting

### Bot sees no messages
- Verify **Message Content Intent** is enabled in the Discord Developer Portal.
- Restart the GoClaw gateway after changing intents.

### Guild messages not working
- Ensure the bot has **View Channels** and **Read Message History** permissions in the server.
- Remember: the bot only responds when @mentioned or replied to in guild channels by default.
- If you have a `guilds` config, check that the Guild ID is correct (right-click server icon → Copy Server ID).

### A user is being blocked unexpectedly
- Check if `users` or `roles` is set at the guild level — if so, only listed entries are allowed.
- Channel-level `allow: false` will silently block all messages in that channel.
- Channel-level `users`/`roles` overrides guild-level allowlists; ensure the user/role ID appears at the right level.
- In a thread, the parent channel's settings do not apply; the guild-level ones do (see [Threads](#threads)).
- If the bot answers with a pairing code, the user is not approved yet: `dm_policy` applies in server channels too.

### Pairing not working
- Check that DMs are enabled for your account: **User Settings** → **Privacy** → **Allow DMs from server members**.
- Verify `dm_policy` is set to `pairing` (not `allowlist`).
- List pending requests with `goclaw pairing pending discord` or on the Web UI's **Pairing** tab (they are kept in `<state dir>/credentials/goclaw-pending.json`).
- If you approved with the CLI while the gateway was running, restart the gateway, or approve on the Web UI instead.

### Finding IDs (Developer Mode required)
| Object | How to copy ID |
|--------|----------------|
| Guild (Server) | Right-click the server icon in the sidebar → **Copy Server ID** |
| Channel | Right-click the channel name → **Copy Channel ID** |
| User | Right-click on a username in chat or member list → **Copy User ID** |
| Role | **Server Settings** → **Roles** → right-click a role → **Copy Role ID** |
