From 0da7ee6cfff1f93ad38e328695ae87a657197ff2 Mon Sep 17 00:00:00 2001 From: Kitt Date: Wed, 18 Feb 2026 13:34:08 +0000 Subject: [PATCH] fix: Add direct Discord delivery for transcript routing When routing transcripts to Discord threads, the OpenClaw agent hook's deliver mechanism doesn't reliably post messages. This adds direct Discord API posting as the primary method when DISCORD_BOT_TOKEN is set. Changes: - Add DISCORD_BOT_TOKEN and DISCORD_DIRECT_DELIVERY env vars - Add postToDiscordDirect() function for Discord API posting - Update sendVerboseMirrorToOpenClaw() to use direct delivery for Discord - Update sendToOpenClaw() (FastInject) to use direct delivery for Discord - Add discord status to /health endpoint - Update README with new configuration options Fixes transcript routing to Discord threads not working despite successful responses from OpenClaw agent hook. --- README.md | 7 +++ services/clawpilot-bridge/server.js | 72 +++++++++++++++++++++++++++-- 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3007674..4e947ac 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,13 @@ Required environment variables: 5. `OPENCLAW_HOOK_URL` 6. `OPENCLAW_HOOK_TOKEN` +Optional Discord direct delivery (recommended for Discord routing): + +7. `DISCORD_BOT_TOKEN` - Bot token for direct Discord posting +8. `DISCORD_DIRECT_DELIVERY` - Enable direct Discord posting (default: true) + +When `DISCORD_BOT_TOKEN` is set and a route_target specifies Discord, transcripts will be posted directly to Discord via API instead of relying on OpenClaw's agent hook deliver mechanism. + ## Verify End-to-End 1. Bridge health: diff --git a/services/clawpilot-bridge/server.js b/services/clawpilot-bridge/server.js index 8ffb208..ff39af5 100644 --- a/services/clawpilot-bridge/server.js +++ b/services/clawpilot-bridge/server.js @@ -115,6 +115,48 @@ const TELEGRAM_BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN || ''; const DEBUG_MIRROR_TELEGRAM = parseBooleanLike(process.env.DEBUG_MIRROR_TELEGRAM, false); const CONTROL_SPEAKER_REGEX = process.env.CONTROL_SPEAKER_REGEX || ''; +// Discord direct delivery settings +const DISCORD_BOT_TOKEN = process.env.DISCORD_BOT_TOKEN || ''; +const DISCORD_DIRECT_DELIVERY = parseBooleanLike(process.env.DISCORD_DIRECT_DELIVERY, true); + +/** + * Post message directly to Discord channel/thread via Discord API + * Bypasses OpenClaw agent hook which doesn't reliably deliver messages + */ +async function postToDiscordDirect(channelId, content) { + if (!DISCORD_BOT_TOKEN) { + console.error('[DiscordDirect] DISCORD_BOT_TOKEN not set'); + return { ok: false, error: 'DISCORD_BOT_TOKEN not configured' }; + } + + const sendStart = Date.now(); + try { + const response = await fetch(`https://discord.com/api/v10/channels/${channelId}/messages`, { + method: 'POST', + headers: { + 'Authorization': `Bot ${DISCORD_BOT_TOKEN}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ content }) + }); + + const elapsed = Date.now() - sendStart; + const data = await response.json(); + + if (response.ok) { + console.log(`[DiscordDirect] ${elapsed}ms - posted to ${channelId}`); + return { ok: true, messageId: data.id, elapsed }; + } else { + console.error(`[DiscordDirect] ${elapsed}ms - failed:`, data); + return { ok: false, error: data, elapsed }; + } + } catch (error) { + const elapsed = Date.now() - sendStart; + console.error(`[DiscordDirect] ${elapsed}ms - error:`, error.message); + return { ok: false, error: error.message, elapsed }; + } +} + // Debug mode - mirror raw final transcripts to active OpenClaw chat channel. // Optional Telegram mirroring can be enabled via DEBUG_MIRROR_TELEGRAM=true. let DEBUG_MODE = parseBooleanLike(process.env.DEBUG_MODE, false); @@ -163,11 +205,21 @@ async function sendDebugTranscript(speaker, text, isPartial, options = {}) { async function sendVerboseMirrorToOpenClaw(line, options = {}) { const sendStart = Date.now(); try { + const routeTarget = resolveRouteTarget(options.routeTarget, options.botId); + + // Use direct Discord delivery when targeting Discord and enabled + if (DISCORD_DIRECT_DELIVERY && routeTarget?.channel === 'discord' && routeTarget?.to && DISCORD_BOT_TOKEN) { + const result = await postToDiscordDirect(routeTarget.to, line); + const elapsed = Date.now() - sendStart; + console.log(`[VerboseMirror] ${elapsed}ms - ${result.ok ? "delivered" : "failed"} route=discord:${routeTarget.to} (direct)`); + return result; + } + + // Fallback to OpenClaw agent hook if (!OPENCLAW_HOOK_TOKEN) { console.error('[VerboseMirror] OPENCLAW_HOOK_TOKEN is required.'); return null; } - const routeTarget = resolveRouteTarget(options.routeTarget, options.botId); const payload = { message: `[MEETVERBOSE MIRROR]\nReply with exactly this line and nothing else:\n${line}`, @@ -372,6 +424,10 @@ app.get('/health', (req, res) => { token_set: Boolean(OPENCLAW_HOOK_TOKEN), url_source: OPENCLAW_HOOK_URL_SOURCE, token_source: OPENCLAW_HOOK_TOKEN_SOURCE + }, + discord: { + direct_delivery: DISCORD_DIRECT_DELIVERY, + token_set: Boolean(DISCORD_BOT_TOKEN) } }); }); @@ -1246,12 +1302,22 @@ async function reactImmediate(message) { async function sendToOpenClaw(message, options = {}) { const sendStart = Date.now(); try { + const routeTarget = resolveRouteTarget(options.routeTarget, options.botId); + const text = `[MEETING TRANSCRIPT]\n${message}`; + + // Use direct Discord delivery when targeting Discord and enabled + if (DISCORD_DIRECT_DELIVERY && routeTarget?.channel === 'discord' && routeTarget?.to && DISCORD_BOT_TOKEN) { + const result = await postToDiscordDirect(routeTarget.to, text); + const elapsed = Date.now() - sendStart; + console.log(`[FastInject] ${elapsed}ms - ${result.ok ? "delivered" : "failed"} route=discord:${routeTarget.to} (direct)`); + return elapsed; + } + + // Fallback to OpenClaw hooks if (!OPENCLAW_HOOK_TOKEN) { console.error('[FastInject] OPENCLAW_HOOK_TOKEN is required.'); return null; } - const routeTarget = resolveRouteTarget(options.routeTarget, options.botId); - const text = `[MEETING TRANSCRIPT]\n${message}`; const headers = { "Authorization": `Bearer ${OPENCLAW_HOOK_TOKEN}`, "Content-Type": "application/json" -- 2.43.0