Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion scripts/generate-openclaw-config.py
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,7 @@ def _placeholder(channel: str, env_key: str) -> str:
}
if ch == "slack":
account["appToken"] = _placeholder(ch, "SLACK_APP_TOKEN")
if ch == "telegram":
if ch in {"discord", "telegram"}:
account["proxy"] = proxy_url
if ch == "telegram":
account["groupPolicy"] = "open"
Expand Down
9 changes: 7 additions & 2 deletions test/e2e/lib/discord-gateway-proof.sh
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,20 @@ apply_fake_discord_gateway_policy() {
run_fake_discord_gateway_node_client() {
local port="$1"
local identify_token="$2"
local proxy_url="${3:-}"
local host="${FAKE_DISCORD_GATEWAY_HOST:-host.openshell.internal}"
sandbox_exec_stdin "FAKE_DISCORD_GATEWAY_CLIENT_HOST='$host' FAKE_DISCORD_GATEWAY_CLIENT_PORT='$port' FAKE_DISCORD_GATEWAY_IDENTIFY_TOKEN='$identify_token' node - 2>&1" <<'NODE'
local proxy_env=""
if [ -n "$proxy_url" ]; then
printf -v proxy_env ' FAKE_DISCORD_GATEWAY_PROXY_URL=%q' "$proxy_url"
fi
sandbox_exec_stdin "FAKE_DISCORD_GATEWAY_CLIENT_HOST='$host' FAKE_DISCORD_GATEWAY_CLIENT_PORT='$port' FAKE_DISCORD_GATEWAY_IDENTIFY_TOKEN='$identify_token'$proxy_env node - 2>&1" <<'NODE'
const crypto = require("crypto");
const net = require("net");

const host = process.env.FAKE_DISCORD_GATEWAY_CLIENT_HOST || "host.openshell.internal";
const port = Number(process.env.FAKE_DISCORD_GATEWAY_CLIENT_PORT);
const identifyToken = process.env.FAKE_DISCORD_GATEWAY_IDENTIFY_TOKEN;
const proxyUrl = process.env.HTTP_PROXY || process.env.http_proxy || "";
const proxyUrl = process.env.FAKE_DISCORD_GATEWAY_PROXY_URL || process.env.HTTP_PROXY || process.env.http_proxy || "";
const results = [];

function proxyTarget() {
Expand Down
46 changes: 45 additions & 1 deletion test/e2e/test-messaging-providers.sh
Original file line number Diff line number Diff line change
Expand Up @@ -916,6 +916,27 @@ print(account.get('token', ''))
skip "M9: No Discord token to check"
fi

# M9b: Discord Gateway WebSocket routing uses the OpenShell proxy.
# #3894 regressed because OpenClaw's Discord gateway client ignores proxy
# env vars and only uses the per-account proxy setting. The fake Gateway
# proof in M13b-M13g exercises that OpenShell WebSocket relay; this config
# assertion ensures the real OpenClaw Discord account is wired to the relay.
dc_proxy=$(echo "$channel_json" | python3 -c "
import json, sys
d = json.load(sys.stdin)
accounts = d.get('discord', {}).get('accounts', {})
account = accounts.get('default') or accounts.get('main') or {}
print(account.get('proxy', ''))
" 2>/dev/null || true)

if [ -n "$dc_token" ] && [ "$dc_proxy" = "http://10.200.0.1:3128" ]; then
pass "M9b: Discord account proxy is baked into openclaw.json for Gateway WebSocket routing"
elif [ -n "$dc_token" ]; then
fail "M9b: Discord account proxy missing or wrong; Gateway WebSocket may bypass OpenShell proxy (proxy='${dc_proxy}')"
else
skip "M9b: No Discord channel config to check"
fi

# M10: Telegram enabled
tg_enabled=$(echo "$channel_json" | python3 -c "
import json, sys
Expand Down Expand Up @@ -1416,7 +1437,9 @@ else
fail "M13-rest-e: Unexpected fake Discord REST capture counts: ${fake_rest_capture}"
fi

# M13b-M13f: Hermetic Discord Gateway over OpenShell's native WebSocket L7 path.
# M13b-M13g: Hermetic Discord Gateway over OpenShell's native WebSocket L7 path.
# M13d-config drives the fake Gateway using the proxy URL from the generated
# OpenClaw Discord account, which is the exact wiring #3894 depends on.
fake_gateway_ready=0
if start_fake_discord_gateway "$DISCORD_TOKEN"; then
fake_gateway_ready=1
Expand All @@ -1432,6 +1455,27 @@ else
fail "M13c: Failed to apply fake Discord Gateway policy: $(tail -20 /tmp/nemoclaw-fake-discord-policy.log 2>/dev/null | tr '\n' ' ' | cut -c1-300)"
fi

dc_ws_account_proxy=""
dc_proxy_safe="${dc_proxy:-}"
if [ "$fake_gateway_ready" = "1" ] && [ -n "$dc_proxy_safe" ]; then
dc_ws_account_proxy=$(run_fake_discord_gateway_node_client "$FAKE_DISCORD_GATEWAY_PORT" "openshell:resolve:env:DISCORD_BOT_TOKEN" "$dc_proxy_safe" || true)
fi
info "OpenClaw-config fake Discord Gateway probe: ${dc_ws_account_proxy:0:500}"

if [ "$fake_gateway_ready" != "1" ]; then
skip "M13d-config: Fake Discord Gateway unavailable; skipping OpenClaw account proxy proof"
elif [ -z "$dc_proxy_safe" ]; then
skip "M13d-config: No Discord account proxy in openclaw.json to exercise against fake Gateway"
elif echo "$dc_ws_account_proxy" | grep -q "^UPGRADE$" \
&& echo "$dc_ws_account_proxy" | grep -q "^HELLO$" \
&& echo "$dc_ws_account_proxy" | grep -q "^IDENTIFY_SENT_PLACEHOLDER$" \
&& echo "$dc_ws_account_proxy" | grep -q "^READY$" \
&& echo "$dc_ws_account_proxy" | grep -q "^HEARTBEAT_ACK$"; then
pass "M13d-config: Discord account proxy from openclaw.json reaches fake Gateway through OpenShell"
else
fail "M13d-config: Discord account proxy from openclaw.json failed against fake Gateway: ${dc_ws_account_proxy:0:400}"
fi

dc_ws_native=""
if [ "$fake_gateway_ready" = "1" ]; then
dc_ws_native=$(run_fake_discord_gateway_node_client "$FAKE_DISCORD_GATEWAY_PORT" "openshell:resolve:env:DISCORD_BOT_TOKEN" || true)
Expand Down
19 changes: 17 additions & 2 deletions test/generate-openclaw-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ describe("generate-openclaw-config.py: config generation", () => {
expect(config.plugins?.entries?.["openclaw-weixin"]?.enabled).toBe(true);
});

it("emits canonical openshell:resolve:env: placeholders for non-Slack channels", () => {
it("emits canonical placeholders and proxy routing for non-Slack channels", () => {
const channels = Buffer.from(JSON.stringify(["telegram", "discord"])).toString("base64");
const config = runConfigScript({ NEMOCLAW_MESSAGING_CHANNELS_B64: channels });
expect(config.channels.telegram.accounts.default.botToken).toBe(
Expand All @@ -358,7 +358,22 @@ describe("generate-openclaw-config.py: config generation", () => {
"openshell:resolve:env:DISCORD_BOT_TOKEN",
);
expect(config.channels.telegram.accounts.default.proxy).toBe("http://10.200.0.1:3128");
expect(config.channels.discord.accounts.default.proxy).toBeUndefined();
expect(config.channels.discord.accounts.default.proxy).toBe("http://10.200.0.1:3128");
});

it("#3894: routes Discord gateway traffic through the configured OpenShell proxy", () => {
const channels = Buffer.from(JSON.stringify(["discord"])).toString("base64");
const config = runConfigScript({
NEMOCLAW_MESSAGING_CHANNELS_B64: channels,
NEMOCLAW_PROXY_HOST: "10.201.0.9",
NEMOCLAW_PROXY_PORT: "43128",
});

expect(config.channels.discord.accounts.default).toMatchObject({
token: "openshell:resolve:env:DISCORD_BOT_TOKEN",
enabled: true,
proxy: "http://10.201.0.9:43128",
});
});

it("emits Bolt-shape placeholders for Slack so the SDK's prefix regex passes", () => {
Expand Down
Loading