feat: add Discord bridge for NemoClaw agent chat - #68
Conversation
|
Thanks for adding the Discord bridge support, this will provide another channel for users to interact with their NemoClaw agents and expand the ways they can use our platform. |
|
Hey @BioInfo — a Discord bridge for agent chat is a cool idea! Wanted to let you know that main has been a moving target lately: we've added CI automation, landed new features, and done some restructuring. Before we dive into reviewing this, it would really help if you could rebase onto the latest main. That way we can see how it fits with everything that's landed since. Looking forward to checking it out! |
91db0fd to
29cf0ac
Compare
📝 WalkthroughWalkthroughAdds a Discord bridge: new bot script and launcher, updates service orchestration to run the bridge, documentation for setup, and a Docker ignore negation to include Changes
Sequence DiagramsequenceDiagram
actor User as Discord User
participant Bot as Discord Bot
participant SSH as SSH / nemoClaw host
participant Agent as OpenClaw Agent
User->>Bot: Send message (DM, mention, or channel)
Bot->>Bot: Apply guild/channel/user filters
alt Accepted
Bot->>Bot: Acquire per-user lock
Bot->>Bot: Create sessionId (discord-<userId>)
Bot->>SSH: Spawn SSH running "nemoclaw-start openclaw agent ..." with message & sessionId
Bot->>Bot: Start typing indicator
SSH->>Agent: Execute agent in sandbox
Agent-->>SSH: Return stdout/stderr
SSH-->>Bot: Return combined output (filtered)
alt Success
Bot->>Bot: Split output into ≤1950-char chunks
Bot->>User: Reply with first chunk
Bot->>User: Send remaining chunks
else Error
Bot->>User: Reply with error/truncated stderr
end
Bot->>Bot: Stop typing indicator and release lock
else Rejected
Bot->>Bot: Ignore message
end
Estimated Code Review Effort🎯 3 (Moderate) | ⏱️ ~30 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (3)
scripts/discord-bridge.js (1)
20-20: Unused import.
execSyncis imported but never used in this file.Proposed fix
-const { execSync, spawn } = require("child_process"); +const { spawn } = require("child_process");🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/discord-bridge.js` at line 20, Remove the unused import execSync from the require call at the top of scripts/discord-bridge.js; update the destructuring to only import spawn (i.e., change const { execSync, spawn } = require("child_process") to only include spawn) so there is no unused symbol left in the module.docs/deployment/set-up-discord-bridge.md (2)
72-72: Passive voice detected."The Discord bridge starts only when..." uses passive construction. Rewrite in active voice.
Proposed fix
-The Discord bridge starts only when the `DISCORD_BOT_TOKEN` environment variable is set. +The `start` command launches the Discord bridge only when you set the `DISCORD_BOT_TOKEN` environment variable.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/deployment/set-up-discord-bridge.md` at line 72, Rewrite the passive sentence to active voice: replace "The Discord bridge starts only when the `DISCORD_BOT_TOKEN` environment variable is set." with an active construction that names the actor (e.g., "The application starts the Discord bridge only when you set the `DISCORD_BOT_TOKEN` environment variable" or "We start the Discord bridge only when the `DISCORD_BOT_TOKEN` environment variable is set"). Ensure the environment variable name `DISCORD_BOT_TOKEN` remains unchanged.
87-88: Multiple sentences on one line.This line contains two sentences. Per formatting rules, use one sentence per line to make diffs readable.
Proposed fix
-You can either mention the bot directly in a channel or send a direct message. -The bridge forwards the message to the OpenClaw agent inside the sandbox and returns the agent response. +You can either mention the bot directly in a channel or send a direct message. +The bridge forwards the message to the OpenClaw agent inside the sandbox. +The agent response is returned to the channel.Note: Also rewrote to active voice ("The agent response is returned" → split into active statements).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/deployment/set-up-discord-bridge.md` around lines 87 - 88, Split the two-sentence line into separate lines so each sentence is on its own line and rewrite to active voice; replace the single line containing "You can either mention the bot directly in a channel or send a direct message. The bridge forwards the message to the OpenClaw agent inside the sandbox and returns the agent response." with three lines like: "You can either mention the bot directly in a channel or send a direct message." "The bridge forwards the message to the OpenClaw agent inside the sandbox." "The agent returns a response to the bridge." to satisfy one-sentence-per-line formatting and avoid passive phrasing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/deployment/set-up-discord-bridge.md`:
- Around line 118-121: Change the bottom section heading "Related Topics" to
"Next Steps" in this markdown file; locate the header line that currently reads
"## Related Topics" and replace it with "## Next Steps" while keeping the
existing bullet links (the deploy-to-remote-gpu.md and ../reference/commands.md
entries) unchanged so the navigation and links remain intact.
- Line 21: The H1 heading "Set Up the Discord Bridge" does not match the
frontmatter key title.page ("Set Up the NemoClaw Discord Bridge for Remote Agent
Chat"); update the H1 heading at the top of the document to exactly match the
title.page value so they are identical (ensure the line that begins with "#" is
replaced with "# Set Up the NemoClaw Discord Bridge for Remote Agent Chat").
In `@scripts/start-discord-bridge.sh`:
- Line 3: The current single-line export of DISCORD_BOT_TOKEN masks the exit
status of `pass show api-keys/nemoclaw-discord-token`; run the command first
into a temporary variable (e.g. token="$(pass show
api-keys/nemoclaw-discord-token 2>/dev/null | head -1)"), check the command's
exit status and/or whether token is empty, and only then export
DISCORD_BOT_TOKEN="$token" or abort with a clear error and non-zero exit;
reference the DISCORD_BOT_TOKEN export and the `pass show
api-keys/nemoclaw-discord-token` invocation when making the change.
- Line 11: The script start-discord-bridge.sh uses a hardcoded absolute path in
the exec call which breaks portability; change the exec invocation to compute
the discord-bridge.js path relative to the script's directory (use the script
dir resolution technique used in start-services.sh) and then exec node with that
constructed path instead of /home/bioinfo/...; update the exec line referencing
discord-bridge.js so it derives the script directory (e.g., via a DIR or
SCRIPT_DIR variable computed from $0) and joins it with
../scripts/discord-bridge.js before calling node.
In `@scripts/start-services.sh`:
- Around line 148-152: The Discord bridge start is missing the SANDBOX_NAME env
var so discord-bridge.js may not receive the same sandbox as Telegram; update
the start_service invocation for the discord-bridge (the start_service
discord-bridge block that runs node "$REPO_DIR/scripts/discord-bridge.js") to
pass SANDBOX_NAME="$SANDBOX_NAME" in the environment like the Telegram bridge
does, ensuring discord-bridge.js reads the intended sandbox value.
---
Nitpick comments:
In `@docs/deployment/set-up-discord-bridge.md`:
- Line 72: Rewrite the passive sentence to active voice: replace "The Discord
bridge starts only when the `DISCORD_BOT_TOKEN` environment variable is set."
with an active construction that names the actor (e.g., "The application starts
the Discord bridge only when you set the `DISCORD_BOT_TOKEN` environment
variable" or "We start the Discord bridge only when the `DISCORD_BOT_TOKEN`
environment variable is set"). Ensure the environment variable name
`DISCORD_BOT_TOKEN` remains unchanged.
- Around line 87-88: Split the two-sentence line into separate lines so each
sentence is on its own line and rewrite to active voice; replace the single line
containing "You can either mention the bot directly in a channel or send a
direct message. The bridge forwards the message to the OpenClaw agent inside the
sandbox and returns the agent response." with three lines like: "You can either
mention the bot directly in a channel or send a direct message." "The bridge
forwards the message to the OpenClaw agent inside the sandbox." "The agent
returns a response to the bridge." to satisfy one-sentence-per-line formatting
and avoid passive phrasing.
In `@scripts/discord-bridge.js`:
- Line 20: Remove the unused import execSync from the require call at the top of
scripts/discord-bridge.js; update the destructuring to only import spawn (i.e.,
change const { execSync, spawn } = require("child_process") to only include
spawn) so there is no unused symbol left in the module.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 9f5ed6cb-f458-4025-8eb1-8201867a0027
📒 Files selected for processing (5)
.dockerignoredocs/deployment/set-up-discord-bridge.mdscripts/discord-bridge.jsscripts/start-discord-bridge.shscripts/start-services.sh
29cf0ac to
01f3662
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (3)
docs/deployment/set-up-discord-bridge.md (3)
24-24: Use active voice in the intro sentence.“The Discord bridge is an auxiliary service managed by
nemoclaw start.” is passive. Rewrite this sentence in active voice.As per coding guidelines, "Active voice required. Flag passive constructions."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/deployment/set-up-discord-bridge.md` at line 24, Rewrite the passive intro sentence "The Discord bridge is an auxiliary service managed by `nemoclaw start`." into active voice by making the subject perform the action; for example, change it to something like "`nemoclaw start` manages the Discord bridge as an auxiliary service" or "`nemoclaw start` starts and manages the Discord bridge," and replace the original sentence in the document.
100-100: Use present tense instead of future tense.“The bot will only respond...” should be rewritten in present tense to match the docs voice rules.
As per coding guidelines, "Present tense. Flag future tense ('will') in descriptions of current behavior."
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/deployment/set-up-discord-bridge.md` at line 100, Change the sentence "The bot will only respond to messages in the specified channel." to present tense; replace "will only respond" with "only responds" so the line reads "The bot only responds to messages in the specified channel." Update the exact sentence in the docs where that text appears (search for the quoted phrase) to match the project's present-tense voice.
26-29: Add an introductory sentence under each H2 before lists.The sections Prerequisites, Add the Bot to Your Server, and Next Steps start directly with lists. Add one short introductory sentence under each heading before list items.
As per coding guidelines, "Sections use H2 and H3, each starting with an introductory sentence."
Also applies to: 40-49, 119-121
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/deployment/set-up-discord-bridge.md` around lines 26 - 29, Add a single short introductory sentence under each H2 heading that currently begins directly with a list (specifically "Prerequisites", "Add the Bot to Your Server", "Next Steps" and the other H2 blocks around lines 40-49 and 119-121) so each H2 is followed by one brief sentence summarizing the section before the bullet points; edit the Markdown around those headings to insert a one-line intro (e.g., "Before you begin, ensure the following:" style) so every H2 is immediately followed by an introductory sentence per the documentation guidelines.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@docs/deployment/set-up-discord-bridge.md`:
- Line 24: Rewrite the passive intro sentence "The Discord bridge is an
auxiliary service managed by `nemoclaw start`." into active voice by making the
subject perform the action; for example, change it to something like "`nemoclaw
start` manages the Discord bridge as an auxiliary service" or "`nemoclaw start`
starts and manages the Discord bridge," and replace the original sentence in the
document.
- Line 100: Change the sentence "The bot will only respond to messages in the
specified channel." to present tense; replace "will only respond" with "only
responds" so the line reads "The bot only responds to messages in the specified
channel." Update the exact sentence in the docs where that text appears (search
for the quoted phrase) to match the project's present-tense voice.
- Around line 26-29: Add a single short introductory sentence under each H2
heading that currently begins directly with a list (specifically
"Prerequisites", "Add the Bot to Your Server", "Next Steps" and the other H2
blocks around lines 40-49 and 119-121) so each H2 is followed by one brief
sentence summarizing the section before the bullet points; edit the Markdown
around those headings to insert a one-line intro (e.g., "Before you begin,
ensure the following:" style) so every H2 is immediately followed by an
introductory sentence per the documentation guidelines.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: a430c416-4dd7-404f-a46f-cc8df13d0d26
📒 Files selected for processing (5)
.dockerignoredocs/deployment/set-up-discord-bridge.mdscripts/discord-bridge.jsscripts/start-discord-bridge.shscripts/start-services.sh
✅ Files skipped from review due to trivial changes (2)
- .dockerignore
- scripts/discord-bridge.js
🚧 Files skipped from review as they are similar to previous changes (2)
- scripts/start-discord-bridge.sh
- scripts/start-services.sh
Add Discord bridge support to NemoClaw, mirroring the existing Telegram bridge functionality. Users can now interact with the OpenClaw agent through Discord messages. Changes: - Add scripts/discord-bridge.js: Discord bot that forwards messages to the OpenClaw agent inside the sandbox - Update scripts/start-services.sh: Add Discord bridge service management alongside Telegram bridge - Add docs/deployment/set-up-discord-bridge.md: Complete setup guide for Discord bridge deployment Features: - Forward Discord messages to OpenClaw agent - Support optional channel restrictions via DISCORD_CHANNEL_ID - Support optional user restrictions via ALLOWED_USER_IDS - Automatic message chunking for Discord 2000-char limit - Typing indicators during agent processing - Concurrent run protection per user Signed-off-by: Justin Johnson <jhjohnsn@gmail.com>
01f3662 to
0eb9794
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (3)
scripts/discord-bridge.js (3)
189-189: Consider handling login failures.
client.login()returns a Promise that rejects on authentication failure (invalid token, network issues). Currently, a failure results in an unhandled rejection.Proposed fix
-client.login(TOKEN); +client.login(TOKEN).catch((err) => { + console.error(`Failed to login: ${err.message}`); + process.exit(1); +});🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/discord-bridge.js` at line 189, client.login(TOKEN) can reject (invalid token/network) and currently causes an unhandled promise rejection; wrap the login in a promise handler or an async try/catch (e.g., await client.login(TOKEN) inside an async IIFE) and on failure log the error (include error details) and exit with a non-zero code (process.exit(1)); ensure you reference the same client.login and TOKEN symbols so the rejection is caught and handled gracefully.
136-139: Consider initial typing call placement.Line 139 sends an immediate typing indicator, but it's placed after the
setIntervalsetup. While functionally correct, consider moving the immediate call beforesetIntervalfor clearer intent, or remove the duplicate sincesetIntervalwith an immediate first call pattern isn't being used.Minor readability nit—no functional issue.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/discord-bridge.js` around lines 136 - 139, The immediate call to msg.channel.sendTyping() is placed after creating typingInterval which reduces clarity; either remove the duplicate call or move the immediate msg.channel.sendTyping() before const typingInterval = setInterval(...) so the pattern "send once immediately, then every 5s" is explicit; update the code around typingInterval and the two msg.channel.sendTyping() calls to implement one of these two options for clearer intent.
21-21: Unused import.The
pathmodule is imported but never used in this file.const { Client, GatewayIntentBits, Partials } = require("discord.js"); const { spawn } = require("child_process"); -const path = require("path");🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/discord-bridge.js` at line 21, The file imports the Node 'path' module via the const path = require("path") statement but never uses it; remove that unused import to clean up the module (or if path functionality is intended, replace/remove usages accordingly). Locate the require("path") binding (the const path variable) in scripts/discord-bridge.js and delete the line, or alternatively implement the intended path usage where referenced; ensure no other code refers to the removed symbol and run lint/tests to confirm.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@scripts/discord-bridge.js`:
- Line 189: client.login(TOKEN) can reject (invalid token/network) and currently
causes an unhandled promise rejection; wrap the login in a promise handler or an
async try/catch (e.g., await client.login(TOKEN) inside an async IIFE) and on
failure log the error (include error details) and exit with a non-zero code
(process.exit(1)); ensure you reference the same client.login and TOKEN symbols
so the rejection is caught and handled gracefully.
- Around line 136-139: The immediate call to msg.channel.sendTyping() is placed
after creating typingInterval which reduces clarity; either remove the duplicate
call or move the immediate msg.channel.sendTyping() before const typingInterval
= setInterval(...) so the pattern "send once immediately, then every 5s" is
explicit; update the code around typingInterval and the two
msg.channel.sendTyping() calls to implement one of these two options for clearer
intent.
- Line 21: The file imports the Node 'path' module via the const path =
require("path") statement but never uses it; remove that unused import to clean
up the module (or if path functionality is intended, replace/remove usages
accordingly). Locate the require("path") binding (the const path variable) in
scripts/discord-bridge.js and delete the line, or alternatively implement the
intended path usage where referenced; ensure no other code refers to the removed
symbol and run lint/tests to confirm.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: f7292aef-6d6b-4e39-8c52-694a40878073
📒 Files selected for processing (5)
.dockerignoredocs/deployment/set-up-discord-bridge.mdscripts/discord-bridge.jsscripts/start-discord-bridge.shscripts/start-services.sh
✅ Files skipped from review due to trivial changes (1)
- .dockerignore
🚧 Files skipped from review as they are similar to previous changes (1)
- scripts/start-discord-bridge.sh
|
Thanks for building this, @BioInfo — Discord support was a real gap and this PR was a solid first take. Discord integration is now handled differently on main: bot tokens are injected directly into the sandbox environment (#601) and network access is managed via the |
This pull request adds Discord bridge support to NemoClaw, enabling users to interact with the OpenClaw agent through Discord.
Changes
Features
Setup
See docs/deployment/set-up-discord-bridge.md for complete instructions.
Summary by CodeRabbit
New Features
Documentation
Chores