@hoverlover/cc-discord 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.template.json +94 -0
- package/.env.example +41 -0
- package/.env.relay.example +46 -0
- package/.env.worker.example +40 -0
- package/README.md +313 -0
- package/hooks/check-discord-messages.ts +204 -0
- package/hooks/cleanup-attachment.ts +47 -0
- package/hooks/safe-bash.ts +157 -0
- package/hooks/steer-send.ts +108 -0
- package/hooks/track-activity.ts +220 -0
- package/memory/README.md +60 -0
- package/memory/core/MemoryCoordinator.ts +703 -0
- package/memory/core/MemoryStore.ts +72 -0
- package/memory/core/session-key.ts +14 -0
- package/memory/core/types.ts +59 -0
- package/memory/index.ts +19 -0
- package/memory/providers/sqlite/SqliteMemoryStore.ts +838 -0
- package/memory/providers/sqlite/index.ts +1 -0
- package/package.json +45 -0
- package/prompts/autoreply-system.md +32 -0
- package/prompts/channel-system.md +22 -0
- package/prompts/orchestrator-system.md +56 -0
- package/scripts/channel-agent.sh +159 -0
- package/scripts/generate-settings.sh +17 -0
- package/scripts/load-env.sh +79 -0
- package/scripts/migrate-memory-to-channel-keys.ts +148 -0
- package/scripts/orchestrator.sh +325 -0
- package/scripts/parse-claude-stream.ts +349 -0
- package/scripts/start-orchestrator.sh +82 -0
- package/scripts/start-relay.sh +17 -0
- package/scripts/start.sh +175 -0
- package/server/attachment.ts +182 -0
- package/server/busy-notify.ts +69 -0
- package/server/config.ts +121 -0
- package/server/db.ts +249 -0
- package/server/index.ts +311 -0
- package/server/memory.ts +88 -0
- package/server/messages.ts +111 -0
- package/server/trace-thread.ts +340 -0
- package/server/typing.ts +101 -0
- package/tools/memory-inspect.ts +94 -0
- package/tools/memory-smoke.ts +173 -0
- package/tools/send-discord +2 -0
- package/tools/send-discord.ts +82 -0
- package/tools/wait-for-discord-messages +2 -0
- package/tools/wait-for-discord-messages.ts +369 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"PreToolUse": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "Bash",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "__ORCHESTRATOR_DIR__/hooks/safe-bash.ts"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"type": "command",
|
|
13
|
+
"command": "__ORCHESTRATOR_DIR__/hooks/steer-send.ts"
|
|
14
|
+
}
|
|
15
|
+
]
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"hooks": [
|
|
19
|
+
{
|
|
20
|
+
"type": "command",
|
|
21
|
+
"command": "__ORCHESTRATOR_DIR__/hooks/track-activity.ts"
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"SessionStart": [
|
|
27
|
+
{
|
|
28
|
+
"hooks": [
|
|
29
|
+
{
|
|
30
|
+
"type": "command",
|
|
31
|
+
"command": "__ORCHESTRATOR_DIR__/hooks/track-activity.ts"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"type": "command",
|
|
35
|
+
"command": "__ORCHESTRATOR_DIR__/hooks/check-discord-messages.ts"
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
"PostToolUse": [
|
|
41
|
+
{
|
|
42
|
+
"matcher": "Read",
|
|
43
|
+
"hooks": [
|
|
44
|
+
{
|
|
45
|
+
"type": "command",
|
|
46
|
+
"command": "__ORCHESTRATOR_DIR__/hooks/cleanup-attachment.ts"
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"hooks": [
|
|
52
|
+
{
|
|
53
|
+
"type": "command",
|
|
54
|
+
"command": "__ORCHESTRATOR_DIR__/hooks/track-activity.ts"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"type": "command",
|
|
58
|
+
"command": "__ORCHESTRATOR_DIR__/hooks/check-discord-messages.ts"
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
],
|
|
63
|
+
"UserPromptSubmit": [
|
|
64
|
+
{
|
|
65
|
+
"hooks": [
|
|
66
|
+
{
|
|
67
|
+
"type": "command",
|
|
68
|
+
"command": "__ORCHESTRATOR_DIR__/hooks/check-discord-messages.ts"
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
],
|
|
73
|
+
"Stop": [
|
|
74
|
+
{
|
|
75
|
+
"hooks": [
|
|
76
|
+
{
|
|
77
|
+
"type": "command",
|
|
78
|
+
"command": "__ORCHESTRATOR_DIR__/hooks/track-activity.ts"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"type": "command",
|
|
82
|
+
"command": "__ORCHESTRATOR_DIR__/hooks/check-discord-messages.ts"
|
|
83
|
+
}
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
},
|
|
88
|
+
"permissions": {
|
|
89
|
+
"allow": [
|
|
90
|
+
"Bash(send-discord:*)",
|
|
91
|
+
"Bash(wait-for-discord-messages:*)"
|
|
92
|
+
]
|
|
93
|
+
}
|
|
94
|
+
}
|
package/.env.example
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Legacy single-file configuration (kept for compatibility).
|
|
2
|
+
# Prefer split files: .env.relay and .env.worker (see *.example files).
|
|
3
|
+
|
|
4
|
+
# Discord bot token from Discord Developer Portal
|
|
5
|
+
DISCORD_BOT_TOKEN=
|
|
6
|
+
|
|
7
|
+
# Default channel for inbound listening and outbound posting
|
|
8
|
+
DISCORD_CHANNEL_ID=
|
|
9
|
+
|
|
10
|
+
# Optional comma-separated allowlist (if unset, only DISCORD_CHANNEL_ID is accepted)
|
|
11
|
+
DISCORD_ALLOWED_CHANNEL_IDS=
|
|
12
|
+
# Optional comma-separated Discord user IDs allowed to trigger Claude.
|
|
13
|
+
# If empty, all users in allowed channels are accepted.
|
|
14
|
+
ALLOWED_DISCORD_USER_IDS=
|
|
15
|
+
|
|
16
|
+
# Session identifier Claude hook/tools use to scope messages
|
|
17
|
+
DISCORD_SESSION_ID=default
|
|
18
|
+
|
|
19
|
+
# Relay API settings
|
|
20
|
+
RELAY_HOST=127.0.0.1
|
|
21
|
+
RELAY_PORT=3199
|
|
22
|
+
# REQUIRED in hardened mode (recommended). Use a long random value.
|
|
23
|
+
RELAY_API_TOKEN=
|
|
24
|
+
# Set true only for local dev if you intentionally want no API auth.
|
|
25
|
+
RELAY_ALLOW_NO_AUTH=false
|
|
26
|
+
|
|
27
|
+
# Typing indicator heartbeat (ms)
|
|
28
|
+
TYPING_INTERVAL_MS=8000
|
|
29
|
+
# Stop typing automatically after this max duration (ms)
|
|
30
|
+
TYPING_MAX_MS=120000
|
|
31
|
+
# Send a fallback status message when typing expires (true/false)
|
|
32
|
+
THINKING_FALLBACK_ENABLED=true
|
|
33
|
+
# Fallback message content
|
|
34
|
+
THINKING_FALLBACK_TEXT=Still working on that—thanks for your patience.
|
|
35
|
+
|
|
36
|
+
# Routing key used by both relay and start:autoreply.
|
|
37
|
+
# start:autoreply will default AGENT_ID to this value.
|
|
38
|
+
CLAUDE_AGENT_ID=claude
|
|
39
|
+
|
|
40
|
+
# Auto-reply permission mode: "skip" (default) or "accept-edits"
|
|
41
|
+
AUTO_REPLY_PERMISSION_MODE=skip
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Relay process configuration (Discord bot + relay server)
|
|
2
|
+
|
|
3
|
+
# Discord bot token from Discord Developer Portal
|
|
4
|
+
DISCORD_BOT_TOKEN=
|
|
5
|
+
|
|
6
|
+
# Default channel for inbound listening and outbound posting
|
|
7
|
+
DISCORD_CHANNEL_ID=
|
|
8
|
+
|
|
9
|
+
# Optional comma-separated allowlist (if unset, only DISCORD_CHANNEL_ID is accepted)
|
|
10
|
+
DISCORD_ALLOWED_CHANNEL_IDS=
|
|
11
|
+
|
|
12
|
+
# Optional comma-separated Discord user IDs allowed to trigger Claude.
|
|
13
|
+
# If empty, all users in allowed channels are accepted.
|
|
14
|
+
ALLOWED_DISCORD_USER_IDS=
|
|
15
|
+
|
|
16
|
+
# Routing scope used for queued messages
|
|
17
|
+
DISCORD_SESSION_ID=default
|
|
18
|
+
# Target Claude agent key written into queue rows
|
|
19
|
+
CLAUDE_AGENT_ID=claude
|
|
20
|
+
|
|
21
|
+
# Message routing mode:
|
|
22
|
+
# - channel (default): route to to_agent=channelId (orchestrator/subagent mode)
|
|
23
|
+
# - agent: route to to_agent=CLAUDE_AGENT_ID (single-agent mode)
|
|
24
|
+
MESSAGE_ROUTING_MODE=channel
|
|
25
|
+
|
|
26
|
+
# Relay API settings
|
|
27
|
+
RELAY_HOST=127.0.0.1
|
|
28
|
+
RELAY_PORT=3199
|
|
29
|
+
# REQUIRED in hardened mode (recommended). Use a long random value.
|
|
30
|
+
RELAY_API_TOKEN=
|
|
31
|
+
# Set true only for local dev if you intentionally want no API auth.
|
|
32
|
+
RELAY_ALLOW_NO_AUTH=false
|
|
33
|
+
|
|
34
|
+
# Typing indicator heartbeat (ms)
|
|
35
|
+
TYPING_INTERVAL_MS=8000
|
|
36
|
+
# Stop typing automatically after this max duration (ms)
|
|
37
|
+
TYPING_MAX_MS=120000
|
|
38
|
+
# Send a fallback status message when typing expires (true/false)
|
|
39
|
+
THINKING_FALLBACK_ENABLED=true
|
|
40
|
+
# Fallback message content
|
|
41
|
+
THINKING_FALLBACK_TEXT=Still working on that—thanks for your patience.
|
|
42
|
+
|
|
43
|
+
# If Claude is currently busy on another tool, send a queued-status note.
|
|
44
|
+
BUSY_NOTIFY_ON_QUEUE=true
|
|
45
|
+
# Cooldown per activity window/channel (ms)
|
|
46
|
+
BUSY_NOTIFY_COOLDOWN_MS=30000
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Claude auto-reply worker configuration (safe to expose to worker process)
|
|
2
|
+
|
|
3
|
+
# Must match relay routing scope/agent target
|
|
4
|
+
DISCORD_SESSION_ID=default
|
|
5
|
+
CLAUDE_AGENT_ID=claude
|
|
6
|
+
# Optional runtime hint; auto-generated by start script if omitted.
|
|
7
|
+
CLAUDE_RUNTIME_ID=
|
|
8
|
+
|
|
9
|
+
# Relay location for send-discord/wait-for-discord-messages tools
|
|
10
|
+
RELAY_HOST=127.0.0.1
|
|
11
|
+
RELAY_PORT=3199
|
|
12
|
+
# Optional direct override
|
|
13
|
+
# RELAY_URL=http://127.0.0.1:3199
|
|
14
|
+
|
|
15
|
+
# Must match relay token if auth is enabled
|
|
16
|
+
RELAY_API_TOKEN=
|
|
17
|
+
|
|
18
|
+
# Auto-reply permission mode: "skip" (default) or "accept-edits"
|
|
19
|
+
AUTO_REPLY_PERMISSION_MODE=skip
|
|
20
|
+
|
|
21
|
+
# Optional: when true (default), wait command timeout exits quietly with code 0.
|
|
22
|
+
WAIT_QUIET_TIMEOUT=true
|
|
23
|
+
|
|
24
|
+
# Bash safety policy for Claude tool calls.
|
|
25
|
+
# - block (default): block risky background patterns
|
|
26
|
+
# - allow: allow risky patterns but still notify in Discord
|
|
27
|
+
BASH_POLICY_MODE=block
|
|
28
|
+
|
|
29
|
+
# Fine-grained allow toggles
|
|
30
|
+
ALLOW_BASH_RUN_IN_BACKGROUND=true
|
|
31
|
+
ALLOW_BASH_BACKGROUND_OPS=false
|
|
32
|
+
|
|
33
|
+
# Notify Discord when safety policy is triggered
|
|
34
|
+
BASH_POLICY_NOTIFY_ON_BLOCK=true
|
|
35
|
+
# Optional override channel for policy notifications (defaults to relay channel)
|
|
36
|
+
BASH_POLICY_NOTIFY_CHANNEL_ID=
|
|
37
|
+
|
|
38
|
+
# Stuck agent detection: seconds without a heartbeat + unread messages = stuck.
|
|
39
|
+
# Default 900 (15 min). Set higher if agents routinely do long tasks.
|
|
40
|
+
STUCK_AGENT_THRESHOLD=900
|
package/README.md
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
# cc-discord
|
|
2
|
+
|
|
3
|
+
**Discord <-> Claude Code relay** — power per-channel AI Agents using your existing Claude subscription (no API key needed).
|
|
4
|
+
|
|
5
|
+
- One autonomous Claude Code agent per Discord channel
|
|
6
|
+
- Messages stored in SQLite, delivered to agents via hooks
|
|
7
|
+
- Replies sent back to Discord via `send-discord` tool
|
|
8
|
+
- Typing indicators, busy notifications, live trace threads, memory context, attachment support, and more
|
|
9
|
+
|
|
10
|
+
## Quick start
|
|
11
|
+
|
|
12
|
+
### Prerequisites
|
|
13
|
+
|
|
14
|
+
- [Bun](https://bun.sh) runtime installed
|
|
15
|
+
- [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed and authenticated (`claude auth login`)
|
|
16
|
+
- A Discord bot (see [Create a Discord bot](#create-a-discord-bot) below)
|
|
17
|
+
|
|
18
|
+
### Option A: Run directly with bunx (recommended)
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
bunx @hoverlover/cc-discord
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
This installs and runs cc-discord in one step. On first run, config files are created at `~/.config/cc-discord/`:
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
~/.config/cc-discord/
|
|
28
|
+
├── .env.relay # Discord bot token, channel ID, relay token
|
|
29
|
+
└── .env.worker # Relay API token, worker settings
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Edit those files with your credentials, then run again. Override the config location with `CC_DISCORD_CONFIG_DIR`.
|
|
33
|
+
|
|
34
|
+
### Option B: Clone the repo (contributors)
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
git clone https://github.com/hoverlover/cc-discord.git
|
|
38
|
+
cd cc-discord
|
|
39
|
+
bun install
|
|
40
|
+
bun run generate-settings
|
|
41
|
+
bun start
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`bun start` launches both the relay server and the orchestrator in a single process. No second terminal needed.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Create a Discord server
|
|
49
|
+
|
|
50
|
+
If you don't already have a Discord server to use:
|
|
51
|
+
|
|
52
|
+
1. Open Discord and click the **+** button in the left sidebar
|
|
53
|
+
2. Choose **Create My Own**, then select a template (or skip)
|
|
54
|
+
3. Name your server and click **Create**
|
|
55
|
+
4. Create one or more text channels where you want the bot to respond
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Create a Discord bot
|
|
60
|
+
|
|
61
|
+
1. Go to [Discord Developer Portal](https://discord.com/developers/applications)
|
|
62
|
+
2. Click **New Application** and give it a name
|
|
63
|
+
3. Go to the **Bot** section in the left sidebar
|
|
64
|
+
4. Click **Add Bot**, then **Reset Token** and copy the bot token
|
|
65
|
+
5. Enable the following **Privileged Gateway Intents**:
|
|
66
|
+
- **Message Content Intent** (required — without this the bot cannot read message text)
|
|
67
|
+
- **Server Members Intent** (optional)
|
|
68
|
+
6. Go to **OAuth2 > URL Generator**:
|
|
69
|
+
- Under **Scopes**, select: `bot`, `applications.commands`
|
|
70
|
+
- Under **Bot Permissions**, select: `Send Messages`, `Read Messages/View Channels`, `Read Message History`, `Manage Threads`
|
|
71
|
+
7. Copy the generated URL and open it in your browser to invite the bot to your server
|
|
72
|
+
8. In your Discord server, note the **channel ID(s)** you want the bot to respond in:
|
|
73
|
+
- Enable Developer Mode in Discord settings (User Settings > Advanced > Developer Mode)
|
|
74
|
+
- Right-click a channel and select **Copy Channel ID**
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Configure environment
|
|
79
|
+
|
|
80
|
+
**bunx users:** env files are auto-created at `~/.config/cc-discord/` on first run. Just edit them there.
|
|
81
|
+
|
|
82
|
+
**Cloned-repo users:** copy the example files into the project root:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
cp .env.relay.example .env.relay
|
|
86
|
+
cp .env.worker.example .env.worker
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Project-local env files take precedence over `~/.config/cc-discord/`, so cloned-repo users are unaffected by the config directory.
|
|
90
|
+
|
|
91
|
+
### `.env.relay` — required
|
|
92
|
+
|
|
93
|
+
| Variable | Description |
|
|
94
|
+
|---|---|
|
|
95
|
+
| `DISCORD_BOT_TOKEN` | Bot token from the Developer Portal |
|
|
96
|
+
| `DISCORD_CHANNEL_ID` | Default channel ID the bot operates in |
|
|
97
|
+
| `RELAY_API_TOKEN` | Shared secret between relay and worker (any random string) |
|
|
98
|
+
|
|
99
|
+
### `.env.relay` — optional
|
|
100
|
+
|
|
101
|
+
| Variable | Default | Description |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| `DISCORD_ALLOWED_CHANNEL_IDS` | _(all)_ | Comma-separated allowlist of channel IDs |
|
|
104
|
+
| `DISCORD_IGNORED_CHANNEL_IDS` | _(none)_ | Comma-separated list of channel IDs to ignore |
|
|
105
|
+
| `ALLOWED_DISCORD_USER_IDS` | _(all)_ | Comma-separated list of user IDs that can interact |
|
|
106
|
+
| `MESSAGE_ROUTING_MODE` | `channel` | `channel` (orchestrator/subagent) or `agent` (single-agent) |
|
|
107
|
+
| `RELAY_HOST` | `127.0.0.1` | Host for the relay HTTP API |
|
|
108
|
+
| `RELAY_PORT` | `3199` | Port for the relay HTTP API |
|
|
109
|
+
| `RELAY_ALLOW_NO_AUTH` | `false` | Set `true` for local dev without a token |
|
|
110
|
+
| `TYPING_INTERVAL_MS` | `8000` | Typing indicator heartbeat interval (ms) |
|
|
111
|
+
| `TYPING_MAX_MS` | `120000` | Max typing duration before sending fallback |
|
|
112
|
+
| `THINKING_FALLBACK_ENABLED` | `true` | Send a fallback message if Claude takes too long |
|
|
113
|
+
| `THINKING_FALLBACK_TEXT` | _"Still working on that..."_ | Fallback message content |
|
|
114
|
+
| `BUSY_NOTIFY_ON_QUEUE` | `true` | Notify user if Claude is busy when their message arrives |
|
|
115
|
+
| `BUSY_NOTIFY_COOLDOWN_MS` | `30000` | Min time between busy notifications (ms) |
|
|
116
|
+
| `BUSY_NOTIFY_MIN_DURATION_MS` | `30000` | Only send busy notification if current activity has been running this long (ms) |
|
|
117
|
+
| `TRACE_THREAD_ENABLED` | `true` | Create live trace threads showing agent activity |
|
|
118
|
+
| `TRACE_THREAD_NAME` | `⚙️ Live Trace` | Name of the trace thread |
|
|
119
|
+
| `TRACE_FLUSH_INTERVAL_MS` | `3000` | How often trace events are flushed to Discord (ms) |
|
|
120
|
+
| `MAX_ATTACHMENT_INLINE_BYTES` | `100000` | Max bytes for inline attachment content |
|
|
121
|
+
| `MAX_ATTACHMENT_DOWNLOAD_BYTES` | `10000000` | Max bytes for downloaded attachments |
|
|
122
|
+
| `ATTACHMENT_TTL_MS` | `3600000` | TTL for downloaded attachment files (ms) |
|
|
123
|
+
|
|
124
|
+
### `.env.worker` — required
|
|
125
|
+
|
|
126
|
+
| Variable | Description |
|
|
127
|
+
|---|---|
|
|
128
|
+
| `RELAY_API_TOKEN` | Must match the token in `.env.relay` |
|
|
129
|
+
|
|
130
|
+
### `.env.worker` — optional
|
|
131
|
+
|
|
132
|
+
| Variable | Default | Description |
|
|
133
|
+
|---|---|---|
|
|
134
|
+
| `RELAY_HOST` | `127.0.0.1` | Relay server host |
|
|
135
|
+
| `RELAY_PORT` | `3199` | Relay server port |
|
|
136
|
+
| `RELAY_URL` | _(derived)_ | Full relay URL (overrides host/port) |
|
|
137
|
+
| `DISCORD_SESSION_ID` | `default` | Session identifier for message routing |
|
|
138
|
+
| `CLAUDE_AGENT_ID` | `claude` | Agent identifier for message routing |
|
|
139
|
+
| `AUTO_REPLY_PERMISSION_MODE` | `skip` | `skip` (fully autonomous) or `accept-edits` (safer) |
|
|
140
|
+
| `CLAUDE_RUNTIME_ID` | _(auto)_ | Runtime context identifier for memory |
|
|
141
|
+
| `WAIT_QUIET_TIMEOUT` | `true` | Exit quietly on timeout (no noise in Claude UI) |
|
|
142
|
+
| `BASH_POLICY_MODE` | `block` | `block` or `allow` for background bash operations |
|
|
143
|
+
| `ALLOW_BASH_RUN_IN_BACKGROUND` | `true` | Allow `run_in_background=true` in Bash tool |
|
|
144
|
+
| `ALLOW_BASH_BACKGROUND_OPS` | `false` | Allow `&` background operator in commands |
|
|
145
|
+
| `BASH_POLICY_NOTIFY_ON_BLOCK` | `true` | Send Discord notification when bash is blocked |
|
|
146
|
+
| `BASH_POLICY_NOTIFY_CHANNEL_ID` | _(none)_ | Channel to send bash policy notifications to |
|
|
147
|
+
| `STUCK_AGENT_THRESHOLD` | `900` | Seconds without heartbeat + unread messages before agent is considered stuck |
|
|
148
|
+
|
|
149
|
+
### Orchestrator env vars
|
|
150
|
+
|
|
151
|
+
These are read by the orchestrator shell script:
|
|
152
|
+
|
|
153
|
+
| Variable | Default | Description |
|
|
154
|
+
|---|---|---|
|
|
155
|
+
| `HEALTH_CHECK_INTERVAL` | `30` | Seconds between health checks |
|
|
156
|
+
| `AGENT_RESTART_DELAY` | `5` | Seconds to wait before restarting a dead agent |
|
|
157
|
+
| `CC_DISCORD_CONFIG_DIR` | `~/.config/cc-discord` | Directory for user config env files |
|
|
158
|
+
| `CC_DISCORD_LOG_DIR` | `/tmp/cc-discord/logs` | Directory for all log files |
|
|
159
|
+
|
|
160
|
+
Security note: the worker process intentionally does not receive `DISCORD_BOT_TOKEN`.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Architecture
|
|
165
|
+
|
|
166
|
+
```
|
|
167
|
+
┌──────────────────────────────────────────────────────────┐
|
|
168
|
+
│ bun start (scripts/start.sh) │
|
|
169
|
+
│ │
|
|
170
|
+
│ ┌──────────────────┐ ┌─────────────────────────────┐ │
|
|
171
|
+
│ │ Relay Server │ │ Shell Orchestrator │ │
|
|
172
|
+
│ │ (bun + express) │ │ (orchestrator.sh) │ │
|
|
173
|
+
│ │ │ │ │ │
|
|
174
|
+
│ │ - Discord bot │ │ Discovers channels via API │ │
|
|
175
|
+
│ │ - HTTP API │ │ Spawns 1 Claude agent per │ │
|
|
176
|
+
│ │ - SQLite store │ │ channel, monitors health, │ │
|
|
177
|
+
│ │ - Typing mgr │ │ restarts dead/stuck agents │ │
|
|
178
|
+
│ │ - Trace threads │ │ │ │
|
|
179
|
+
│ └────────┬─────────┘ │ ┌────────┐ ┌────────┐ │ │
|
|
180
|
+
│ │ │ │Agent #1│ │Agent #2│ ... │ │
|
|
181
|
+
│ │ │ └────────┘ └────────┘ │ │
|
|
182
|
+
│ │ └─────────────────────────────┘ │
|
|
183
|
+
└───────────┼──────────────────────────────────────────────┘
|
|
184
|
+
│
|
|
185
|
+
Discord API
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Message flow:**
|
|
189
|
+
|
|
190
|
+
1. A Discord user sends a message
|
|
191
|
+
2. The relay server stores it in SQLite and starts a typing indicator
|
|
192
|
+
3. The channel's Claude agent picks up the message via the `check-discord-messages` hook
|
|
193
|
+
4. Claude processes the message and calls `send-discord` to reply
|
|
194
|
+
5. The `steer-send` hook intercepts the send if new messages arrived while Claude was composing, forcing a revised reply
|
|
195
|
+
6. The relay posts the reply to Discord and stops the typing indicator
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Features
|
|
200
|
+
|
|
201
|
+
### Typing indicators
|
|
202
|
+
|
|
203
|
+
When a user sends a message, the relay starts a typing indicator that repeats every `TYPING_INTERVAL_MS` (default 8s). It stops automatically when Claude replies. After `TYPING_MAX_MS` (default 120s), a configurable fallback patience message is sent.
|
|
204
|
+
|
|
205
|
+
### Busy notifications
|
|
206
|
+
|
|
207
|
+
If Claude is already processing a task when a new message arrives, a notification is sent to let the user know their message is queued. Controlled by `BUSY_NOTIFY_ON_QUEUE`, `BUSY_NOTIFY_COOLDOWN_MS`, and `BUSY_NOTIFY_MIN_DURATION_MS`.
|
|
208
|
+
|
|
209
|
+
### Send steering
|
|
210
|
+
|
|
211
|
+
The `steer-send` hook intercepts outgoing `send-discord` calls and checks for unread messages. If new messages arrived while Claude was composing a reply, the send is blocked and Claude is forced to revise its reply to address all messages. This prevents Claude from sending stale responses.
|
|
212
|
+
|
|
213
|
+
### Live trace threads
|
|
214
|
+
|
|
215
|
+
When enabled, the relay creates a thread in each channel (default name: "⚙️ Live Trace") and streams live agent activity — tool calls, status changes, and more. Users can watch the agent work in real time without cluttering the main channel. Controlled by `TRACE_THREAD_ENABLED`, `TRACE_THREAD_NAME`, and `TRACE_FLUSH_INTERVAL_MS`.
|
|
216
|
+
|
|
217
|
+
### Attachments
|
|
218
|
+
|
|
219
|
+
Discord attachments are downloaded to a temp directory and delivered to Claude as file paths. The `cleanup-attachment` hook automatically deletes them after Claude reads them. Size limits and TTL are configurable via `MAX_ATTACHMENT_INLINE_BYTES`, `MAX_ATTACHMENT_DOWNLOAD_BYTES`, and `ATTACHMENT_TTL_MS`.
|
|
220
|
+
|
|
221
|
+
### /model slash command
|
|
222
|
+
|
|
223
|
+
Use `/model` in any channel to get or set the Claude model for that channel:
|
|
224
|
+
|
|
225
|
+
- `/model` — show the current model
|
|
226
|
+
- `/model name:claude-opus-4-6` — set the model
|
|
227
|
+
- `/model name:clear` — reset to default
|
|
228
|
+
|
|
229
|
+
### Memory system
|
|
230
|
+
|
|
231
|
+
A pluggable memory system backed by SQLite provides cross-session context. When a message arrives, the `check-discord-messages` hook retrieves relevant prior turns (avoiding duplicates from the current session) and includes them as memory context.
|
|
232
|
+
|
|
233
|
+
### Stuck agent detection
|
|
234
|
+
|
|
235
|
+
The orchestrator periodically checks each agent's health. An agent is considered stuck when all three conditions are met:
|
|
236
|
+
|
|
237
|
+
1. Heartbeat is stale (older than `STUCK_AGENT_THRESHOLD`, default 15 min)
|
|
238
|
+
2. Unread messages are waiting
|
|
239
|
+
3. Log file is also stale (no recent output)
|
|
240
|
+
|
|
241
|
+
Stuck agents are killed and automatically restarted.
|
|
242
|
+
|
|
243
|
+
### Bash safety guard
|
|
244
|
+
|
|
245
|
+
The `safe-bash` hook inspects Bash tool calls for risky background execution patterns (`run_in_background=true`, standalone `&` operator). Depending on `BASH_POLICY_MODE`, these are either blocked or allowed with a Discord notification. Fine-grained control via `ALLOW_BASH_RUN_IN_BACKGROUND` and `ALLOW_BASH_BACKGROUND_OPS`.
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Interactive mode
|
|
250
|
+
|
|
251
|
+
To run Claude interactively (with a terminal UI) instead of in autonomous headless mode:
|
|
252
|
+
|
|
253
|
+
```bash
|
|
254
|
+
# Start the relay in one terminal
|
|
255
|
+
bun run start:relay
|
|
256
|
+
|
|
257
|
+
# Start the interactive orchestrator in another terminal
|
|
258
|
+
bun run start:orchestrator-interactive
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
In interactive mode, the orchestrator runs as a Claude Code session with a visible terminal. The relay must be started separately since there is no master process managing both.
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Development
|
|
266
|
+
|
|
267
|
+
### Scripts
|
|
268
|
+
|
|
269
|
+
| Script | Description |
|
|
270
|
+
|---|---|
|
|
271
|
+
| `bun start` | Start relay + orchestrator (production) |
|
|
272
|
+
| `bun run start:relay` | Start relay server only |
|
|
273
|
+
| `bun run start:orchestrator` | Start headless orchestrator only |
|
|
274
|
+
| `bun run start:orchestrator-interactive` | Start interactive orchestrator (terminal UI) |
|
|
275
|
+
| `bun run dev` | Alias for `start:relay` |
|
|
276
|
+
| `bun run generate-settings` | Generate `.claude/settings.json` with absolute hook paths |
|
|
277
|
+
| `bun run memory:smoke` | Run memory system smoke test |
|
|
278
|
+
| `bun run memory:inspect` | Inspect memory database contents |
|
|
279
|
+
| `bun run memory:migrate` | Migrate memory to channel-scoped keys |
|
|
280
|
+
| `bun run lint` | Run Biome linter |
|
|
281
|
+
| `bun run lint:fix` | Run Biome linter with auto-fix |
|
|
282
|
+
| `bun run format` | Format code with Biome |
|
|
283
|
+
| `bun run typecheck` | Run TypeScript type checking |
|
|
284
|
+
|
|
285
|
+
### Hook system
|
|
286
|
+
|
|
287
|
+
Claude Code hooks are configured in `.claude/settings.json` (generated from `.claude/settings.template.json` by `bun run generate-settings`). The template uses `__ORCHESTRATOR_DIR__` placeholders that are replaced with absolute paths at generation time.
|
|
288
|
+
|
|
289
|
+
| Hook | Event | Description |
|
|
290
|
+
|---|---|---|
|
|
291
|
+
| `check-discord-messages` | PostToolUse, SessionStart, UserPromptSubmit, Stop | Delivers unread Discord messages + memory context into Claude's context |
|
|
292
|
+
| `steer-send` | PreToolUse (Bash) | Blocks `send-discord` if new messages arrived, forcing a revised reply |
|
|
293
|
+
| `safe-bash` | PreToolUse (Bash) | Guards against risky background execution patterns |
|
|
294
|
+
| `track-activity` | PreToolUse, PostToolUse, Stop, SessionStart | Tracks agent busy/idle status and writes trace events |
|
|
295
|
+
| `cleanup-attachment` | PostToolUse (Read) | Deletes downloaded attachment files after Claude reads them |
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
## Logs
|
|
300
|
+
|
|
301
|
+
All logs are written to `CC_DISCORD_LOG_DIR` (default: `/tmp/cc-discord/logs`):
|
|
302
|
+
|
|
303
|
+
| File | Contents |
|
|
304
|
+
|---|---|
|
|
305
|
+
| `relay.log` | Relay server output |
|
|
306
|
+
| `orchestrator.log` | Orchestrator process management |
|
|
307
|
+
| `channel-<name>-<id>.log` | Per-channel Claude agent output |
|
|
308
|
+
|
|
309
|
+
Monitor all logs:
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
tail -f /tmp/cc-discord/logs/*.log
|
|
313
|
+
```
|