@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.
Files changed (46) hide show
  1. package/.claude/settings.template.json +94 -0
  2. package/.env.example +41 -0
  3. package/.env.relay.example +46 -0
  4. package/.env.worker.example +40 -0
  5. package/README.md +313 -0
  6. package/hooks/check-discord-messages.ts +204 -0
  7. package/hooks/cleanup-attachment.ts +47 -0
  8. package/hooks/safe-bash.ts +157 -0
  9. package/hooks/steer-send.ts +108 -0
  10. package/hooks/track-activity.ts +220 -0
  11. package/memory/README.md +60 -0
  12. package/memory/core/MemoryCoordinator.ts +703 -0
  13. package/memory/core/MemoryStore.ts +72 -0
  14. package/memory/core/session-key.ts +14 -0
  15. package/memory/core/types.ts +59 -0
  16. package/memory/index.ts +19 -0
  17. package/memory/providers/sqlite/SqliteMemoryStore.ts +838 -0
  18. package/memory/providers/sqlite/index.ts +1 -0
  19. package/package.json +45 -0
  20. package/prompts/autoreply-system.md +32 -0
  21. package/prompts/channel-system.md +22 -0
  22. package/prompts/orchestrator-system.md +56 -0
  23. package/scripts/channel-agent.sh +159 -0
  24. package/scripts/generate-settings.sh +17 -0
  25. package/scripts/load-env.sh +79 -0
  26. package/scripts/migrate-memory-to-channel-keys.ts +148 -0
  27. package/scripts/orchestrator.sh +325 -0
  28. package/scripts/parse-claude-stream.ts +349 -0
  29. package/scripts/start-orchestrator.sh +82 -0
  30. package/scripts/start-relay.sh +17 -0
  31. package/scripts/start.sh +175 -0
  32. package/server/attachment.ts +182 -0
  33. package/server/busy-notify.ts +69 -0
  34. package/server/config.ts +121 -0
  35. package/server/db.ts +249 -0
  36. package/server/index.ts +311 -0
  37. package/server/memory.ts +88 -0
  38. package/server/messages.ts +111 -0
  39. package/server/trace-thread.ts +340 -0
  40. package/server/typing.ts +101 -0
  41. package/tools/memory-inspect.ts +94 -0
  42. package/tools/memory-smoke.ts +173 -0
  43. package/tools/send-discord +2 -0
  44. package/tools/send-discord.ts +82 -0
  45. package/tools/wait-for-discord-messages +2 -0
  46. 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
+ ```