-
Notifications
You must be signed in to change notification settings - Fork 2
feat: replace OpenClaw with Claude Agent SDK triage system #68
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
0f44fbb
f92a2e8
ca7bce8
b18342e
0f46bc3
0dfc4e6
97ee256
1f7bcbb
6c19942
3b74210
252c80a
cbff8e0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,6 +28,14 @@ COPY --chown=botuser:botgroup src/ ./src/ | |
| # Create data directory for state persistence | ||
| RUN mkdir -p data && chown botuser:botgroup data | ||
|
|
||
| # Pre-seed Claude Code config with cached GrowthBook feature flags so the CLI | ||
| # does not attempt a slow/hanging network fetch on first invocation inside Docker. | ||
| # The userID and firstStartTime are placeholders; the CLI updates them at runtime. | ||
| RUN mkdir -p /home/botuser/.claude && \ | ||
| printf '{\n "cachedGrowthBookFeatures": {\n "tengu_mcp_tool_search": false,\n "tengu_scratch": false,\n "tengu_disable_bypass_permissions_mode": false,\n "tengu_1p_event_batch_config": {"scheduledDelayMillis": 5000, "maxExportBatchSize": 200, "maxQueueSize": 8192},\n "tengu_claudeai_mcp_connectors": true,\n "tengu_event_sampling_config": {},\n "tengu_log_segment_events": false,\n "tengu_log_datadog_events": true,\n "tengu_marble_anvil": true,\n "tengu_tool_pear": false,\n "tengu_scarf_coffee": false,\n "tengu_keybinding_customization_release": true,\n "tengu_penguins_enabled": true,\n "tengu_thinkback": false,\n "tengu_oboe": true,\n "tengu_chomp_inflection": true,\n "tengu_copper_lantern": false,\n "tengu_marble_lantern_disabled": false,\n "tengu_vinteuil_phrase": true,\n "tengu_system_prompt_global_cache": false,\n "enhanced_telemetry_beta": false,\n "tengu_cache_plum_violet": false,\n "tengu_streaming_tool_execution2": true,\n "tengu_tool_search_unsupported_models": ["haiku"],\n "tengu_plan_mode_interview_phase": false,\n "tengu_fgts": false,\n "tengu_attribution_header": false,\n "tengu_prompt_cache_1h_config": {"allowlist": ["repl_main_thread*", "sdk"]},\n "tengu_tst_names_in_messages": false,\n "tengu_mulberry_fog": false,\n "tengu_coral_fern": false,\n "tengu_bergotte_lantern": false,\n "tengu_moth_copse": false\n },\n "opusProMigrationComplete": true,\n "sonnet1m45MigrationComplete": true,\n "cachedExtraUsageDisabledReason": null\n}\n' > /home/botuser/.claude.json && \ | ||
| chown -R botuser:botgroup /home/botuser/.claude /home/botuser/.claude.json && \ | ||
| chmod 600 /home/botuser/.claude.json | ||
|
Comment on lines
+31
to
+37
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hardcoded internal GrowthBook feature flags will drift with CLI updates. This JSON payload contains ~30 internal Consider documenting a process to regenerate this file (e.g., "run 🤖 Prompt for AI Agents |
||
|
|
||
| USER botuser | ||
|
|
||
| CMD ["node", "src/index.js"] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,12 +4,12 @@ | |
| [](LICENSE) | ||
| [](https://nodejs.org) | ||
|
|
||
| AI-powered Discord bot for the [Volvox](https://volvox.dev) developer community. Built with discord.js v14 and powered by Claude via [OpenClaw](https://openclaw.com). | ||
| AI-powered Discord bot for the [Volvox](https://volvox.dev) developer community. Built with discord.js v14 and powered by Claude via the Claude CLI in headless mode. | ||
|
|
||
| ## ✨ Features | ||
|
|
||
| - **🧠 AI Chat** — Mention the bot to chat with Claude. Maintains per-channel conversation history with intelligent context management. | ||
| - **🎯 Chime-In** — Bot can organically join conversations when it has something relevant to add (configurable per-channel). | ||
| - **🎯 Smart Triage** — Two-step evaluation (fast classifier + responder) that drives chime-ins and community rule enforcement. | ||
| - **👋 Dynamic Welcome Messages** — Contextual onboarding with time-of-day greetings, community activity snapshots, member milestones, and highlight channels. | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| - **🛡️ Spam Detection** — Pattern-based scam/spam detection with mod alerts and optional auto-delete. | ||
| - **⚔️ Moderation Suite** — Full-featured mod toolkit: warn, kick, ban, tempban, softban, timeout, purge, lock/unlock, slowmode. Includes case management, mod log routing, DM notifications, auto-escalation, and tempban scheduling. | ||
|
|
@@ -25,8 +25,8 @@ Discord User | |
| │ | ||
| ▼ | ||
| ┌─────────────┐ ┌──────────────┐ ┌─────────┐ | ||
| │ Bill Bot │────▶│ OpenClaw │────▶│ Claude │ | ||
| │ (Node.js) │◀────│ Gateway │◀────│ (AI) │ | ||
| │ Bill Bot │────▶│ Claude CLI │────▶│ Claude │ | ||
| │ (Node.js) │◀────│ (headless) │◀────│ (AI) │ | ||
| └──────┬──────┘ └──────────────┘ └─────────┘ | ||
| │ | ||
| ▼ | ||
|
|
@@ -40,7 +40,7 @@ Discord User | |
| - [Node.js](https://nodejs.org) 22+ | ||
| - [pnpm](https://pnpm.io) (`npm install -g pnpm`) | ||
| - [PostgreSQL](https://www.postgresql.org/) database | ||
| - [OpenClaw](https://openclaw.com) gateway (for AI chat features) | ||
| - An [Anthropic API key](https://console.anthropic.com) (for AI chat features) | ||
| - A [Discord application](https://discord.com/developers/applications) with bot token | ||
|
|
||
| ## 🚀 Setup | ||
|
|
@@ -96,8 +96,8 @@ pnpm dev | |
| | `DISCORD_TOKEN` | ✅ | Discord bot token | | ||
| | `DISCORD_CLIENT_ID` | ✅* | Discord application/client ID for slash-command deployment (`pnpm deploy`) | | ||
| | `GUILD_ID` | ❌ | Guild ID for faster dev command deployment (omit for global) | | ||
| | `OPENCLAW_API_URL` | ✅ | OpenClaw chat completions endpoint | | ||
| | `OPENCLAW_API_KEY` | ✅ | OpenClaw gateway authentication token | | ||
| | `ANTHROPIC_API_KEY` | ✅ | Anthropic API key for Claude AI | | ||
| | `CLAUDE_CODE_OAUTH_TOKEN` | ❌ | Required when using OAuth access tokens (`sk-ant-oat01-*`). Leave `ANTHROPIC_API_KEY` blank when using this. | | ||
| | `DATABASE_URL` | ✅** | PostgreSQL connection string for persistent config/state | | ||
| | `MEM0_API_KEY` | ❌ | Mem0 API key for long-term memory | | ||
| | `BOT_API_SECRET` | ✅*** | Shared secret for web dashboard API authentication | | ||
|
|
@@ -107,7 +107,6 @@ pnpm dev | |
| \** Bot can run without DB, but persistent config is strongly recommended in production. | ||
| \*** Required when running with the web dashboard. Can be omitted for bot-only deployments. | ||
|
|
||
| Legacy OpenClaw aliases are also supported for backwards compatibility: `OPENCLAW_URL`, `OPENCLAW_TOKEN`. | ||
|
|
||
| ### Web Dashboard | ||
|
|
||
|
|
@@ -130,20 +129,41 @@ All configuration lives in `config.json` and can be updated at runtime via the ` | |
| | Key | Type | Description | | ||
| |-----|------|-------------| | ||
| | `enabled` | boolean | Enable/disable AI responses | | ||
| | `model` | string | Claude model to use (e.g. `claude-sonnet-4-20250514`) | | ||
| | `maxTokens` | number | Max tokens per AI response | | ||
| | `systemPrompt` | string | System prompt defining bot personality | | ||
| | `channels` | string[] | Channel IDs to respond in (empty = all channels) | | ||
| | `historyLength` | number | Max conversation history entries per channel (default: 20) | | ||
| | `historyTTLDays` | number | Days before old history is cleaned up (default: 30) | | ||
| | `threadMode.enabled` | boolean | Enable threaded responses (default: false) | | ||
| | `threadMode.autoArchiveMinutes` | number | Thread auto-archive timeout (default: 60) | | ||
| | `threadMode.reuseWindowMinutes` | number | Window for reusing existing threads (default: 30) | | ||
|
|
||
| ### Chime-In (`chimeIn`) | ||
| ### Triage (`triage`) | ||
|
|
||
| | Key | Type | Description | | ||
| |-----|------|-------------| | ||
| | `enabled` | boolean | Enable organic conversation joining | | ||
| | `evaluateEvery` | number | Evaluate every N messages | | ||
| | `model` | string | Model for evaluation (e.g. `claude-haiku-4-5`) | | ||
| | `enabled` | boolean | Enable triage-based message evaluation | | ||
| | `defaultInterval` | number | Base evaluation interval in ms (default: 5000) | | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The README says "default: 5000" but 🤖 Prompt for AI Agents |
||
| | `maxBufferSize` | number | Max messages per channel buffer (default: 30) | | ||
| | `triggerWords` | string[] | Words that force instant evaluation (default: `["volvox"]`) | | ||
| | `moderationKeywords` | string[] | Words that flag for moderation | | ||
| | `classifyModel` | string | Model for classification step (default: `claude-haiku-4-5`) | | ||
| | `respondModel` | string | Model for response step (default: `claude-sonnet-4-6`) | | ||
| | `classifyBudget` | number | Max USD per classify call (default: 0.05) | | ||
| | `respondBudget` | number | Max USD per respond call (default: 0.20) | | ||
| | `thinkingTokens` | number | Thinking token budget for responder (default: 4096) | | ||
| | `contextMessages` | number | Channel history messages fetched for context (default: 10) | | ||
| | `streaming` | boolean | Enable streaming responses (default: false) | | ||
| | `tokenRecycleLimit` | number | Token threshold before recycling CLI process (default: 20000) | | ||
| | `timeout` | number | Evaluation timeout in ms (default: 30000) | | ||
| | `classifyBaseUrl` | string | Custom API base URL for classifier (default: null) | | ||
| | `respondBaseUrl` | string | Custom API base URL for responder (default: null) | | ||
| | `classifyApiKey` | string | Custom API key for classifier (default: null) | | ||
| | `respondApiKey` | string | Custom API key for responder (default: null) | | ||
| | `moderationResponse` | boolean | Send moderation nudge messages (default: true) | | ||
| | `channels` | string[] | Channels to monitor (empty = all) | | ||
| | `excludeChannels` | string[] | Channels to never chime into | | ||
| | `excludeChannels` | string[] | Channels to never triage | | ||
| | `debugFooter` | boolean | Show debug stats footer on AI responses (default: false) | | ||
| | `debugFooterLevel` | string | Footer density: `"verbose"`, `"compact"`, or `"split"` (default: `"verbose"`) | | ||
|
|
||
| ### Welcome Messages (`welcome`) | ||
|
|
||
|
|
@@ -351,8 +371,8 @@ Set these in the Railway dashboard for the Bot service: | |
| | `DISCORD_TOKEN` | Yes | Discord bot token | | ||
| | `DISCORD_CLIENT_ID` | Yes | Discord application/client ID | | ||
| | `GUILD_ID` | No | Guild ID for faster dev command deployment (omit for global) | | ||
| | `OPENCLAW_API_URL` | Yes | OpenClaw chat completions endpoint | | ||
| | `OPENCLAW_API_KEY` | Yes | OpenClaw gateway authentication token | | ||
| | `ANTHROPIC_API_KEY` | Yes | Anthropic API key for Claude AI | | ||
| | `CLAUDE_CODE_OAUTH_TOKEN` | No | Required when using OAuth access tokens (`sk-ant-oat01-*`). Leave `ANTHROPIC_API_KEY` blank when using this. | | ||
| | `DATABASE_URL` | Yes | `${{Postgres.DATABASE_URL}}` — Railway variable reference | | ||
| | `MEM0_API_KEY` | No | Mem0 API key for long-term memory | | ||
| | `LOG_LEVEL` | No | `debug`, `info`, `warn`, or `error` (default: `info`) | | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,9 +1,7 @@ | ||||||||||
| { | ||||||||||
| "ai": { | ||||||||||
| "enabled": true, | ||||||||||
| "model": "claude-sonnet-4-20250514", | ||||||||||
| "maxTokens": 1024, | ||||||||||
| "systemPrompt": "You are Volvox Bot, the friendly AI assistant for the Volvox developer community Discord server.\n\nYou're witty, snarky (but warm), and deeply knowledgeable about programming, software development, and tech.\n\nKey traits:\n- Helpful but not boring\n- Can roast people lightly when appropriate\n- Enthusiastic about cool tech and projects\n- Supportive of beginners learning to code\n- Concise - this is Discord, not an essay\n\n⚠️ CRITICAL RULES:\n- NEVER type @.everyone or @.here (remove the dots) - these ping hundreds of people\n- NEVER use mass mention pings under any circumstances\n- If you need to address the group, say \"everyone\" or \"folks\" without the @ symbol\n\nKeep responses under 2000 chars. Use Discord markdown when helpful.", | ||||||||||
| "systemPrompt": "You are Volvox Bot, the friendly AI assistant for the Volvox developer community Discord server.\n\nYou're witty, snarky (but warm), and deeply knowledgeable about programming, software development, and tech.\n\nKey traits:\n- Helpful but not boring\n- Can roast people lightly when appropriate\n- Enthusiastic about cool tech and projects\n- Supportive of beginners learning to code\n- Concise - this is Discord, not an essay\n\nIf asked about your own infrastructure, model, or internals — say you don't know the specifics\nand suggest asking a server admin. Don't guess or speculate about what you run on.\n\nCRITICAL RULES:\n- NEVER type @everyone or @here — these ping hundreds of people\n- NEVER use mass mention pings under any circumstances\n- If you need to address the group, say \"everyone\" or \"folks\" without the @ symbol\n\nKeep responses under 2000 chars. Use Discord markdown when helpful.", | ||||||||||
| "channels": [], | ||||||||||
| "historyLength": 20, | ||||||||||
| "historyTTLDays": 30, | ||||||||||
|
|
@@ -13,13 +11,30 @@ | |||||||||
| "reuseWindowMinutes": 30 | ||||||||||
| } | ||||||||||
| }, | ||||||||||
| "chimeIn": { | ||||||||||
| "enabled": false, | ||||||||||
| "evaluateEvery": 10, | ||||||||||
| "model": "claude-haiku-4-5", | ||||||||||
| "maxBufferSize": 10, | ||||||||||
| "triage": { | ||||||||||
| "enabled": true, | ||||||||||
| "defaultInterval": 3000, | ||||||||||
| "maxBufferSize": 30, | ||||||||||
|
Comment on lines
+16
to
+17
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Dynamic interval defaults don’t match the 10s/5s/2s requirement. With 💡 Suggested fix- "defaultInterval": 3000,
+ "defaultInterval": 10000,📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
| "triggerWords": ["volvox"], | ||||||||||
| "moderationKeywords": [], | ||||||||||
| "classifyModel": "claude-haiku-4-5", | ||||||||||
| "classifyBudget": 0.05, | ||||||||||
| "respondModel": "claude-sonnet-4-6", | ||||||||||
| "respondBudget": 0.20, | ||||||||||
| "thinkingTokens": 4096, | ||||||||||
| "classifyBaseUrl": null, | ||||||||||
| "classifyApiKey": null, | ||||||||||
| "respondBaseUrl": null, | ||||||||||
| "respondApiKey": null, | ||||||||||
| "streaming": false, | ||||||||||
| "tokenRecycleLimit": 20000, | ||||||||||
| "contextMessages": 10, | ||||||||||
| "timeout": 30000, | ||||||||||
| "moderationResponse": true, | ||||||||||
| "channels": [], | ||||||||||
| "excludeChannels": [] | ||||||||||
| "excludeChannels": [], | ||||||||||
| "debugFooter": true, | ||||||||||
| "debugFooterLevel": "verbose" | ||||||||||
|
Comment on lines
+36
to
+37
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The default config enables debug footers with verbose level, which shows triage/response model names, token counts, and cost data to end users. This should default to 🔧 Suggested fix- "debugFooter": true,
- "debugFooterLevel": "verbose"
+ "debugFooter": false,
+ "debugFooterLevel": "compact"📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
| }, | ||||||||||
| "welcome": { | ||||||||||
| "enabled": true, | ||||||||||
|
|
||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -50,5 +50,20 @@ services: | |
| profiles: | ||
| - full | ||
|
|
||
| router: | ||
| build: | ||
| context: ./router | ||
| dockerfile: Dockerfile | ||
| restart: unless-stopped | ||
| environment: | ||
| - OPENROUTER_API_KEY=${OPENROUTER_API_KEY} | ||
| healthcheck: | ||
| test: ["CMD-SHELL", "wget -q --spider http://127.0.0.1:3456 || exit 1"] | ||
| interval: 5s | ||
| timeout: 3s | ||
| retries: 5 | ||
| profiles: | ||
| - router | ||
|
Comment on lines
+53
to
+66
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Verify router service network accessibility. The
If the router is only used externally (e.g., for development tooling), the current setup is fine. Also, ensure 🤖 Prompt for AI Agents |
||
|
|
||
| volumes: | ||
| pgdata: | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| FROM node:22-alpine | ||
| RUN npm install -g @musistudio/claude-code-router | ||
| COPY config.json /root/.claude-code-router/config.json | ||
| EXPOSE 3456 | ||
| CMD ["ccr", "start"] | ||
|
Comment on lines
+1
to
+5
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: The latest published version of the npm package 🏁 Script executed: # Check for docker-compose.yml and any existing configuration
fd -e "docker-compose" -e "docker-compose.yml" -e ".dockerignore"
# Look for any package version references or pinning in the codebase
fd -e "package.json" | head -20Repository: BillChirico/bills-bot Length of output: 47 🏁 Script executed: # Get repository overview
git ls-files | head -30
# Look for Dockerfile and docker-compose in the repo
find . -name "Dockerfile" -o -name "docker-compose.yml" 2>/dev/null
# Check directory structure
ls -laRepository: BillChirico/bills-bot Length of output: 2397 🏁 Script executed: # Read the docker-compose.yml to understand how router service is configured
cat -n docker-compose.yml
# Check router/config.json
cat -n router/config.json
# Check package.json for any package version references
cat -n package.jsonRepository: BillChirico/bills-bot Length of output: 4148 Run as a non-root user and pin the package version. The container runs a network-facing service as Pin the version to 🧰 Tools🪛 Checkov (3.2.334)[low] 1-5: Ensure that HEALTHCHECK instructions have been added to container images (CKV_DOCKER_2) [low] 1-5: Ensure that a user for the container has been created (CKV_DOCKER_3) 🪛 Hadolint (2.14.0)[warning] 2-2: Pin versions in npm. Instead of (DL3016) 🪛 Trivy (0.69.1)[info] 1-1: No HEALTHCHECK defined Add HEALTHCHECK instruction in your Dockerfile Rule: DS-0026 (IaC/Dockerfile) [error] 1-1: Image user should not be 'root' Specify at least 1 USER command in Dockerfile with non-root user as argument Rule: DS-0002 (IaC/Dockerfile) [info] 1-1: No HEALTHCHECK defined Add HEALTHCHECK instruction in your Dockerfile Rule: DS-0026 (IaC/Dockerfile) 🤖 Prompt for AI Agents |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
🧩 Analysis chain
🏁 Script executed:
find src -name "triage.js" -type fRepository: BillChirico/bills-bot
Length of output: 86
🏁 Script executed:
rg -n --type=js '100|30.*min|evict|MAX_CHANNEL|EVICTION' src/modules/triage.jsRepository: BillChirico/bills-bot
Length of output: 631
🏁 Script executed:
rg -n --type=js 'channels?.*100|30.*minut|inactive|evict' src/Repository: BillChirico/bills-bot
Length of output: 1848
🏁 Script executed:
sed -n '110,125p' src/modules/triage.jsRepository: BillChirico/bills-bot
Length of output: 503
🏁 Script executed:
sed -n '185,210p' src/modules/triage.jsRepository: BillChirico/bills-bot
Length of output: 1011
🏁 Script executed:
Repository: BillChirico/bills-bot
Length of output: 303
Consider making the 100-channel cap and 30-minute eviction timeout configurable.
These values are hardcoded as
MAX_TRACKED_CHANNELSandCHANNEL_INACTIVE_MSat lines 115–116 insrc/modules/triage.js. While they have inline comments, they bypass the config system used elsewhere in the module (e.g.,startTriage(),accumulateMessage(),evaluateNow()). Exposing them as config parameters would make future tuning easier without code changes.🤖 Prompt for AI Agents