From 11532a36fb910ca92ddd015b22392fab7eeaf0dd Mon Sep 17 00:00:00 2001 From: Alex Newman Date: Mon, 9 Feb 2026 20:14:41 -0500 Subject: [PATCH] Fix sendToChannel to use explicit OpenClaw SDK function mapping Replace dynamic function name construction with CHANNEL_SEND_MAP that matches the actual PluginRuntime.channel structure. Fixes WhatsApp (sendMessageWhatsApp) and iMessage (sendMessageIMessage) casing, and adds WhatsApp's required verbose option. Also adds null guard on SSE observation payload before type casting. Co-Authored-By: Claude Opus 4.6 --- openclaw/src/index.test.ts | 2 +- openclaw/src/index.ts | 36 +++++++++++++++++++++++++++++------- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/openclaw/src/index.test.ts b/openclaw/src/index.test.ts index 31dc82f5..d62f5d4c 100644 --- a/openclaw/src/index.test.ts +++ b/openclaw/src/index.test.ts @@ -943,7 +943,7 @@ describe("SSE stream integration", () => { await new Promise((resolve) => setTimeout(resolve, 200)); assert.equal(sentMessages.length, 0); - assert.ok(logs.some((l) => l.includes("Unknown channel type: matrix"))); + assert.ok(logs.some((l) => l.includes("Unsupported channel type: matrix"))); await getService().stop({}); }); diff --git a/openclaw/src/index.ts b/openclaw/src/index.ts index 9cbb2b0e..1a088af9 100644 --- a/openclaw/src/index.ts +++ b/openclaw/src/index.ts @@ -220,26 +220,48 @@ function formatObservationMessage(observation: ObservationSSEPayload): string { return message; } +// Explicit mapping from channel name to [runtime namespace key, send function name]. +// These match the PluginRuntime.channel structure in the OpenClaw SDK. +const CHANNEL_SEND_MAP: Record = { + telegram: { namespace: "telegram", functionName: "sendMessageTelegram" }, + whatsapp: { namespace: "whatsapp", functionName: "sendMessageWhatsApp" }, + discord: { namespace: "discord", functionName: "sendMessageDiscord" }, + slack: { namespace: "slack", functionName: "sendMessageSlack" }, + signal: { namespace: "signal", functionName: "sendMessageSignal" }, + imessage: { namespace: "imessage", functionName: "sendMessageIMessage" }, + line: { namespace: "line", functionName: "sendMessageLine" }, +}; + function sendToChannel( api: OpenClawPluginApi, channel: string, to: string, text: string ): Promise { - const channelApi = api.runtime.channel[channel]; + const mapping = CHANNEL_SEND_MAP[channel]; + if (!mapping) { + api.logger.warn(`[claude-mem] Unsupported channel type: ${channel}`); + return Promise.resolve(); + } + + const channelApi = api.runtime.channel[mapping.namespace]; if (!channelApi) { - api.logger.warn(`[claude-mem] Unknown channel type: ${channel}`); + api.logger.warn(`[claude-mem] Channel "${channel}" not available in runtime`); return Promise.resolve(); } - const sendFunctionName = `sendMessage${channel.charAt(0).toUpperCase()}${channel.slice(1)}`; - const senderFunction = channelApi[sendFunctionName]; + const senderFunction = channelApi[mapping.functionName]; if (!senderFunction) { - api.logger.warn(`[claude-mem] Channel "${channel}" has no ${sendFunctionName} function`); + api.logger.warn(`[claude-mem] Channel "${channel}" has no ${mapping.functionName} function`); return Promise.resolve(); } - return senderFunction(to, text).catch((error: unknown) => { + // WhatsApp requires a third options argument with { verbose: boolean } + const args: unknown[] = channel === "whatsapp" + ? [to, text, { verbose: false }] + : [to, text]; + + return senderFunction(...args).catch((error: unknown) => { const message = error instanceof Error ? error.message : String(error); api.logger.error(`[claude-mem] Failed to send to ${channel}: ${message}`); }); @@ -307,7 +329,7 @@ async function connectToSSEStream( try { const parsed = JSON.parse(jsonStr); - if (parsed.type === "new_observation") { + if (parsed.type === "new_observation" && parsed.observation) { const event = parsed as SSENewObservationEvent; const message = formatObservationMessage(event.observation); await sendToChannel(api, channel, to, message);