Conversation
|
Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually. Contributors can view more details about this message here. |
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR adds a local Discord facade server that intercepts and bridges Discord REST and Gateway traffic for Hermes. It includes the facade implementation in Python, network interception via aiohttp preload monkey-patching, sandbox policies, Docker build configuration, startup orchestration, recovery logic, and comprehensive E2E and unit tests. ChangesDiscord Facade & Network Bridging
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 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: 1
🧹 Nitpick comments (1)
test/e2e/test-hermes-discord-e2e.sh (1)
398-403: ⚡ Quick winAvoid keying this E2E on an exact Hermes log line.
This assertion couples CI to upstream log wording rather than behavior. Since the Hermes base image is still tracked via
:latest, a harmless message-text change would fail the test even if proxy routing still works. Prefer a looser match on stable substrings or rely on the existing.env/sandbox-env + reachability checks as the contract here.Suggested tweak
-gateway_proxy_log=$(sandbox_exec "grep -F 'Using proxy for Discord: http://127.0.0.1:3129' /tmp/gateway.log 2>/dev/null | tail -1 || true") +gateway_proxy_log=$(sandbox_exec "grep -E 'Using proxy for Discord: .*127\.0\.0\.1:3129' /tmp/gateway.log 2>/dev/null | tail -1 || true")🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@test/e2e/test-hermes-discord-e2e.sh` around lines 398 - 403, The test is too brittle because it greps an exact Hermes log line including the URL; update the assertion around gateway_proxy_log (the sandbox_exec grep invocation) to match a looser, stable substring or alternative behavior: either grep for both "proxy" and "Discord" (without the exact host:port) or assert the DISCORD_PROXY env is present in the sandbox-env and that outbound requests to Discord are routed through the proxy (reachability check), then call pass/fail as before. Locate the sandbox_exec call that assigns gateway_proxy_log and replace the exact URL match with the looser substring or the equivalent env+reachability checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@test/e2e/test-hermes-discord-e2e.sh`:
- Around line 501-508: The script currently allows skipping gateway auth even
when a real Discord token is supplied; update the conditional around
gateway_auth_log to treat a provided real token as requiring gateway
authentication: after computing gateway_auth_log, if it's non-empty retain pass
("Hermes Discord gateway authenticated..."); otherwise, if the environment
variable DISCORD_BOT_TOKEN_REAL is set/non-empty, call fail (same message as the
HERMES_DISCORD_REQUIRE_GATEWAY_AUTH branch) to enforce that a real token must
produce gateway auth; keep the existing HERMES_DISCORD_REQUIRE_GATEWAY_AUTH
check for explicit test-run forcing, and only fall back to skip when neither
DISCORD_BOT_TOKEN_REAL nor HERMES_DISCORD_REQUIRE_GATEWAY_AUTH indicate a hard
requirement. Use the same identifiers gateway_auth_log, DISCORD_BOT_TOKEN_REAL,
and HERMES_DISCORD_REQUIRE_GATEWAY_AUTH to locate and modify the logic.
---
Nitpick comments:
In `@test/e2e/test-hermes-discord-e2e.sh`:
- Around line 398-403: The test is too brittle because it greps an exact Hermes
log line including the URL; update the assertion around gateway_proxy_log (the
sandbox_exec grep invocation) to match a looser, stable substring or alternative
behavior: either grep for both "proxy" and "Discord" (without the exact
host:port) or assert the DISCORD_PROXY env is present in the sandbox-env and
that outbound requests to Discord are routed through the proxy (reachability
check), then call pass/fail as before. Locate the sandbox_exec call that assigns
gateway_proxy_log and replace the exact URL match with the looser substring or
the equivalent env+reachability checks.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 43a72dd9-bbb6-4e16-ac19-41f310a626dc
📒 Files selected for processing (13)
agents/hermes/Dockerfileagents/hermes/config/messaging-config.tsagents/hermes/decode-proxy.pyagents/hermes/policy-additions.yamlagents/hermes/policy-permissive.yamlagents/hermes/start.shsrc/lib/agent-runtime.test.tssrc/lib/agent-runtime.tstest/e2e/test-hermes-discord-e2e.shtest/e2e/test-messaging-providers.shtest/generate-hermes-config.test.tstest/policies.test.tstest/sandbox-init.test.ts
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@agents/hermes/policy-additions.yaml`:
- Around line 93-94: The policy currently grants PUT and PATCH on the glob "/**"
(the allow entries for method: PUT and method: PATCH), which overbroadly permits
mutation across the site; update those two allow rules to the same Discord REST
scopes used by the existing DELETE rules (i.e., restrict paths to the Discord
endpoints introduced in this file such as the channel/message API paths used
elsewhere), replacing the "/**" path with those specific Discord route patterns
so PUT/PATCH are limited to the Discord surface only.
In `@agents/hermes/start.sh`:
- Around line 283-309: The start_discord_facade function currently redirects
stdout/stderr to /tmp/discord-facade.log from the parent shell
(">/tmp/discord-facade.log"), which allows a TOCTOU/symlink attack; fix it by
using the project’s hardened/no-follow log helper to open the log file safely
and perform the redirection inside the child process after privilege drop (or
pass a safe file descriptor/path returned by the helper into the child),
ensuring the parent shell does not create or follow the /tmp symlink; update
both branches that call gosu/python3 in start_discord_facade to reuse that safe
log setup instead of direct redirection to /tmp/discord-facade.log.
In `@src/lib/agent-runtime.ts`:
- Around line 160-161: The readiness wait loops in
hermesDecodeProxyRecoveryCommand treat a missing ss binary as a success because
the break condition uses '! command -v ss ... || ss -tln ... | grep -q ... &&
break'; change both wait-loop break predicates to require ss to exist AND the
port to be found, i.e. use 'command -v ss >/dev/null 2>&1 && ss -tln 2>/dev/null
| grep -q "127.0.0.1:3129" && break' (and similarly for 3130) so the loops only
break when ss is present and the service port is actually listening.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 30a96de5-51dc-4bfc-90b9-2c528b72caf2
📒 Files selected for processing (12)
agents/hermes/Dockerfileagents/hermes/config/messaging-config.tsagents/hermes/discord-facade.pyagents/hermes/discord-preload/sitecustomize.pyagents/hermes/policy-additions.yamlagents/hermes/start.shsrc/lib/agent-runtime.test.tssrc/lib/agent-runtime.tstest/e2e/test-hermes-discord-e2e.shtest/generate-hermes-config.test.tstest/hermes-discord-facade.test.tstest/sandbox-init.test.ts
✅ Files skipped from review due to trivial changes (1)
- src/lib/agent-runtime.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- test/generate-hermes-config.test.ts
- agents/hermes/config/messaging-config.ts
- agents/hermes/Dockerfile
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@test/e2e/test-hermes-discord-e2e.sh`:
- Around line 597-604: The one-shot grep for "Connected as " in /tmp/gateway.log
(variable gateway_auth_log) can race with the gateway IDENTIFY→READY handshake;
replace it with a retry/wait loop that calls sandbox_exec "grep -E 'Connected as
' /tmp/gateway.log" repeatedly (e.g., sleep 1 between attempts) up to a short
timeout (e.g., 30s) and sets gateway_auth_log when a match is found; after the
loop keep the existing pass/fail logic that uses gateway_auth_log, ensuring you
reuse the same sandbox_exec, gateway_auth_log variable, and pass/fail calls so
behavior is unchanged except for waiting for the READY line.
- Around line 409-414: The single-shot facade health probe using sandbox_exec
and curl (checking facade_health for '"ok":true') can race with the facade
startup; change the check in the test-hermes-discord-e2e.sh script to retry: run
sandbox_exec "curl -sf http://127.0.0.1:3130/health ..." in a loop (e.g., up to
15 attempts with sleep 4 between tries), update facade_health on each attempt,
break on success, then call pass "Hermes fake Discord facade is healthy..." if
succeeded or fail "Hermes fake Discord facade did not answer health probe:
${facade_health:0:200}" if all retries exhausted; keep use of sandbox_exec,
pass, and fail symbols intact.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 1c71782f-95ee-406c-9285-c7648029d157
📒 Files selected for processing (1)
test/e2e/test-hermes-discord-e2e.sh
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/lib/agent-runtime.ts`:
- Around line 160-161: The hermesDecodeProxyRecoveryCommand string currently
redirects the discord facade output to a parent-shell path
(>/tmp/discord-facade.log) which is unsafe; update
hermesDecodeProxyRecoveryCommand so it does not perform shell-level redirection
in the parent but instead invokes the child process with the same no-follow log
setup used in agents/hermes/start.sh (i.e., remove the parent-side
">/tmp/discord-facade.log 2>&1" and ensure the python3
/usr/local/bin/nemoclaw-discord-facade child does its own safe logfile open with
O_NOFOLLOW/O_EXCL or the existing helper used by start.sh), preserving the same
environment and background/port-check logic around
hermesDecodeProxyRecoveryCommand.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 6c8f08bf-451c-45fd-9046-94158e05a4f5
📒 Files selected for processing (7)
agents/hermes/policy-additions.yamlagents/hermes/start.shsrc/lib/agent-runtime.test.tssrc/lib/agent-runtime.tstest/e2e/test-hermes-discord-e2e.shtest/policies.test.tstest/sandbox-init.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- src/lib/agent-runtime.test.ts
- test/e2e/test-hermes-discord-e2e.sh
| function hermesDecodeProxyRecoveryCommand(): string { | ||
| return 'if ! command -v ss >/dev/null 2>&1 || ! ss -tln 2>/dev/null | grep -q "127.0.0.1:3129"; then nohup python3 /usr/local/bin/nemoclaw-decode-proxy >/dev/null 2>&1 & for _i in 1 2 3 4 5 6 7 8 9 10; do ! command -v ss >/dev/null 2>&1 || ss -tln 2>/dev/null | grep -q "127.0.0.1:3129" && break; sleep 0.5; done; fi;'; | ||
| return 'if ! command -v ss >/dev/null 2>&1 || ! ss -tln 2>/dev/null | grep -q "127.0.0.1:3129"; then nohup python3 /usr/local/bin/nemoclaw-decode-proxy >/dev/null 2>&1 & for _i in 1 2 3 4 5 6 7 8 9 10; do command -v ss >/dev/null 2>&1 && ss -tln 2>/dev/null | grep -q "127.0.0.1:3129" && break; sleep 0.5; done; fi; if ! command -v ss >/dev/null 2>&1 || ! ss -tln 2>/dev/null | grep -q "127.0.0.1:3130"; then env -u NEMOCLAW_DISCORD_FACADE_URL -u PYTHONPATH DISCORD_PROXY=http://127.0.0.1:3129 HTTPS_PROXY=http://127.0.0.1:3129 HTTP_PROXY=http://127.0.0.1:3129 NEMOCLAW_DISCORD_FACADE_PORT=3130 nohup python3 /usr/local/bin/nemoclaw-discord-facade >/tmp/discord-facade.log 2>&1 & for _i in 1 2 3 4 5 6 7 8 9 10; do command -v ss >/dev/null 2>&1 && ss -tln 2>/dev/null | grep -q "127.0.0.1:3130" && break; sleep 0.5; done; fi;'; |
There was a problem hiding this comment.
Harden the recovery facade log the same way as startup.
The recovery command still opens >/tmp/discord-facade.log in the parent shell. If this path is reachable from a root recovery run, a sandbox-created symlink in /tmp can clobber an arbitrary file. Reuse the existing no-follow log setup here and move the redirection into the child process, mirroring agents/hermes/start.sh.
Suggested fix
function hermesDecodeProxyRecoveryCommand(): string {
- return 'if ! command -v ss >/dev/null 2>&1 || ! ss -tln 2>/dev/null | grep -q "127.0.0.1:3129"; then nohup python3 /usr/local/bin/nemoclaw-decode-proxy >/dev/null 2>&1 & for _i in 1 2 3 4 5 6 7 8 9 10; do command -v ss >/dev/null 2>&1 && ss -tln 2>/dev/null | grep -q "127.0.0.1:3129" && break; sleep 0.5; done; fi; if ! command -v ss >/dev/null 2>&1 || ! ss -tln 2>/dev/null | grep -q "127.0.0.1:3130"; then env -u NEMOCLAW_DISCORD_FACADE_URL -u PYTHONPATH DISCORD_PROXY=http://127.0.0.1:3129 HTTPS_PROXY=http://127.0.0.1:3129 HTTP_PROXY=http://127.0.0.1:3129 NEMOCLAW_DISCORD_FACADE_PORT=3130 nohup python3 /usr/local/bin/nemoclaw-discord-facade >/tmp/discord-facade.log 2>&1 & for _i in 1 2 3 4 5 6 7 8 9 10; do command -v ss >/dev/null 2>&1 && ss -tln 2>/dev/null | grep -q "127.0.0.1:3130" && break; sleep 0.5; done; fi;';
+ const facadeLogSetup =
+ `${buildNoFollowLogSetupCommand("/tmp/discord-facade.log", "gateway", "0o600")} || exit 1;`;
+ return `${facadeLogSetup} if ! command -v ss >/dev/null 2>&1 || ! ss -tln 2>/dev/null | grep -q "127.0.0.1:3129"; then nohup python3 /usr/local/bin/nemoclaw-decode-proxy >/dev/null 2>&1 & for _i in 1 2 3 4 5 6 7 8 9 10; do command -v ss >/dev/null 2>&1 && ss -tln 2>/dev/null | grep -q "127.0.0.1:3129" && break; sleep 0.5; done; fi; if ! command -v ss >/dev/null 2>&1 || ! ss -tln 2>/dev/null | grep -q "127.0.0.1:3130"; then env -u NEMOCLAW_DISCORD_FACADE_URL -u PYTHONPATH DISCORD_PROXY=http://127.0.0.1:3129 HTTPS_PROXY=http://127.0.0.1:3129 HTTP_PROXY=http://127.0.0.1:3129 NEMOCLAW_DISCORD_FACADE_PORT=3130 nohup sh -c 'exec "$@" >/tmp/discord-facade.log 2>&1' sh python3 /usr/local/bin/nemoclaw-discord-facade & for _i in 1 2 3 4 5 6 7 8 9 10; do command -v ss >/dev/null 2>&1 && ss -tln 2>/dev/null | grep -q "127.0.0.1:3130" && break; sleep 0.5; done; fi;`;
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/agent-runtime.ts` around lines 160 - 161, The
hermesDecodeProxyRecoveryCommand string currently redirects the discord facade
output to a parent-shell path (>/tmp/discord-facade.log) which is unsafe; update
hermesDecodeProxyRecoveryCommand so it does not perform shell-level redirection
in the parent but instead invokes the child process with the same no-follow log
setup used in agents/hermes/start.sh (i.e., remove the parent-side
">/tmp/discord-facade.log 2>&1" and ensure the python3
/usr/local/bin/nemoclaw-discord-facade child does its own safe logfile open with
O_NOFOLLOW/O_EXCL or the existing helper used by start.sh), preserving the same
environment and background/port-check logic around
hermesDecodeProxyRecoveryCommand.
…oxy-bridge Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/lib/agent/runtime.ts`:
- Line 164: The ss port checks in the returned shell command string match
substrings (e.g. "127.0.0.1:3129" also matches "127.0.0.1:31290"); update both
grep invocations inside the returned string in src/lib/agent/runtime.ts so they
match the port exactly by using a stricter regex, e.g. replace grep -q
"127.0.0.1:3129" and grep -q "127.0.0.1:3130" with grep -qE
"127\\.0\\.0\\.1:3129($|\\s)" and grep -qE "127\\.0\\.0\\.1:3130($|\\s)" (or
equivalent word-boundary pattern) so the code paths around ${facadeLogSetup} and
${facadeLaunch} and the nohup python3 proxy launch use exact-port checks.
- Around line 161-164: The facade log setup currently hardcodes
/tmp/discord-facade.log (facadeLogSetup and facadeLaunch) so if that file is
unwritable the redirection fails; change facadeLogSetup to try
buildNoFollowLogSetupCommand("/tmp/discord-facade.log", ...) and if it returns a
non-fatal EACCES/EPERM outcome fall back to creating or using a writable path
(e.g. mktemp in $TMPDIR or /dev/shm/discord-facade-XXXX) and set a single
variable (e.g. facadeLogPath) to that chosen path; then update facadeLaunch to
use that facadeLogPath for the exec redirection instead of the hardcoded
"/tmp/discord-facade.log" so the setup and launch always reference the same
writable log file.
In `@test/policies.test.ts`:
- Around line 798-830: rulesFor currently uses find so it only returns rules
from the first matching endpoint; change rulesFor to collect rules from all
endpoints for the host (use filter + flatMap or map+flat) so
nousRules/discordRules include every endpoint's rules; then tighten the discord
assertions by not only checking expect.arrayContaining for required mutation
rules but also asserting that no other write rules exist (e.g., collect all
rules with methods in ["PUT","PATCH","DELETE"] from discordRules and assert they
exactly match the allowed list and that none have path "/**"). Ensure you update
references to rulesFor, nousRules, and discordRules in the test accordingly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 0d2e0a64-bd87-444e-88bf-f34b42cacfad
📒 Files selected for processing (4)
agents/hermes/policy-additions.yamlsrc/lib/agent/runtime.test.tssrc/lib/agent/runtime.tstest/policies.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- agents/hermes/policy-additions.yaml
Signed-off-by: Aaron Erickson <aerickson@nvidia.com>
Summary
discord.pysees normal Discord REST and Gateway behavior without exposing the real bot token to sandbox-owned config, env, argv, logs, or local WebSocket framesdiscord.pyDiscord transports to the loopback facade with a Pythonsitecustomizepreload while keeping Hermes provider semantics unchanged (commands.Bot(...)andclient.start(self.config.token)still run normally)Authorization: Bot openshell:resolve:env:DISCORD_BOT_TOKENHow the fix works
Hermes still writes and consumes the Discord token as
openshell:resolve:env:DISCORD_BOT_TOKEN. At runtime,start.shnow starts two local helpers before the Hermes gateway: the existing URL decode proxy on127.0.0.1:3129, then the new Discord facade on127.0.0.1:3130. Hermes is launched withDISCORD_PROXY=http://127.0.0.1:3129,NEMOCLAW_DISCORD_FACADE_URL=http://127.0.0.1:3130, andPYTHONPATH=/opt/nemoclaw-hermes-discord-preload....The preload patches
aiohttpcalls made bydiscord.py. Requests todiscord.com/discordapp.comAPI paths are rewritten to the loopback facade. Gateway WebSocket connections togateway.discord.ggare rewritten tows://127.0.0.1:3130/gateway. This meansdiscord.pystill emits its normal REST requests and GatewayIDENTIFY/RESUMEframes, but they terminate at a sandbox-local Discord-compatible service instead of crossing the OpenShell WebSocket tunnel with an unresolved placeholder token.The facade accepts the placeholder token only. It sends the expected Gateway
HELLO, heartbeat ACKs,READY, andRESUMEDdispatches, tracks sequence/session state, and rejects non-placeholder local Gateway tokens with close code4004. Startup REST endpoints thatdiscord.pyneeds are emulated locally, and application-command endpoints are stored in memory so slash command registration can complete without leaking secrets.REST calls the facade does not emulate are forwarded to real Discord through the decode proxy. The forwarded Authorization header remains
Bot openshell:resolve:env:DISCORD_BOT_TOKEN, so OpenShell remains the only layer that resolves the real credential. The facade process itself is launched with the preload disabled so it cannot accidentally rewrite its own upstream Discord traffic back into localhost.Discord interactions are received through the HTTP outgoing-webhook path. The facade validates Discord Ed25519 signatures on
/interactions, handles PINGs directly, maps real interaction tokens to memory-onlynemoclaw-local-*tokens, and injects non-PING payloads into the local Gateway stream asINTERACTION_CREATE. Followup/callback routes translate the local token back to the real interaction token only inside the facade's memory before forwarding through the REST proxy path.For non-interaction Gateway event coverage, the facade includes a REST poller that can synthesize message, edit, delete, reaction, thread, channel, guild, and member-style deltas into normal Gateway dispatches. Reaction polling uses a stable synthetic user id because Discord REST reaction aggregates do not expose the individual reacting user.
Security boundary
openshell:resolve:env:DISCORD_BOT_TOKENor otheropenshell:resolve:env:*placeholders, never the real bot token.Runtime wiring changed
agents/hermes/discord-facade.pyimplements the local REST/Gateway facade and interaction ingress.agents/hermes/discord-preload/sitecustomize.pyrewritesdiscord.pyDiscord REST/Gateway transports to the facade.agents/hermes/start.shstarts the facade and exports the preload/facade environment for Hermes.src/lib/agent-runtime.tsincludes the facade in Hermes manual recovery startup.agents/hermes/config/messaging-config.tswrites the facade URL and Discord guild ids into Hermes env.agents/hermes/policy-additions.yamlallows the Discord REST methods/paths needed for command, message, reaction, and webhook operations.Validation
python3 -m py_compile agents/hermes/discord-facade.py agents/hermes/decode-proxy.pybash -n agents/hermes/start.sh test/e2e/test-hermes-discord-e2e.shnpm run build:clinpx vitest run test/hermes-discord-facade.test.ts test/generate-hermes-config.test.ts src/lib/agent-runtime.test.ts test/sandbox-init.test.ts test/policies.test.tsgit diff --checkNot run
Summary by CodeRabbit
New Features
Tests