diff --git a/CHANGELOG.md b/CHANGELOG.md index eb39b22d065..c60a8d9ec96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,11 @@ Docs: https://docs.openclaw.ai - Plugins/CLI: include package dependency install state in `openclaw plugins list --json` so scripts can spot missing plugin dependencies without runtime-loading plugins. - Plugins/update: on the beta OpenClaw update channel, default-line npm and ClawHub plugin updates try `@beta` first and fall back to default/latest when no plugin beta release exists. +- Channels: add Yuanbao channel docs entrance so the Tencent Yuanbao bot appears in the channel listing and sidebar navigation. (#73443) Thanks @loongfay. +- Active Memory: add optional per-conversation `allowedChatIds` and `deniedChatIds` filters so operators can enable recall only for selected direct, group, or channel conversations while keeping broad sessions skipped. (#67977) Thanks @quengh. +- Active Memory: return bounded partial recall summaries when the hidden memory sub-agent times out, including the default temporary-transcript path, so useful recovered context is not discarded. (#73219) Thanks @joeykrug. +- Docker setup: add `OPENCLAW_SKIP_ONBOARDING` so automated Docker installs can skip the interactive onboarding step while still applying gateway defaults. (#55518) Thanks @jinjimz. +- Channels/WhatsApp: support explicit WhatsApp Channel/Newsletter `@newsletter` outbound message targets with channel session metadata instead of DM routing. Fixes #13417; carries forward the narrow target idea from #13424 while addressing routing/session review concerns; refs #62697. Thanks @vincentkoc and @agentz-manfred. ### Fixes diff --git a/docs/channels/whatsapp.md b/docs/channels/whatsapp.md index 5bc9bab9b26..ac52d84df62 100644 --- a/docs/channels/whatsapp.md +++ b/docs/channels/whatsapp.md @@ -158,6 +158,7 @@ OpenClaw recommends running WhatsApp on a separate number when possible. (The ch - The reconnect watchdog follows WhatsApp Web transport activity, not only inbound app-message volume: quiet linked-device sessions stay up while transport frames continue, but a transport stall forces reconnect well before the later remote disconnect path. - Direct chats use DM session rules (`session.dmScope`; default `main` collapses DMs to the agent main session). - Group sessions are isolated (`agent::whatsapp:group:`). +- WhatsApp Channels/Newsletters can be explicit outbound targets with their native `@newsletter` JID. Outbound newsletter sends use channel session metadata (`agent::whatsapp:channel:`) rather than DM session semantics. - WhatsApp Web transport honors standard proxy environment variables on the gateway host (`HTTPS_PROXY`, `HTTP_PROXY`, `NO_PROXY` / lowercase variants). Prefer host-level proxy config over channel-specific WhatsApp proxy settings. - When `messages.removeAckAfterReply` is enabled, OpenClaw clears the WhatsApp ack reaction after a visible reply is delivered. @@ -214,6 +215,8 @@ content and identifiers. `allowFrom` accepts E.164-style numbers (normalized internally). + `allowFrom` is a DM sender access-control list. It does not gate explicit outbound sends to WhatsApp group JIDs or `@newsletter` channel JIDs. + Multi-account override: `channels.whatsapp.accounts..dmPolicy` (and `allowFrom`) take precedence over channel-level defaults for that account. Runtime behavior details: diff --git a/docs/cli/directory.md b/docs/cli/directory.md index 073a698e0d2..5d24fbff1f9 100644 --- a/docs/cli/directory.md +++ b/docs/cli/directory.md @@ -32,7 +32,7 @@ openclaw message send --channel slack --target user:U012ABCDEF --message "hello" ## ID formats (by channel) -- WhatsApp: `+15551234567` (DM), `1234567890-1234567890@g.us` (group) +- WhatsApp: `+15551234567` (DM), `1234567890-1234567890@g.us` (group), `120363123456789@newsletter` (Channel/Newsletter outbound target) - Telegram: `@username` or numeric chat id; groups are numeric ids - Slack: `user:U…` and `channel:C…` - Discord: `user:` and `channel:` diff --git a/docs/cli/message.md b/docs/cli/message.md index 76c42c3e23a..891a4b1c203 100644 --- a/docs/cli/message.md +++ b/docs/cli/message.md @@ -26,7 +26,7 @@ Channel selection: Target formats (`--target`): -- WhatsApp: E.164 or group JID +- WhatsApp: E.164, group JID, or WhatsApp Channel/Newsletter JID (`...@newsletter`) - Telegram: chat id or `@username` - Discord: `channel:` or `user:` (or `<@id>` mention; raw numeric ids are treated as channels) - Google Chat: `spaces/` or `users/` @@ -76,7 +76,7 @@ Name lookup: - Telegram only: `--thread-id` (forum topic id) - Slack only: `--thread-id` (thread timestamp; `--reply-to` uses the same field) - Telegram + Discord: `--silent` - - WhatsApp only: `--gif-playback` + - WhatsApp only: `--gif-playback`; WhatsApp Channels/Newsletters are addressed with their native `@newsletter` JID. - `poll` - Channels: WhatsApp/Telegram/Discord/Matrix/Microsoft Teams diff --git a/extensions/whatsapp/src/channel.ts b/extensions/whatsapp/src/channel.ts index 56755448053..2ddd9ff9857 100644 --- a/extensions/whatsapp/src/channel.ts +++ b/extensions/whatsapp/src/channel.ts @@ -28,6 +28,7 @@ import { import { checkWhatsAppHeartbeatReady } from "./heartbeat.js"; import { isWhatsAppGroupJid, + isWhatsAppNewsletterJid, looksLikeWhatsAppTargetId, normalizeWhatsAppMessagingTarget, normalizeWhatsAppTarget, @@ -56,7 +57,11 @@ function parseWhatsAppExplicitTarget(raw: string) { } return { to: normalized, - chatType: isWhatsAppGroupJid(normalized) ? ("group" as const) : ("direct" as const), + chatType: isWhatsAppGroupJid(normalized) + ? ("group" as const) + : isWhatsAppNewsletterJid(normalized) + ? ("channel" as const) + : ("direct" as const), }; } @@ -117,7 +122,7 @@ export const whatsappPlugin: ChannelPlugin = inferTargetChatType: ({ to }) => parseWhatsAppExplicitTarget(to)?.chatType, targetResolver: { looksLikeId: looksLikeWhatsAppTargetId, - hint: "", + hint: "", }, }, directory: { diff --git a/extensions/whatsapp/src/inbound/send-api.test.ts b/extensions/whatsapp/src/inbound/send-api.test.ts index de27afeb263..99fc7c4b316 100644 --- a/extensions/whatsapp/src/inbound/send-api.test.ts +++ b/extensions/whatsapp/src/inbound/send-api.test.ts @@ -260,6 +260,18 @@ describe("createWebSendApi", () => { expect(sendPresenceUpdate).toHaveBeenCalledWith("composing", "1555@s.whatsapp.net"); }); + it("does not send composing presence to newsletter JIDs", async () => { + await api.sendComposingTo("120363401234567890@newsletter"); + expect(sendPresenceUpdate).not.toHaveBeenCalled(); + }); + + it("preserves newsletter JIDs for outbound sends", async () => { + await api.sendMessage("120363401234567890@newsletter", "hello"); + expect(sendMessage).toHaveBeenCalledWith("120363401234567890@newsletter", { + text: "hello", + }); + }); + it("sends media as document when mediaType is undefined", async () => { const mediaBuffer = Buffer.from("test"); diff --git a/extensions/whatsapp/src/inbound/send-api.ts b/extensions/whatsapp/src/inbound/send-api.ts index 607d7d35646..fb2a4f99f6a 100644 --- a/extensions/whatsapp/src/inbound/send-api.ts +++ b/extensions/whatsapp/src/inbound/send-api.ts @@ -5,6 +5,7 @@ import type { WAPresence, } from "@whiskeysockets/baileys"; import { recordChannelActivity } from "openclaw/plugin-sdk/channel-activity-runtime"; +import { isWhatsAppNewsletterJid } from "../normalize.js"; import { buildQuotedMessageOptions } from "../quoted-message.js"; import { toWhatsappJid } from "../text-runtime.js"; import { @@ -135,6 +136,9 @@ export function createWebSendApi(params: { }, sendComposingTo: async (to: string): Promise => { const jid = toWhatsappJid(to); + if (isWhatsAppNewsletterJid(jid)) { + return; + } await params.sock.sendPresenceUpdate("composing", jid); }, } as const; diff --git a/extensions/whatsapp/src/normalize-target.ts b/extensions/whatsapp/src/normalize-target.ts index 4e3ec37df0a..e8cf26f6a49 100644 --- a/extensions/whatsapp/src/normalize-target.ts +++ b/extensions/whatsapp/src/normalize-target.ts @@ -5,6 +5,7 @@ const WHATSAPP_USER_JID_RE = /^(\d+)(?::\d+)?@s\.whatsapp\.net$/i; const WHATSAPP_LEGACY_USER_JID_RE = /^(\d+)@c\.us$/i; const WHATSAPP_LID_RE = /^(\d+)@lid$/i; const NON_WHATSAPP_PROVIDER_PREFIX_RE = /^[a-z][a-z0-9-]*:/i; +const WHATSAPP_NEWSLETTER_JID_RE = /^([0-9]+)@newsletter$/i; function stripWhatsAppTargetPrefixes(value: string): string { let candidate = value.trim(); @@ -30,6 +31,11 @@ export function isWhatsAppGroupJid(value: string): boolean { return /^[0-9]+(-[0-9]+)*$/.test(localPart); } +export function isWhatsAppNewsletterJid(value: string): boolean { + const candidate = stripWhatsAppTargetPrefixes(value); + return WHATSAPP_NEWSLETTER_JID_RE.test(candidate); +} + export function isWhatsAppUserTarget(value: string): boolean { const candidate = stripWhatsAppTargetPrefixes(value); return ( @@ -64,6 +70,10 @@ export function normalizeWhatsAppTarget(value: string): string | null { const localPart = candidate.slice(0, candidate.length - "@g.us".length); return `${localPart}@g.us`; } + if (isWhatsAppNewsletterJid(candidate)) { + const match = candidate.match(WHATSAPP_NEWSLETTER_JID_RE); + return match ? `${match[1]}@newsletter` : null; + } if (isWhatsAppUserTarget(candidate)) { const phone = extractUserJidPhone(candidate); if (!phone) { @@ -106,6 +116,7 @@ export function looksLikeWhatsAppTargetId(raw: string): boolean { return ( /^whatsapp:/i.test(trimmed) || isWhatsAppGroupJid(trimmed) || + isWhatsAppNewsletterJid(trimmed) || isWhatsAppUserTarget(trimmed) || normalizeWhatsAppTarget(trimmed) !== null ); diff --git a/extensions/whatsapp/src/normalize.ts b/extensions/whatsapp/src/normalize.ts index fa8afc80c12..2978ad01981 100644 --- a/extensions/whatsapp/src/normalize.ts +++ b/extensions/whatsapp/src/normalize.ts @@ -2,5 +2,7 @@ export { looksLikeWhatsAppTargetId, normalizeWhatsAppMessagingTarget, isWhatsAppGroupJid, + isWhatsAppNewsletterJid, + isWhatsAppUserTarget, normalizeWhatsAppTarget, } from "./normalize-target.js"; diff --git a/extensions/whatsapp/src/resolve-outbound-target.test.ts b/extensions/whatsapp/src/resolve-outbound-target.test.ts index c3af14ad902..3028bb09452 100644 --- a/extensions/whatsapp/src/resolve-outbound-target.test.ts +++ b/extensions/whatsapp/src/resolve-outbound-target.test.ts @@ -130,6 +130,29 @@ describe("resolveWhatsAppOutboundTarget", () => { }); }); + describe("newsletter JID handling", () => { + it("returns success for valid newsletter JID without applying DM allowFrom", () => { + vi.mocked(normalize.normalizeWhatsAppTarget).mockReturnValueOnce( + "120363123456789@newsletter", + ); + vi.mocked(normalize.isWhatsAppGroupJid).mockReturnValueOnce(false); + vi.mocked(normalize.isWhatsAppNewsletterJid).mockReturnValueOnce(true); + + expectResolutionOk( + { + to: "120363123456789@newsletter", + allowFrom: [SECONDARY_TARGET], + mode: "implicit", + }, + "120363123456789@newsletter", + ); + expect(vi.mocked(normalize.normalizeWhatsAppTarget)).toHaveBeenCalledOnce(); + expect(vi.mocked(normalize.normalizeWhatsAppTarget)).toHaveBeenCalledWith( + "120363123456789@newsletter", + ); + }); + }); + describe("implicit/heartbeat mode with allowList", () => { it("allows message when wildcard is present", () => { mockNormalizedDirectMessage(PRIMARY_TARGET, PRIMARY_TARGET); @@ -154,14 +177,14 @@ describe("resolveWhatsAppOutboundTarget", () => { allowFrom: [SECONDARY_TARGET], mode: "implicit", }, - `Target "${SECONDARY_TARGET}" is not listed in the configured WhatsApp allowFrom policy.`, + `Target "${PRIMARY_TARGET}" is not listed in the configured WhatsApp allowFrom policy.`, ); }); it("uses the normalized target in the allowFrom error message", () => { vi.mocked(normalize.normalizeWhatsAppTarget) - .mockReturnValueOnce(SECONDARY_TARGET) - .mockReturnValueOnce(PRIMARY_TARGET); + .mockReturnValueOnce(PRIMARY_TARGET) + .mockReturnValueOnce(SECONDARY_TARGET); vi.mocked(normalize.isWhatsAppGroupJid).mockReturnValueOnce(false); expectResolutionErrorMessage( @@ -189,8 +212,8 @@ describe("resolveWhatsAppOutboundTarget", () => { it("filters out invalid normalized entries from allowList", () => { vi.mocked(normalize.normalizeWhatsAppTarget) - .mockReturnValueOnce(null) .mockReturnValueOnce("+11234567890") + .mockReturnValueOnce(null) .mockReturnValueOnce("+11234567890"); vi.mocked(normalize.isWhatsAppGroupJid).mockReturnValueOnce(false); diff --git a/extensions/whatsapp/src/resolve-outbound-target.ts b/extensions/whatsapp/src/resolve-outbound-target.ts index d2233453f7e..8a994629fa7 100644 --- a/extensions/whatsapp/src/resolve-outbound-target.ts +++ b/extensions/whatsapp/src/resolve-outbound-target.ts @@ -1,5 +1,9 @@ import { missingTargetError } from "openclaw/plugin-sdk/channel-feedback"; -import { isWhatsAppGroupJid, normalizeWhatsAppTarget } from "./normalize-target.js"; +import { + isWhatsAppGroupJid, + isWhatsAppNewsletterJid, + normalizeWhatsAppTarget, +} from "./normalize-target.js"; export type WhatsAppOutboundTargetResolution = | { ok: true; to: string } @@ -15,6 +19,24 @@ export function resolveWhatsAppOutboundTarget(params: { mode: string | null | undefined; }): WhatsAppOutboundTargetResolution { const trimmed = params.to?.trim() ?? ""; + if (!trimmed) { + return { + ok: false, + error: missingTargetError("WhatsApp", ""), + }; + } + + const normalizedTo = normalizeWhatsAppTarget(trimmed); + if (!normalizedTo) { + return { + ok: false, + error: missingTargetError("WhatsApp", ""), + }; + } + if (isWhatsAppGroupJid(normalizedTo) || isWhatsAppNewsletterJid(normalizedTo)) { + return { ok: true, to: normalizedTo }; + } + const allowListRaw = (params.allowFrom ?? []) .map((entry) => String(entry).trim()) .filter(Boolean); @@ -23,32 +45,14 @@ export function resolveWhatsAppOutboundTarget(params: { .filter((entry) => entry !== "*") .map((entry) => normalizeWhatsAppTarget(entry)) .filter((entry): entry is string => Boolean(entry)); - - if (trimmed) { - const normalizedTo = normalizeWhatsAppTarget(trimmed); - if (!normalizedTo) { - return { - ok: false, - error: missingTargetError("WhatsApp", ""), - }; - } - if (isWhatsAppGroupJid(normalizedTo)) { - return { ok: true, to: normalizedTo }; - } - if (hasWildcard || allowList.length === 0) { - return { ok: true, to: normalizedTo }; - } - if (allowList.includes(normalizedTo)) { - return { ok: true, to: normalizedTo }; - } - return { - ok: false, - error: whatsappAllowFromPolicyError(normalizedTo), - }; + if (hasWildcard || allowList.length === 0) { + return { ok: true, to: normalizedTo }; + } + if (allowList.includes(normalizedTo)) { + return { ok: true, to: normalizedTo }; } - return { ok: false, - error: missingTargetError("WhatsApp", ""), + error: whatsappAllowFromPolicyError(normalizedTo), }; } diff --git a/extensions/whatsapp/src/resolve-target.test.ts b/extensions/whatsapp/src/resolve-target.test.ts index 37d5f3a7e58..67282b7e9c9 100644 --- a/extensions/whatsapp/src/resolve-target.test.ts +++ b/extensions/whatsapp/src/resolve-target.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from "vitest"; import { isWhatsAppGroupJid, + isWhatsAppNewsletterJid, looksLikeWhatsAppTargetId, isWhatsAppUserTarget, normalizeWhatsAppMessagingTarget, @@ -16,6 +17,15 @@ describe("normalizeWhatsAppTarget", () => { ); }); + it("preserves newsletter JIDs", () => { + expect(normalizeWhatsAppTarget("120363401234567890@newsletter")).toBe( + "120363401234567890@newsletter", + ); + expect(normalizeWhatsAppTarget("WhatsApp:120363401234567890@NEWSLETTER")).toBe( + "120363401234567890@newsletter", + ); + }); + it("normalizes direct JIDs to E.164", () => { expect(normalizeWhatsAppTarget("1555123@s.whatsapp.net")).toBe("+1555123"); }); @@ -40,6 +50,7 @@ describe("normalizeWhatsAppTarget", () => { expect(normalizeWhatsAppTarget("group:123456789-987654321@g.us")).toBeNull(); expect(normalizeWhatsAppTarget(" WhatsApp:Group:123456789-987654321@G.US ")).toBeNull(); expect(normalizeWhatsAppTarget("abc@s.whatsapp.net")).toBeNull(); + expect(normalizeWhatsAppTarget("abc@newsletter")).toBeNull(); }); it("rejects non-WhatsApp provider-prefixed phone-like targets", () => { @@ -68,6 +79,17 @@ describe("isWhatsAppUserTarget", () => { }); }); +describe("isWhatsAppNewsletterJid", () => { + it("detects newsletter JIDs with or without prefixes", () => { + expect(isWhatsAppNewsletterJid("120363401234567890@newsletter")).toBe(true); + expect(isWhatsAppNewsletterJid("whatsapp:120363401234567890@newsletter")).toBe(true); + expect(isWhatsAppNewsletterJid("120363401234567890@NEWSLETTER")).toBe(true); + expect(isWhatsAppNewsletterJid("abc@newsletter")).toBe(false); + expect(isWhatsAppNewsletterJid("120363401234567890@g.us")).toBe(false); + expect(isWhatsAppNewsletterJid("+1555123")).toBe(false); + }); +}); + describe("isWhatsAppGroupJid", () => { it("detects group JIDs with or without prefixes", () => { expect(isWhatsAppGroupJid("120363401234567890@g.us")).toBe(true); @@ -91,6 +113,7 @@ describe("looksLikeWhatsAppTargetId", () => { it("detects common WhatsApp target forms", () => { expect(looksLikeWhatsAppTargetId("whatsapp:+15555550123")).toBe(true); expect(looksLikeWhatsAppTargetId("15555550123@c.us")).toBe(true); + expect(looksLikeWhatsAppTargetId("120363401234567890@newsletter")).toBe(true); expect(looksLikeWhatsAppTargetId("+15555550123")).toBe(true); expect(looksLikeWhatsAppTargetId("")).toBe(false); }); diff --git a/extensions/whatsapp/src/send.test.ts b/extensions/whatsapp/src/send.test.ts index cb19e83e8ec..33d186312f5 100644 --- a/extensions/whatsapp/src/send.test.ts +++ b/extensions/whatsapp/src/send.test.ts @@ -144,6 +144,25 @@ describe("web outbound", () => { expect(sendMessage).toHaveBeenCalledWith("+1555", "hi", undefined, undefined); }); + it("sends newsletter messages via the active listener without composing presence", async () => { + const result = await sendMessageWhatsApp("120363401234567890@newsletter", "hi", { + verbose: false, + cfg: WHATSAPP_TEST_CFG, + }); + + expect(result).toEqual({ + messageId: "msg123", + toJid: "120363401234567890@newsletter", + }); + expect(sendComposingTo).not.toHaveBeenCalled(); + expect(sendMessage).toHaveBeenCalledWith( + "120363401234567890@newsletter", + "hi", + undefined, + undefined, + ); + }); + it("uses configured defaultAccount when outbound accountId is omitted", async () => { hoisted.controllerListeners.clear(); hoisted.controllerListeners.set("work", { diff --git a/extensions/whatsapp/src/send.ts b/extensions/whatsapp/src/send.ts index 836f083113f..490bb80f4a4 100644 --- a/extensions/whatsapp/src/send.ts +++ b/extensions/whatsapp/src/send.ts @@ -16,6 +16,7 @@ import { } from "./accounts.js"; import { getRegisteredWhatsAppConnectionController } from "./connection-controller-registry.js"; import type { ActiveWebListener, ActiveWebSendOptions } from "./inbound/types.js"; +import { isWhatsAppNewsletterJid } from "./normalize.js"; import { normalizeWhatsAppPayloadText, prepareWhatsAppOutboundMedia, @@ -142,7 +143,9 @@ export async function sendMessageWhatsApp( } outboundLog.info(`Sending message -> ${redactedJid}${primaryMediaUrl ? " (media)" : ""}`); logger.info({ jid: redactedJid, hasMedia: Boolean(primaryMediaUrl) }, "sending message"); - await active.sendComposingTo(to); + if (!isWhatsAppNewsletterJid(jid)) { + await active.sendComposingTo(to); + } const hasExplicitAccountId = Boolean(options.accountId?.trim()); const accountId = hasExplicitAccountId ? resolvedAccountId : undefined; const sendOptions: ActiveWebSendOptions | undefined = @@ -192,7 +195,9 @@ export async function sendTypingWhatsApp( cfg, accountId: options.accountId, }); - await active.sendComposingTo(to); + if (!isWhatsAppNewsletterJid(toWhatsappJid(to))) { + await active.sendComposingTo(to); + } } export async function sendReactionWhatsApp( diff --git a/extensions/whatsapp/src/session-route.test.ts b/extensions/whatsapp/src/session-route.test.ts new file mode 100644 index 00000000000..9e903b04047 --- /dev/null +++ b/extensions/whatsapp/src/session-route.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, it } from "vitest"; +import { resolveWhatsAppOutboundSessionRoute } from "./session-route.js"; + +describe("resolveWhatsAppOutboundSessionRoute", () => { + it("routes newsletter JIDs as channel sessions", () => { + const route = resolveWhatsAppOutboundSessionRoute({ + cfg: {}, + agentId: "main", + target: "120363401234567890@newsletter", + }); + + expect(route).toMatchObject({ + sessionKey: "agent:main:whatsapp:channel:120363401234567890@newsletter", + baseSessionKey: "agent:main:whatsapp:channel:120363401234567890@newsletter", + peer: { + kind: "channel", + id: "120363401234567890@newsletter", + }, + chatType: "channel", + from: "120363401234567890@newsletter", + to: "120363401234567890@newsletter", + }); + }); + + it("keeps direct user targets on direct session semantics", () => { + const route = resolveWhatsAppOutboundSessionRoute({ + cfg: { session: { dmScope: "per-channel-peer" } }, + agentId: "main", + target: "+15551234567", + }); + + expect(route).toMatchObject({ + sessionKey: "agent:main:whatsapp:direct:+15551234567", + peer: { + kind: "direct", + id: "+15551234567", + }, + chatType: "direct", + }); + }); +}); diff --git a/extensions/whatsapp/src/session-route.ts b/extensions/whatsapp/src/session-route.ts index 8f24f5de06d..85460cb0e69 100644 --- a/extensions/whatsapp/src/session-route.ts +++ b/extensions/whatsapp/src/session-route.ts @@ -2,7 +2,11 @@ import { buildChannelOutboundSessionRoute, type ChannelOutboundSessionRouteParams, } from "openclaw/plugin-sdk/core"; -import { isWhatsAppGroupJid, normalizeWhatsAppTarget } from "./normalize.js"; +import { + isWhatsAppGroupJid, + isWhatsAppNewsletterJid, + normalizeWhatsAppTarget, +} from "./normalize.js"; export function resolveWhatsAppOutboundSessionRoute(params: ChannelOutboundSessionRouteParams) { const normalized = normalizeWhatsAppTarget(params.target); @@ -10,16 +14,18 @@ export function resolveWhatsAppOutboundSessionRoute(params: ChannelOutboundSessi return null; } const isGroup = isWhatsAppGroupJid(normalized); + const isNewsletter = isWhatsAppNewsletterJid(normalized); + const chatType = isGroup ? "group" : isNewsletter ? "channel" : "direct"; return buildChannelOutboundSessionRoute({ cfg: params.cfg, agentId: params.agentId, channel: "whatsapp", accountId: params.accountId, peer: { - kind: isGroup ? "group" : "direct", + kind: chatType, id: normalized, }, - chatType: isGroup ? "group" : "direct", + chatType, from: normalized, to: normalized, }); diff --git a/extensions/whatsapp/src/shared.ts b/extensions/whatsapp/src/shared.ts index 673b4d7cd89..b3ff58ddbc1 100644 --- a/extensions/whatsapp/src/shared.ts +++ b/extensions/whatsapp/src/shared.ts @@ -208,7 +208,7 @@ export function createWhatsAppPluginBase(params: { }, setupWizard: params.setupWizard, capabilities: { - chatTypes: ["direct", "group"], + chatTypes: ["direct", "group", "channel"], polls: true, reactions: true, media: true,