mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-07 22:44:16 +00:00
Feature/default messenger delivery target (openclaw#16985) thanks @KirillShchetinin
Verified: - pnpm build - pnpm check - pnpm test:macmini Co-authored-by: KirillShchetinin <13061871+KirillShchetinin@users.noreply.github.com> Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
59e58bf81c
commit
ee519086f6
@@ -6,6 +6,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Changes
|
||||
|
||||
- Channels/CLI: add per-account/channel `defaultTo` outbound routing fallback so `openclaw agent --deliver` can send without explicit `--reply-to` when a default target is configured. (#16985) Thanks @KirillShchetinin.
|
||||
- iOS/Gateway: stabilize background wake and reconnect behavior with background reconnect suppression/lease windows, BGAppRefresh wake fallback, location wake hook throttling, and APNs wake retry+nudge instrumentation. (#21226) thanks @mbelinky.
|
||||
- Auto-reply/UI: add model fallback lifecycle visibility in verbose logs, /status active-model context with fallback reason, and cohesive WebUI fallback indicators. (#20704) Thanks @joshavant.
|
||||
|
||||
|
||||
@@ -110,6 +110,8 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
|
||||
.map((entry) => String(entry).trim())
|
||||
.filter(Boolean)
|
||||
.map((entry) => entry.toLowerCase()),
|
||||
resolveDefaultTo: ({ cfg, accountId }) =>
|
||||
resolveDiscordAccount({ cfg, accountId }).config.defaultTo?.trim() || undefined,
|
||||
},
|
||||
security: {
|
||||
resolveDmPolicy: ({ cfg, accountId, account }) => {
|
||||
|
||||
@@ -178,6 +178,8 @@ export const googlechatPlugin: ChannelPlugin<ResolvedGoogleChatAccount> = {
|
||||
.map((entry) => String(entry))
|
||||
.filter(Boolean)
|
||||
.map(formatAllowFromEntry),
|
||||
resolveDefaultTo: ({ cfg, accountId }) =>
|
||||
resolveGoogleChatAccount({ cfg, accountId }).config.defaultTo?.trim() || undefined,
|
||||
},
|
||||
security: {
|
||||
resolveDmPolicy: ({ cfg, accountId, account }) => {
|
||||
|
||||
@@ -78,6 +78,8 @@ export const imessagePlugin: ChannelPlugin<ResolvedIMessageAccount> = {
|
||||
),
|
||||
formatAllowFrom: ({ allowFrom }) =>
|
||||
allowFrom.map((entry) => String(entry).trim()).filter(Boolean),
|
||||
resolveDefaultTo: ({ cfg, accountId }) =>
|
||||
resolveIMessageAccount({ cfg, accountId }).config.defaultTo?.trim() || undefined,
|
||||
},
|
||||
security: {
|
||||
resolveDmPolicy: ({ cfg, accountId, account }) => {
|
||||
|
||||
@@ -112,6 +112,9 @@ export const ircPlugin: ChannelPlugin<ResolvedIrcAccount, IrcProbe> = {
|
||||
),
|
||||
formatAllowFrom: ({ allowFrom }) =>
|
||||
allowFrom.map((entry) => normalizeIrcAllowEntry(String(entry))).filter(Boolean),
|
||||
resolveDefaultTo: ({ cfg, accountId }) =>
|
||||
resolveIrcAccount({ cfg: cfg as CoreConfig, accountId }).config.defaultTo?.trim() ||
|
||||
undefined,
|
||||
},
|
||||
security: {
|
||||
resolveDmPolicy: ({ cfg, accountId, account }) => {
|
||||
|
||||
@@ -43,6 +43,7 @@ export type IrcAccountConfig = {
|
||||
nickserv?: IrcNickServConfig;
|
||||
dmPolicy?: DmPolicy;
|
||||
allowFrom?: Array<string | number>;
|
||||
defaultTo?: string;
|
||||
groupPolicy?: GroupPolicy;
|
||||
groupAllowFrom?: Array<string | number>;
|
||||
groups?: Record<string, IrcChannelConfig>;
|
||||
|
||||
@@ -123,6 +123,7 @@ export const msteamsPlugin: ChannelPlugin<ResolvedMSTeamsAccount> = {
|
||||
.map((entry) => String(entry).trim())
|
||||
.filter(Boolean)
|
||||
.map((entry) => entry.toLowerCase()),
|
||||
resolveDefaultTo: ({ cfg }) => cfg.channels?.msteams?.defaultTo?.trim() || undefined,
|
||||
},
|
||||
security: {
|
||||
collectWarnings: ({ cfg }) => {
|
||||
|
||||
@@ -103,6 +103,8 @@ export const signalPlugin: ChannelPlugin<ResolvedSignalAccount> = {
|
||||
.filter(Boolean)
|
||||
.map((entry) => (entry === "*" ? "*" : normalizeE164(entry.replace(/^signal:/i, ""))))
|
||||
.filter(Boolean),
|
||||
resolveDefaultTo: ({ cfg, accountId }) =>
|
||||
resolveSignalAccount({ cfg, accountId }).config.defaultTo?.trim() || undefined,
|
||||
},
|
||||
security: {
|
||||
resolveDmPolicy: ({ cfg, accountId, account }) => {
|
||||
|
||||
@@ -130,6 +130,8 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount> = {
|
||||
.map((entry) => String(entry).trim())
|
||||
.filter(Boolean)
|
||||
.map((entry) => entry.toLowerCase()),
|
||||
resolveDefaultTo: ({ cfg, accountId }) =>
|
||||
resolveSlackAccount({ cfg, accountId }).config.defaultTo?.trim() || undefined,
|
||||
},
|
||||
security: {
|
||||
resolveDmPolicy: ({ cfg, accountId, account }) => {
|
||||
|
||||
@@ -119,6 +119,10 @@ export const telegramPlugin: ChannelPlugin<ResolvedTelegramAccount, TelegramProb
|
||||
.filter(Boolean)
|
||||
.map((entry) => entry.replace(/^(telegram|tg):/i, ""))
|
||||
.map((entry) => entry.toLowerCase()),
|
||||
resolveDefaultTo: ({ cfg, accountId }) => {
|
||||
const val = resolveTelegramAccount({ cfg, accountId }).config.defaultTo;
|
||||
return val != null ? String(val) : undefined;
|
||||
},
|
||||
},
|
||||
security: {
|
||||
resolveDmPolicy: ({ cfg, accountId, account }) => {
|
||||
|
||||
@@ -118,6 +118,12 @@ export const whatsappPlugin: ChannelPlugin<ResolvedWhatsAppAccount> = {
|
||||
.filter((entry): entry is string => Boolean(entry))
|
||||
.map((entry) => (entry === "*" ? entry : normalizeWhatsAppTarget(entry)))
|
||||
.filter((entry): entry is string => Boolean(entry)),
|
||||
resolveDefaultTo: ({ cfg, accountId }) => {
|
||||
const root = cfg.channels?.whatsapp;
|
||||
const normalized = normalizeAccountId(accountId);
|
||||
const account = root?.accounts?.[normalized];
|
||||
return (account?.defaultTo ?? root?.defaultTo)?.trim() || undefined;
|
||||
},
|
||||
},
|
||||
security: {
|
||||
resolveDmPolicy: ({ cfg, accountId, account }) => {
|
||||
|
||||
@@ -63,6 +63,10 @@ export type ChannelDock = {
|
||||
accountId?: string | null;
|
||||
allowFrom: Array<string | number>;
|
||||
}) => string[];
|
||||
resolveDefaultTo?: (params: {
|
||||
cfg: OpenClawConfig;
|
||||
accountId?: string | null;
|
||||
}) => string | undefined;
|
||||
};
|
||||
groups?: ChannelGroupAdapter;
|
||||
mentions?: ChannelMentionAdapter;
|
||||
@@ -174,6 +178,10 @@ const DOCKS: Record<ChatChannelId, ChannelDock> = {
|
||||
.filter(Boolean)
|
||||
.map((entry) => entry.replace(/^(telegram|tg):/i, ""))
|
||||
.map((entry) => entry.toLowerCase()),
|
||||
resolveDefaultTo: ({ cfg, accountId }) => {
|
||||
const val = resolveTelegramAccount({ cfg, accountId }).config.defaultTo;
|
||||
return val != null ? String(val) : undefined;
|
||||
},
|
||||
},
|
||||
groups: {
|
||||
resolveRequireMention: resolveTelegramGroupRequireMention,
|
||||
@@ -213,6 +221,12 @@ const DOCKS: Record<ChatChannelId, ChannelDock> = {
|
||||
.filter((entry): entry is string => Boolean(entry))
|
||||
.map((entry) => (entry === "*" ? entry : normalizeWhatsAppTarget(entry)))
|
||||
.filter((entry): entry is string => Boolean(entry)),
|
||||
resolveDefaultTo: ({ cfg, accountId }) => {
|
||||
const root = cfg.channels?.whatsapp;
|
||||
const normalized = normalizeAccountId(accountId);
|
||||
const account = root?.accounts?.[normalized];
|
||||
return (account?.defaultTo ?? root?.defaultTo)?.trim() || undefined;
|
||||
},
|
||||
},
|
||||
groups: {
|
||||
resolveRequireMention: resolveWhatsAppGroupRequireMention,
|
||||
@@ -267,6 +281,8 @@ const DOCKS: Record<ChatChannelId, ChannelDock> = {
|
||||
);
|
||||
},
|
||||
formatAllowFrom: ({ allowFrom }) => formatDiscordAllowFrom(allowFrom),
|
||||
resolveDefaultTo: ({ cfg, accountId }) =>
|
||||
resolveDiscordAccount({ cfg, accountId }).config.defaultTo?.trim() || undefined,
|
||||
},
|
||||
groups: {
|
||||
resolveRequireMention: resolveDiscordGroupRequireMention,
|
||||
@@ -311,6 +327,20 @@ const DOCKS: Record<ChatChannelId, ChannelDock> = {
|
||||
.replace(/^user:/i, "")
|
||||
.toLowerCase(),
|
||||
),
|
||||
resolveDefaultTo: ({ cfg, accountId }) => {
|
||||
const channel = cfg.channels?.irc as
|
||||
| { accounts?: Record<string, { defaultTo?: string }>; defaultTo?: string }
|
||||
| undefined;
|
||||
const normalized = normalizeAccountId(accountId);
|
||||
const account =
|
||||
channel?.accounts?.[normalized] ??
|
||||
channel?.accounts?.[
|
||||
Object.keys(channel?.accounts ?? {}).find(
|
||||
(key) => key.toLowerCase() === normalized.toLowerCase(),
|
||||
) ?? ""
|
||||
];
|
||||
return (account?.defaultTo ?? channel?.defaultTo)?.trim() || undefined;
|
||||
},
|
||||
},
|
||||
groups: {
|
||||
resolveRequireMention: ({ cfg, accountId, groupId }) => {
|
||||
@@ -378,6 +408,20 @@ const DOCKS: Record<ChatChannelId, ChannelDock> = {
|
||||
.replace(/^users\//i, "")
|
||||
.toLowerCase(),
|
||||
),
|
||||
resolveDefaultTo: ({ cfg, accountId }) => {
|
||||
const channel = cfg.channels?.googlechat as
|
||||
| { accounts?: Record<string, { defaultTo?: string }>; defaultTo?: string }
|
||||
| undefined;
|
||||
const normalized = normalizeAccountId(accountId);
|
||||
const account =
|
||||
channel?.accounts?.[normalized] ??
|
||||
channel?.accounts?.[
|
||||
Object.keys(channel?.accounts ?? {}).find(
|
||||
(key) => key.toLowerCase() === normalized.toLowerCase(),
|
||||
) ?? ""
|
||||
];
|
||||
return (account?.defaultTo ?? channel?.defaultTo)?.trim() || undefined;
|
||||
},
|
||||
},
|
||||
groups: {
|
||||
resolveRequireMention: resolveGoogleChatGroupRequireMention,
|
||||
@@ -416,6 +460,8 @@ const DOCKS: Record<ChatChannelId, ChannelDock> = {
|
||||
);
|
||||
},
|
||||
formatAllowFrom: ({ allowFrom }) => formatLower(allowFrom),
|
||||
resolveDefaultTo: ({ cfg, accountId }) =>
|
||||
resolveSlackAccount({ cfg, accountId }).config.defaultTo?.trim() || undefined,
|
||||
},
|
||||
groups: {
|
||||
resolveRequireMention: resolveSlackGroupRequireMention,
|
||||
@@ -453,6 +499,8 @@ const DOCKS: Record<ChatChannelId, ChannelDock> = {
|
||||
.filter(Boolean)
|
||||
.map((entry) => (entry === "*" ? "*" : normalizeE164(entry.replace(/^signal:/i, ""))))
|
||||
.filter(Boolean),
|
||||
resolveDefaultTo: ({ cfg, accountId }) =>
|
||||
resolveSignalAccount({ cfg, accountId }).config.defaultTo?.trim() || undefined,
|
||||
},
|
||||
threading: {
|
||||
buildToolContext: ({ context, hasRepliedRef }) =>
|
||||
@@ -474,6 +522,8 @@ const DOCKS: Record<ChatChannelId, ChannelDock> = {
|
||||
),
|
||||
formatAllowFrom: ({ allowFrom }) =>
|
||||
allowFrom.map((entry) => String(entry).trim()).filter(Boolean),
|
||||
resolveDefaultTo: ({ cfg, accountId }) =>
|
||||
resolveIMessageAccount({ cfg, accountId }).config.defaultTo?.trim() || undefined,
|
||||
},
|
||||
groups: {
|
||||
resolveRequireMention: resolveIMessageGroupRequireMention,
|
||||
@@ -502,6 +552,7 @@ function buildDockFromPlugin(plugin: ChannelPlugin): ChannelDock {
|
||||
? {
|
||||
resolveAllowFrom: plugin.config.resolveAllowFrom,
|
||||
formatAllowFrom: plugin.config.formatAllowFrom,
|
||||
resolveDefaultTo: plugin.config.resolveDefaultTo,
|
||||
}
|
||||
: undefined,
|
||||
groups: plugin.groups,
|
||||
|
||||
@@ -63,6 +63,10 @@ export type ChannelConfigAdapter<ResolvedAccount> = {
|
||||
accountId?: string | null;
|
||||
allowFrom: Array<string | number>;
|
||||
}) => string[];
|
||||
resolveDefaultTo?: (params: {
|
||||
cfg: OpenClawConfig;
|
||||
accountId?: string | null;
|
||||
}) => string | undefined;
|
||||
};
|
||||
|
||||
export type ChannelGroupAdapter = {
|
||||
|
||||
@@ -31,6 +31,8 @@ export type ChannelDefaultsConfig = {
|
||||
export type ExtensionChannelConfig = {
|
||||
enabled?: boolean;
|
||||
allowFrom?: string | string[];
|
||||
/** Default delivery target for CLI --deliver when no explicit --reply-to is provided. */
|
||||
defaultTo?: string;
|
||||
dmPolicy?: string;
|
||||
groupPolicy?: GroupPolicy;
|
||||
accounts?: Record<string, unknown>;
|
||||
|
||||
@@ -183,6 +183,8 @@ export type DiscordAccountConfig = {
|
||||
* Legacy key: channels.discord.dm.allowFrom.
|
||||
*/
|
||||
allowFrom?: string[];
|
||||
/** Default delivery target for CLI --deliver when no explicit --reply-to is provided. */
|
||||
defaultTo?: string;
|
||||
dm?: DiscordDmConfig;
|
||||
/** New per-guild config keyed by guild id or slug. */
|
||||
guilds?: Record<string, DiscordGuildEntry>;
|
||||
|
||||
@@ -54,6 +54,8 @@ export type GoogleChatAccountConfig = {
|
||||
groupPolicy?: GroupPolicy;
|
||||
/** Optional allowlist for space senders (user ids or emails). */
|
||||
groupAllowFrom?: Array<string | number>;
|
||||
/** Default delivery target for CLI --deliver when no explicit --reply-to is provided. */
|
||||
defaultTo?: string;
|
||||
/** Per-space configuration keyed by space id or name. */
|
||||
groups?: Record<string, GoogleChatGroupConfig>;
|
||||
/** Service account JSON (inline string or object). */
|
||||
|
||||
@@ -33,6 +33,8 @@ export type IMessageAccountConfig = {
|
||||
dmPolicy?: DmPolicy;
|
||||
/** Optional allowlist for inbound handles or chat_id targets. */
|
||||
allowFrom?: Array<string | number>;
|
||||
/** Default delivery target for CLI --deliver when no explicit --reply-to is provided. */
|
||||
defaultTo?: string;
|
||||
/** Optional allowlist for group senders or chat_id targets. */
|
||||
groupAllowFrom?: Array<string | number>;
|
||||
/**
|
||||
|
||||
@@ -56,6 +56,8 @@ export type IrcAccountConfig = {
|
||||
dmPolicy?: DmPolicy;
|
||||
/** Optional allowlist for inbound DM senders. */
|
||||
allowFrom?: Array<string | number>;
|
||||
/** Default delivery target for CLI --deliver when no explicit --reply-to is provided. */
|
||||
defaultTo?: string;
|
||||
/** Optional allowlist for IRC channel senders. */
|
||||
groupAllowFrom?: Array<string | number>;
|
||||
/**
|
||||
|
||||
@@ -63,6 +63,8 @@ export type MSTeamsConfig = {
|
||||
dmPolicy?: DmPolicy;
|
||||
/** Allowlist for DM senders (AAD object IDs or UPNs). */
|
||||
allowFrom?: Array<string>;
|
||||
/** Default delivery target for CLI --deliver when no explicit --reply-to is provided. */
|
||||
defaultTo?: string;
|
||||
/** Optional allowlist for group/channel senders (AAD object IDs or UPNs). */
|
||||
groupAllowFrom?: Array<string>;
|
||||
/**
|
||||
|
||||
@@ -42,6 +42,8 @@ export type SignalAccountConfig = {
|
||||
/** Direct message access policy (default: pairing). */
|
||||
dmPolicy?: DmPolicy;
|
||||
allowFrom?: Array<string | number>;
|
||||
/** Default delivery target for CLI --deliver when no explicit --reply-to is provided. */
|
||||
defaultTo?: string;
|
||||
/** Optional allowlist for Signal group senders (E.164). */
|
||||
groupAllowFrom?: Array<string | number>;
|
||||
/**
|
||||
|
||||
@@ -160,6 +160,8 @@ export type SlackAccountConfig = {
|
||||
* Legacy key: channels.slack.dm.allowFrom.
|
||||
*/
|
||||
allowFrom?: Array<string | number>;
|
||||
/** Default delivery target for CLI --deliver when no explicit --reply-to is provided. */
|
||||
defaultTo?: string;
|
||||
dm?: SlackDmConfig;
|
||||
channels?: Record<string, SlackChannelConfig>;
|
||||
/** Heartbeat visibility settings for this channel. */
|
||||
|
||||
@@ -74,6 +74,8 @@ export type TelegramAccountConfig = {
|
||||
groups?: Record<string, TelegramGroupConfig>;
|
||||
/** DM allowlist (numeric Telegram user IDs). Onboarding can resolve @username to IDs. */
|
||||
allowFrom?: Array<string | number>;
|
||||
/** Default delivery target for CLI `--deliver` when no explicit `--reply-to` is provided. */
|
||||
defaultTo?: string | number;
|
||||
/** Optional allowlist for Telegram group senders (numeric Telegram user IDs). */
|
||||
groupAllowFrom?: Array<string | number>;
|
||||
/**
|
||||
|
||||
@@ -67,6 +67,8 @@ export type WhatsAppConfig = {
|
||||
selfChatMode?: boolean;
|
||||
/** Optional allowlist for WhatsApp direct chats (E.164). */
|
||||
allowFrom?: string[];
|
||||
/** Default delivery target for CLI `--deliver` when no explicit `--reply-to` is provided (E.164 or group JID). */
|
||||
defaultTo?: string;
|
||||
/** Optional allowlist for WhatsApp group senders (E.164). */
|
||||
groupAllowFrom?: string[];
|
||||
/**
|
||||
@@ -127,6 +129,8 @@ export type WhatsAppAccountConfig = {
|
||||
/** Same-phone setup for this account (bot uses your personal WhatsApp number). */
|
||||
selfChatMode?: boolean;
|
||||
allowFrom?: string[];
|
||||
/** Default delivery target for CLI `--deliver` when no explicit `--reply-to` is provided (E.164 or group JID). */
|
||||
defaultTo?: string;
|
||||
groupAllowFrom?: string[];
|
||||
groupPolicy?: GroupPolicy;
|
||||
/** Max group messages to keep as history context (0 disables). */
|
||||
|
||||
@@ -113,6 +113,7 @@ export const TelegramAccountSchemaBase = z
|
||||
replyToMode: ReplyToModeSchema.optional(),
|
||||
groups: z.record(z.string(), TelegramGroupSchema.optional()).optional(),
|
||||
allowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
||||
defaultTo: z.union([z.string(), z.number()]).optional(),
|
||||
groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
||||
groupPolicy: GroupPolicySchema.optional().default("allowlist"),
|
||||
historyLimit: z.number().int().min(0).optional(),
|
||||
@@ -321,6 +322,7 @@ export const DiscordAccountSchema = z
|
||||
// inheritance in multi-account setups (shallow merge works; nested dm object doesn't).
|
||||
dmPolicy: DmPolicySchema.optional(),
|
||||
allowFrom: DiscordIdListSchema.optional(),
|
||||
defaultTo: z.string().optional(),
|
||||
dm: DiscordDmSchema.optional(),
|
||||
guilds: z.record(z.string(), DiscordGuildSchema.optional()).optional(),
|
||||
heartbeat: ChannelHeartbeatVisibilitySchema,
|
||||
@@ -448,6 +450,7 @@ export const GoogleChatAccountSchema = z
|
||||
groupPolicy: GroupPolicySchema.optional().default("allowlist"),
|
||||
groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
||||
groups: z.record(z.string(), GoogleChatGroupSchema.optional()).optional(),
|
||||
defaultTo: z.string().optional(),
|
||||
serviceAccount: z.union([z.string(), z.record(z.string(), z.unknown())]).optional(),
|
||||
serviceAccountFile: z.string().optional(),
|
||||
audienceType: z.enum(["app-url", "project-number"]).optional(),
|
||||
@@ -581,6 +584,7 @@ export const SlackAccountSchema = z
|
||||
// inheritance in multi-account setups (shallow merge works; nested dm object doesn't).
|
||||
dmPolicy: DmPolicySchema.optional(),
|
||||
allowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
||||
defaultTo: z.string().optional(),
|
||||
dm: SlackDmSchema.optional(),
|
||||
channels: z.record(z.string(), SlackChannelSchema.optional()).optional(),
|
||||
heartbeat: ChannelHeartbeatVisibilitySchema,
|
||||
@@ -663,6 +667,7 @@ export const SignalAccountSchemaBase = z
|
||||
sendReadReceipts: z.boolean().optional(),
|
||||
dmPolicy: DmPolicySchema.optional().default("pairing"),
|
||||
allowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
||||
defaultTo: z.string().optional(),
|
||||
groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
||||
groupPolicy: GroupPolicySchema.optional().default("allowlist"),
|
||||
historyLimit: z.number().int().min(0).optional(),
|
||||
@@ -751,6 +756,7 @@ export const IrcAccountSchemaBase = z
|
||||
channels: z.array(z.string()).optional(),
|
||||
dmPolicy: DmPolicySchema.optional().default("pairing"),
|
||||
allowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
||||
defaultTo: z.string().optional(),
|
||||
groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
||||
groupPolicy: GroupPolicySchema.optional().default("allowlist"),
|
||||
groups: z.record(z.string(), IrcGroupSchema.optional()).optional(),
|
||||
@@ -814,6 +820,7 @@ export const IMessageAccountSchemaBase = z
|
||||
region: z.string().optional(),
|
||||
dmPolicy: DmPolicySchema.optional().default("pairing"),
|
||||
allowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
||||
defaultTo: z.string().optional(),
|
||||
groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
||||
groupPolicy: GroupPolicySchema.optional().default("allowlist"),
|
||||
historyLimit: z.number().int().min(0).optional(),
|
||||
@@ -991,6 +998,7 @@ export const MSTeamsConfigSchema = z
|
||||
.optional(),
|
||||
dmPolicy: DmPolicySchema.optional().default("pairing"),
|
||||
allowFrom: z.array(z.string()).optional(),
|
||||
defaultTo: z.string().optional(),
|
||||
groupAllowFrom: z.array(z.string()).optional(),
|
||||
groupPolicy: GroupPolicySchema.optional().default("allowlist"),
|
||||
textChunkLimit: z.number().int().positive().optional(),
|
||||
|
||||
@@ -41,6 +41,7 @@ const WhatsAppSharedSchema = z.object({
|
||||
dmPolicy: DmPolicySchema.optional().default("pairing"),
|
||||
selfChatMode: z.boolean().optional(),
|
||||
allowFrom: z.array(z.string()).optional(),
|
||||
defaultTo: z.string().optional(),
|
||||
groupAllowFrom: z.array(z.string()).optional(),
|
||||
groupPolicy: GroupPolicySchema.optional().default("allowlist"),
|
||||
historyLimit: z.number().int().min(0).optional(),
|
||||
|
||||
@@ -1,5 +1,167 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { resolveSessionDeliveryTarget } from "./targets.js";
|
||||
import { beforeEach, describe, expect, it } from "vitest";
|
||||
import { telegramPlugin } from "../../../extensions/telegram/src/channel.js";
|
||||
import { whatsappPlugin } from "../../../extensions/whatsapp/src/channel.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import { setActivePluginRegistry } from "../../plugins/runtime.js";
|
||||
import { createTestRegistry } from "../../test-utils/channel-plugins.js";
|
||||
import { resolveOutboundTarget, resolveSessionDeliveryTarget } from "./targets.js";
|
||||
|
||||
describe("resolveOutboundTarget", () => {
|
||||
beforeEach(() => {
|
||||
setActivePluginRegistry(
|
||||
createTestRegistry([
|
||||
{ pluginId: "whatsapp", plugin: whatsappPlugin, source: "test" },
|
||||
{ pluginId: "telegram", plugin: telegramPlugin, source: "test" },
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects whatsapp with empty target even when allowFrom configured", () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
channels: { whatsapp: { allowFrom: ["+1555"] } },
|
||||
};
|
||||
const res = resolveOutboundTarget({
|
||||
channel: "whatsapp",
|
||||
to: "",
|
||||
cfg,
|
||||
mode: "explicit",
|
||||
});
|
||||
expect(res.ok).toBe(false);
|
||||
if (!res.ok) {
|
||||
expect(res.error.message).toContain("WhatsApp");
|
||||
}
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "normalizes whatsapp target when provided",
|
||||
input: { channel: "whatsapp" as const, to: " (555) 123-4567 " },
|
||||
expected: { ok: true as const, to: "+5551234567" },
|
||||
},
|
||||
{
|
||||
name: "keeps whatsapp group targets",
|
||||
input: { channel: "whatsapp" as const, to: "120363401234567890@g.us" },
|
||||
expected: { ok: true as const, to: "120363401234567890@g.us" },
|
||||
},
|
||||
{
|
||||
name: "normalizes prefixed/uppercase whatsapp group targets",
|
||||
input: {
|
||||
channel: "whatsapp" as const,
|
||||
to: " WhatsApp:120363401234567890@G.US ",
|
||||
},
|
||||
expected: { ok: true as const, to: "120363401234567890@g.us" },
|
||||
},
|
||||
{
|
||||
name: "rejects whatsapp with empty target and allowFrom (no silent fallback)",
|
||||
input: { channel: "whatsapp" as const, to: "", allowFrom: ["+1555"] },
|
||||
expectedErrorIncludes: "WhatsApp",
|
||||
},
|
||||
{
|
||||
name: "rejects whatsapp with empty target and prefixed allowFrom (no silent fallback)",
|
||||
input: {
|
||||
channel: "whatsapp" as const,
|
||||
to: "",
|
||||
allowFrom: ["whatsapp:(555) 123-4567"],
|
||||
},
|
||||
expectedErrorIncludes: "WhatsApp",
|
||||
},
|
||||
{
|
||||
name: "rejects invalid whatsapp target",
|
||||
input: { channel: "whatsapp" as const, to: "wat" },
|
||||
expectedErrorIncludes: "WhatsApp",
|
||||
},
|
||||
{
|
||||
name: "rejects whatsapp without to when allowFrom missing",
|
||||
input: { channel: "whatsapp" as const, to: " " },
|
||||
expectedErrorIncludes: "WhatsApp",
|
||||
},
|
||||
{
|
||||
name: "rejects whatsapp allowFrom fallback when invalid",
|
||||
input: { channel: "whatsapp" as const, to: "", allowFrom: ["wat"] },
|
||||
expectedErrorIncludes: "WhatsApp",
|
||||
},
|
||||
])("$name", ({ input, expected, expectedErrorIncludes }) => {
|
||||
const res = resolveOutboundTarget(input);
|
||||
if (expected) {
|
||||
expect(res).toEqual(expected);
|
||||
return;
|
||||
}
|
||||
expect(res.ok).toBe(false);
|
||||
if (!res.ok) {
|
||||
expect(res.error.message).toContain(expectedErrorIncludes);
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects telegram with missing target", () => {
|
||||
const res = resolveOutboundTarget({ channel: "telegram", to: " " });
|
||||
expect(res.ok).toBe(false);
|
||||
if (!res.ok) {
|
||||
expect(res.error.message).toContain("Telegram");
|
||||
}
|
||||
});
|
||||
|
||||
it("rejects webchat delivery", () => {
|
||||
const res = resolveOutboundTarget({ channel: "webchat", to: "x" });
|
||||
expect(res.ok).toBe(false);
|
||||
if (!res.ok) {
|
||||
expect(res.error.message).toContain("WebChat");
|
||||
}
|
||||
});
|
||||
|
||||
describe("defaultTo config fallback", () => {
|
||||
it("uses whatsapp defaultTo when no explicit target is provided", () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
channels: { whatsapp: { defaultTo: "+15551234567", allowFrom: ["*"] } },
|
||||
};
|
||||
const res = resolveOutboundTarget({
|
||||
channel: "whatsapp",
|
||||
to: undefined,
|
||||
cfg,
|
||||
mode: "implicit",
|
||||
});
|
||||
expect(res).toEqual({ ok: true, to: "+15551234567" });
|
||||
});
|
||||
|
||||
it("uses telegram defaultTo when no explicit target is provided", () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
channels: { telegram: { defaultTo: "123456789" } },
|
||||
};
|
||||
const res = resolveOutboundTarget({
|
||||
channel: "telegram",
|
||||
to: "",
|
||||
cfg,
|
||||
mode: "implicit",
|
||||
});
|
||||
expect(res).toEqual({ ok: true, to: "123456789" });
|
||||
});
|
||||
|
||||
it("explicit --reply-to overrides defaultTo", () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
channels: { whatsapp: { defaultTo: "+15551234567", allowFrom: ["*"] } },
|
||||
};
|
||||
const res = resolveOutboundTarget({
|
||||
channel: "whatsapp",
|
||||
to: "+15559999999",
|
||||
cfg,
|
||||
mode: "explicit",
|
||||
});
|
||||
expect(res).toEqual({ ok: true, to: "+15559999999" });
|
||||
});
|
||||
|
||||
it("still errors when no defaultTo and no explicit target", () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
channels: { whatsapp: { allowFrom: ["+1555"] } },
|
||||
};
|
||||
const res = resolveOutboundTarget({
|
||||
channel: "whatsapp",
|
||||
to: "",
|
||||
cfg,
|
||||
mode: "implicit",
|
||||
});
|
||||
expect(res.ok).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveSessionDeliveryTarget", () => {
|
||||
it("derives implicit delivery from the last route", () => {
|
||||
|
||||
@@ -169,20 +169,29 @@ export function resolveOutboundTarget(params: {
|
||||
})
|
||||
: undefined);
|
||||
|
||||
// Fall back to per-channel defaultTo when no explicit target is provided.
|
||||
const effectiveTo =
|
||||
params.to?.trim() ||
|
||||
(params.cfg && plugin.config.resolveDefaultTo
|
||||
? plugin.config.resolveDefaultTo({
|
||||
cfg: params.cfg,
|
||||
accountId: params.accountId ?? undefined,
|
||||
})
|
||||
: undefined);
|
||||
|
||||
const resolveTarget = plugin.outbound?.resolveTarget;
|
||||
if (resolveTarget) {
|
||||
return resolveTarget({
|
||||
cfg: params.cfg,
|
||||
to: params.to,
|
||||
to: effectiveTo,
|
||||
allowFrom,
|
||||
accountId: params.accountId ?? undefined,
|
||||
mode: params.mode ?? "explicit",
|
||||
});
|
||||
}
|
||||
|
||||
const trimmed = params.to?.trim();
|
||||
if (trimmed) {
|
||||
return { ok: true, to: trimmed };
|
||||
if (effectiveTo) {
|
||||
return { ok: true, to: effectiveTo };
|
||||
}
|
||||
const hint = plugin.messaging?.targetResolver?.hint;
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user