From 0da7ee6cfff1f93ad38e328695ae87a657197ff2 Mon Sep 17 00:00:00 2001
From: Kitt <assafdagancos@gmail.com>
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

