Compare commits
2 Commits
main
...
vincentkoc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1587b4279a | ||
|
|
19ca0c5949 |
@ -603,15 +603,17 @@ export async function processMessage(
|
||||
if (created) {
|
||||
logVerbose(core, runtime, `bluebubbles pairing request sender=${message.senderId}`);
|
||||
try {
|
||||
await sendMessageBlueBubbles(
|
||||
message.senderId,
|
||||
core.channel.pairing.buildPairingReply({
|
||||
channel: "bluebubbles",
|
||||
idLine: `Your BlueBubbles sender id: ${message.senderId}`,
|
||||
code,
|
||||
}),
|
||||
{ cfg: config, accountId: account.accountId },
|
||||
);
|
||||
const replyText = core.channel.pairing.buildPairingReply({
|
||||
channel: "bluebubbles",
|
||||
idLine: `Your BlueBubbles sender id: ${message.senderId}`,
|
||||
code,
|
||||
});
|
||||
if (replyText) {
|
||||
await sendMessageBlueBubbles(message.senderId, replyText, {
|
||||
cfg: config,
|
||||
accountId: account.accountId,
|
||||
});
|
||||
}
|
||||
statusSink?.({ lastOutboundAt: Date.now() });
|
||||
} catch (err) {
|
||||
logVerbose(
|
||||
|
||||
@ -1108,16 +1108,19 @@ export async function handleFeishuMessage(params: {
|
||||
if (created) {
|
||||
log(`feishu[${account.accountId}]: pairing request sender=${ctx.senderOpenId}`);
|
||||
try {
|
||||
await sendMessageFeishu({
|
||||
cfg,
|
||||
to: `chat:${ctx.chatId}`,
|
||||
text: core.channel.pairing.buildPairingReply({
|
||||
channel: "feishu",
|
||||
idLine: `Your Feishu user id: ${ctx.senderOpenId}`,
|
||||
code,
|
||||
}),
|
||||
accountId: account.accountId,
|
||||
const replyText = core.channel.pairing.buildPairingReply({
|
||||
channel: "feishu",
|
||||
idLine: `Your Feishu user id: ${ctx.senderOpenId}`,
|
||||
code,
|
||||
});
|
||||
if (replyText) {
|
||||
await sendMessageFeishu({
|
||||
cfg,
|
||||
to: `chat:${ctx.chatId}`,
|
||||
text: replyText,
|
||||
accountId: account.accountId,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
log(
|
||||
`feishu[${account.accountId}]: pairing reply failed for ${ctx.senderOpenId}: ${String(err)}`,
|
||||
|
||||
@ -318,16 +318,19 @@ export async function applyGoogleChatInboundAccessPolicy(params: {
|
||||
if (created) {
|
||||
logVerbose(`googlechat pairing request sender=${senderId}`);
|
||||
try {
|
||||
await sendGoogleChatMessage({
|
||||
account,
|
||||
space: spaceId,
|
||||
text: core.channel.pairing.buildPairingReply({
|
||||
channel: "googlechat",
|
||||
idLine: `Your Google Chat user id: ${senderId}`,
|
||||
code,
|
||||
}),
|
||||
const replyText = core.channel.pairing.buildPairingReply({
|
||||
channel: "googlechat",
|
||||
idLine: `Your Google Chat user id: ${senderId}`,
|
||||
code,
|
||||
});
|
||||
statusSink?.({ lastOutboundAt: Date.now() });
|
||||
if (replyText) {
|
||||
await sendGoogleChatMessage({
|
||||
account,
|
||||
space: spaceId,
|
||||
text: replyText,
|
||||
});
|
||||
statusSink?.({ lastOutboundAt: Date.now() });
|
||||
}
|
||||
} catch (err) {
|
||||
logVerbose(`pairing reply failed for ${senderId}: ${String(err)}`);
|
||||
}
|
||||
|
||||
@ -219,13 +219,15 @@ export async function handleIrcInbound(params: {
|
||||
idLine: `Your IRC id: ${senderDisplay}`,
|
||||
code,
|
||||
});
|
||||
await deliverIrcReply({
|
||||
payload: { text: reply },
|
||||
target: message.senderNick,
|
||||
accountId: account.accountId,
|
||||
sendReply: params.sendReply,
|
||||
statusSink,
|
||||
});
|
||||
if (reply) {
|
||||
await deliverIrcReply({
|
||||
payload: { text: reply },
|
||||
target: message.senderNick,
|
||||
accountId: account.accountId,
|
||||
sendReply: params.sendReply,
|
||||
statusSink,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
runtime.error?.(`irc: pairing reply failed for ${senderDisplay}: ${String(err)}`);
|
||||
}
|
||||
|
||||
@ -1047,12 +1047,16 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {}
|
||||
id: params.payload.user_id,
|
||||
meta: { name: params.userName },
|
||||
});
|
||||
const replyText = core.channel.pairing.buildPairingReply({
|
||||
channel: "mattermost",
|
||||
idLine: `Your Mattermost user id: ${params.payload.user_id}`,
|
||||
code,
|
||||
});
|
||||
if (!replyText) {
|
||||
return { ephemeral_text: "" };
|
||||
}
|
||||
return {
|
||||
ephemeral_text: core.channel.pairing.buildPairingReply({
|
||||
channel: "mattermost",
|
||||
idLine: `Your Mattermost user id: ${params.payload.user_id}`,
|
||||
code,
|
||||
}),
|
||||
ephemeral_text: replyText,
|
||||
};
|
||||
}
|
||||
const denyText =
|
||||
@ -1316,15 +1320,16 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {}
|
||||
logVerboseMessage(`mattermost: pairing request sender=${senderId} created=${created}`);
|
||||
if (created) {
|
||||
try {
|
||||
await sendMessageMattermost(
|
||||
`user:${senderId}`,
|
||||
core.channel.pairing.buildPairingReply({
|
||||
channel: "mattermost",
|
||||
idLine: `Your Mattermost user id: ${senderId}`,
|
||||
code,
|
||||
}),
|
||||
{ accountId: account.accountId },
|
||||
);
|
||||
const replyText = core.channel.pairing.buildPairingReply({
|
||||
channel: "mattermost",
|
||||
idLine: `Your Mattermost user id: ${senderId}`,
|
||||
code,
|
||||
});
|
||||
if (replyText) {
|
||||
await sendMessageMattermost(`user:${senderId}`, replyText, {
|
||||
accountId: account.accountId,
|
||||
});
|
||||
}
|
||||
opts.statusSink?.({ lastOutboundAt: Date.now() });
|
||||
} catch (err) {
|
||||
logVerboseMessage(`mattermost: pairing reply failed for ${senderId}: ${String(err)}`);
|
||||
|
||||
@ -171,11 +171,12 @@ async function authorizeSlashInvocation(params: {
|
||||
...decision,
|
||||
denyResponse: {
|
||||
response_type: "ephemeral",
|
||||
text: core.channel.pairing.buildPairingReply({
|
||||
channel: "mattermost",
|
||||
idLine: `Your Mattermost user id: ${senderId}`,
|
||||
code,
|
||||
}),
|
||||
text:
|
||||
core.channel.pairing.buildPairingReply({
|
||||
channel: "mattermost",
|
||||
idLine: `Your Mattermost user id: ${senderId}`,
|
||||
code,
|
||||
}) ?? "",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -179,15 +179,16 @@ export async function handleNextcloudTalkInbound(params: {
|
||||
});
|
||||
if (created) {
|
||||
try {
|
||||
await sendMessageNextcloudTalk(
|
||||
roomToken,
|
||||
core.channel.pairing.buildPairingReply({
|
||||
channel: CHANNEL_ID,
|
||||
idLine: `Your Nextcloud user id: ${senderId}`,
|
||||
code,
|
||||
}),
|
||||
{ accountId: account.accountId },
|
||||
);
|
||||
const replyText = core.channel.pairing.buildPairingReply({
|
||||
channel: CHANNEL_ID,
|
||||
idLine: `Your Nextcloud user id: ${senderId}`,
|
||||
code,
|
||||
});
|
||||
if (replyText) {
|
||||
await sendMessageNextcloudTalk(roomToken, replyText, {
|
||||
accountId: account.accountId,
|
||||
});
|
||||
}
|
||||
statusSink?.({ lastOutboundAt: Date.now() });
|
||||
} catch (err) {
|
||||
runtime.error?.(`nextcloud-talk: pairing reply failed for ${senderId}: ${String(err)}`);
|
||||
|
||||
@ -422,15 +422,19 @@ async function processMessageWithPipeline(params: {
|
||||
if (created) {
|
||||
logVerbose(core, runtime, `zalo pairing request sender=${senderId}`);
|
||||
try {
|
||||
const replyText = core.channel.pairing.buildPairingReply({
|
||||
channel: "zalo",
|
||||
idLine: `Your Zalo user id: ${senderId}`,
|
||||
code,
|
||||
});
|
||||
if (!replyText) {
|
||||
return;
|
||||
}
|
||||
await sendMessage(
|
||||
token,
|
||||
{
|
||||
chat_id: chatId,
|
||||
text: core.channel.pairing.buildPairingReply({
|
||||
channel: "zalo",
|
||||
idLine: `Your Zalo user id: ${senderId}`,
|
||||
code,
|
||||
}),
|
||||
text: replyText,
|
||||
},
|
||||
fetcher,
|
||||
);
|
||||
|
||||
@ -270,15 +270,14 @@ async function processMessage(
|
||||
if (created) {
|
||||
logVerbose(core, runtime, `zalouser pairing request sender=${senderId}`);
|
||||
try {
|
||||
await sendMessageZalouser(
|
||||
chatId,
|
||||
core.channel.pairing.buildPairingReply({
|
||||
channel: "zalouser",
|
||||
idLine: `Your Zalo user id: ${senderId}`,
|
||||
code,
|
||||
}),
|
||||
{ profile: account.profile },
|
||||
);
|
||||
const replyText = core.channel.pairing.buildPairingReply({
|
||||
channel: "zalouser",
|
||||
idLine: `Your Zalo user id: ${senderId}`,
|
||||
code,
|
||||
});
|
||||
if (replyText) {
|
||||
await sendMessageZalouser(chatId, replyText, { profile: account.profile });
|
||||
}
|
||||
statusSink?.({ lastOutboundAt: Date.now() });
|
||||
} catch (err) {
|
||||
logVerbose(
|
||||
|
||||
@ -1444,6 +1444,8 @@ export const FIELD_HELP: Record<string, string> = {
|
||||
"Debounce window (ms) for batching rapid inbound messages from the same sender (0 to disable).",
|
||||
"channels.telegram.dmPolicy":
|
||||
'Direct message access control ("pairing" recommended). "open" requires channels.telegram.allowFrom=["*"].',
|
||||
"channels.telegram.unpairedResponse":
|
||||
'Response style for unknown direct-message senders in pairing mode: "silent", "code-only", or "branded" (default).',
|
||||
"channels.telegram.streaming":
|
||||
'Unified Telegram stream preview mode: "off" | "partial" | "block" | "progress" (default: "partial"). "progress" maps to "partial" on Telegram. Legacy boolean/streamMode keys are auto-mapped.',
|
||||
"channels.discord.streaming":
|
||||
@ -1478,17 +1480,27 @@ export const FIELD_HELP: Record<string, string> = {
|
||||
"Allow ACP spawns with thread=true to auto-bind Telegram current conversations when supported.",
|
||||
"channels.whatsapp.dmPolicy":
|
||||
'Direct message access control ("pairing" recommended). "open" requires channels.whatsapp.allowFrom=["*"].',
|
||||
"channels.whatsapp.unpairedResponse":
|
||||
'Response style for unknown direct-message senders in pairing mode: "silent", "code-only", or "branded" (default).',
|
||||
"channels.whatsapp.selfChatMode": "Same-phone setup (bot uses your personal WhatsApp number).",
|
||||
"channels.whatsapp.debounceMs":
|
||||
"Debounce window (ms) for batching rapid consecutive messages from the same sender (0 to disable).",
|
||||
"channels.signal.dmPolicy":
|
||||
'Direct message access control ("pairing" recommended). "open" requires channels.signal.allowFrom=["*"].',
|
||||
"channels.signal.unpairedResponse":
|
||||
'Response style for unknown direct-message senders in pairing mode: "silent", "code-only", or "branded" (default).',
|
||||
"channels.imessage.dmPolicy":
|
||||
'Direct message access control ("pairing" recommended). "open" requires channels.imessage.allowFrom=["*"].',
|
||||
"channels.imessage.unpairedResponse":
|
||||
'Response style for unknown direct-message senders in pairing mode: "silent", "code-only", or "branded" (default).',
|
||||
"channels.bluebubbles.dmPolicy":
|
||||
'Direct message access control ("pairing" recommended). "open" requires channels.bluebubbles.allowFrom=["*"].',
|
||||
"channels.bluebubbles.unpairedResponse":
|
||||
'Response style for unknown direct-message senders in pairing mode: "silent", "code-only", or "branded" (default).',
|
||||
"channels.discord.dmPolicy":
|
||||
'Direct message access control ("pairing" recommended). "open" requires channels.discord.allowFrom=["*"].',
|
||||
"channels.discord.unpairedResponse":
|
||||
'Response style for unknown direct-message senders in pairing mode: "silent", "code-only", or "branded" (default).',
|
||||
"channels.discord.dm.policy":
|
||||
'Direct message access control ("pairing" recommended). "open" requires channels.discord.allowFrom=["*"] (legacy: channels.discord.dm.allowFrom).',
|
||||
"channels.discord.retry.attempts":
|
||||
@ -1554,4 +1566,6 @@ export const FIELD_HELP: Record<string, string> = {
|
||||
'Direct message access control ("pairing" recommended). "open" requires channels.slack.allowFrom=["*"] (legacy: channels.slack.dm.allowFrom).',
|
||||
"channels.slack.dmPolicy":
|
||||
'Direct message access control ("pairing" recommended). "open" requires channels.slack.allowFrom=["*"].',
|
||||
"channels.slack.unpairedResponse":
|
||||
'Response style for unknown direct-message senders in pairing mode: "silent", "code-only", or "branded" (default).',
|
||||
};
|
||||
|
||||
@ -704,6 +704,7 @@ export const FIELD_LABELS: Record<string, string> = {
|
||||
...IRC_FIELD_LABELS,
|
||||
"channels.telegram.botToken": "Telegram Bot Token",
|
||||
"channels.telegram.dmPolicy": "Telegram DM Policy",
|
||||
"channels.telegram.unpairedResponse": "Telegram Unpaired DM Response",
|
||||
"channels.telegram.configWrites": "Telegram Config Writes",
|
||||
"channels.telegram.commands.native": "Telegram Native Commands",
|
||||
"channels.telegram.commands.nativeSkills": "Telegram Native Skill Commands",
|
||||
@ -721,17 +722,22 @@ export const FIELD_LABELS: Record<string, string> = {
|
||||
"channels.telegram.threadBindings.spawnSubagentSessions": "Telegram Thread-Bound Subagent Spawn",
|
||||
"channels.telegram.threadBindings.spawnAcpSessions": "Telegram Thread-Bound ACP Spawn",
|
||||
"channels.whatsapp.dmPolicy": "WhatsApp DM Policy",
|
||||
"channels.whatsapp.unpairedResponse": "WhatsApp Unpaired DM Response",
|
||||
"channels.whatsapp.selfChatMode": "WhatsApp Self-Phone Mode",
|
||||
"channels.whatsapp.debounceMs": "WhatsApp Message Debounce (ms)",
|
||||
"channels.whatsapp.configWrites": "WhatsApp Config Writes",
|
||||
"channels.signal.dmPolicy": "Signal DM Policy",
|
||||
"channels.signal.unpairedResponse": "Signal Unpaired DM Response",
|
||||
"channels.signal.configWrites": "Signal Config Writes",
|
||||
"channels.imessage.dmPolicy": "iMessage DM Policy",
|
||||
"channels.imessage.unpairedResponse": "iMessage Unpaired DM Response",
|
||||
"channels.imessage.configWrites": "iMessage Config Writes",
|
||||
"channels.bluebubbles.dmPolicy": "BlueBubbles DM Policy",
|
||||
"channels.bluebubbles.unpairedResponse": "BlueBubbles Unpaired DM Response",
|
||||
"channels.msteams.configWrites": "MS Teams Config Writes",
|
||||
"channels.irc.configWrites": "IRC Config Writes",
|
||||
"channels.discord.dmPolicy": "Discord DM Policy",
|
||||
"channels.discord.unpairedResponse": "Discord Unpaired DM Response",
|
||||
"channels.discord.dm.policy": "Discord DM Policy",
|
||||
"channels.discord.configWrites": "Discord Config Writes",
|
||||
"channels.discord.proxy": "Discord Proxy URL",
|
||||
@ -779,6 +785,7 @@ export const FIELD_LABELS: Record<string, string> = {
|
||||
"channels.discord.activityUrl": "Discord Presence Activity URL",
|
||||
"channels.slack.dm.policy": "Slack DM Policy",
|
||||
"channels.slack.dmPolicy": "Slack DM Policy",
|
||||
"channels.slack.unpairedResponse": "Slack Unpaired DM Response",
|
||||
"channels.slack.configWrites": "Slack Config Writes",
|
||||
"channels.slack.commands.native": "Slack Native Commands",
|
||||
"channels.slack.commands.nativeSkills": "Slack Native Skill Commands",
|
||||
|
||||
@ -7,6 +7,7 @@ export type DmScope = "main" | "per-peer" | "per-channel-peer" | "per-account-ch
|
||||
export type ReplyToMode = "off" | "first" | "all";
|
||||
export type GroupPolicy = "open" | "disabled" | "allowlist";
|
||||
export type DmPolicy = "pairing" | "allowlist" | "open" | "disabled";
|
||||
export type UnpairedResponseMode = "silent" | "code-only" | "branded";
|
||||
|
||||
export type OutboundRetryConfig = {
|
||||
/** Max retry attempts for outbound requests (default: 3). */
|
||||
|
||||
@ -3,6 +3,7 @@ import type {
|
||||
DmPolicy,
|
||||
GroupPolicy,
|
||||
MarkdownConfig,
|
||||
UnpairedResponseMode,
|
||||
} from "./types.base.js";
|
||||
import type { ChannelHeartbeatVisibilityConfig } from "./types.channels.js";
|
||||
import type { DmConfig } from "./types.messages.js";
|
||||
@ -20,6 +21,8 @@ export type CommonChannelMessagingConfig = {
|
||||
enabled?: boolean;
|
||||
/** Direct message access policy (default: pairing). */
|
||||
dmPolicy?: DmPolicy;
|
||||
/** How OpenClaw responds to unknown DM senders in pairing mode. */
|
||||
unpairedResponse?: UnpairedResponseMode;
|
||||
/** Optional allowlist for inbound DM senders. */
|
||||
allowFrom?: Array<string | number>;
|
||||
/** Default delivery target for CLI --deliver when no explicit --reply-to is provided. */
|
||||
|
||||
@ -7,6 +7,7 @@ import type {
|
||||
MarkdownConfig,
|
||||
OutboundRetryConfig,
|
||||
ReplyToMode,
|
||||
UnpairedResponseMode,
|
||||
} from "./types.base.js";
|
||||
import type { ChannelHeartbeatVisibilityConfig } from "./types.channels.js";
|
||||
import type { DmConfig, ProviderCommandsConfig } from "./types.messages.js";
|
||||
@ -219,6 +220,8 @@ export type DiscordAccountConfig = {
|
||||
configWrites?: boolean;
|
||||
/** If false, do not start this Discord account. Default: true. */
|
||||
enabled?: boolean;
|
||||
/** How OpenClaw responds to unknown DM senders in pairing mode. */
|
||||
unpairedResponse?: UnpairedResponseMode;
|
||||
token?: SecretInput;
|
||||
/** HTTP(S) proxy URL for Discord gateway WebSocket connections. */
|
||||
proxy?: string;
|
||||
|
||||
@ -3,6 +3,7 @@ import type {
|
||||
DmPolicy,
|
||||
GroupPolicy,
|
||||
MarkdownConfig,
|
||||
UnpairedResponseMode,
|
||||
} from "./types.base.js";
|
||||
import type { ChannelHeartbeatVisibilityConfig } from "./types.channels.js";
|
||||
import type { DmConfig } from "./types.messages.js";
|
||||
@ -31,6 +32,8 @@ export type IMessageAccountConfig = {
|
||||
region?: string;
|
||||
/** Direct message access policy (default: pairing). */
|
||||
dmPolicy?: DmPolicy;
|
||||
/** How OpenClaw responds to unknown DM senders in pairing mode. */
|
||||
unpairedResponse?: UnpairedResponseMode;
|
||||
/** 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. */
|
||||
|
||||
@ -4,6 +4,7 @@ import type {
|
||||
GroupPolicy,
|
||||
MarkdownConfig,
|
||||
ReplyToMode,
|
||||
UnpairedResponseMode,
|
||||
} from "./types.base.js";
|
||||
import type { ChannelHeartbeatVisibilityConfig } from "./types.channels.js";
|
||||
import type { DmConfig, ProviderCommandsConfig } from "./types.messages.js";
|
||||
@ -98,6 +99,8 @@ export type SlackAccountConfig = {
|
||||
configWrites?: boolean;
|
||||
/** If false, do not start this Slack account. Default: true. */
|
||||
enabled?: boolean;
|
||||
/** How OpenClaw responds to unknown DM senders in pairing mode. */
|
||||
unpairedResponse?: UnpairedResponseMode;
|
||||
botToken?: string;
|
||||
appToken?: string;
|
||||
userToken?: string;
|
||||
|
||||
@ -7,6 +7,7 @@ import type {
|
||||
OutboundRetryConfig,
|
||||
ReplyToMode,
|
||||
SessionThreadBindingsConfig,
|
||||
UnpairedResponseMode,
|
||||
} from "./types.base.js";
|
||||
import type { ChannelHeartbeatVisibilityConfig } from "./types.channels.js";
|
||||
import type { DmConfig, ProviderCommandsConfig } from "./types.messages.js";
|
||||
@ -74,6 +75,8 @@ export type TelegramAccountConfig = {
|
||||
* - "disabled": ignore all inbound DMs
|
||||
*/
|
||||
dmPolicy?: DmPolicy;
|
||||
/** How OpenClaw responds to unknown DM senders in pairing mode. */
|
||||
unpairedResponse?: UnpairedResponseMode;
|
||||
/** If false, do not start this Telegram account. Default: true. */
|
||||
enabled?: boolean;
|
||||
botToken?: string;
|
||||
|
||||
@ -3,6 +3,7 @@ import type {
|
||||
DmPolicy,
|
||||
GroupPolicy,
|
||||
MarkdownConfig,
|
||||
UnpairedResponseMode,
|
||||
} from "./types.base.js";
|
||||
import type { ChannelHeartbeatVisibilityConfig } from "./types.channels.js";
|
||||
import type { DmConfig } from "./types.messages.js";
|
||||
@ -40,6 +41,8 @@ type WhatsAppSharedConfig = {
|
||||
enabled?: boolean;
|
||||
/** Direct message access policy (default: pairing). */
|
||||
dmPolicy?: DmPolicy;
|
||||
/** How OpenClaw responds to unknown DM senders in pairing mode. */
|
||||
unpairedResponse?: UnpairedResponseMode;
|
||||
/** Same-phone setup (bot uses your personal WhatsApp number). */
|
||||
selfChatMode?: boolean;
|
||||
/** Optional allowlist for WhatsApp direct chats (E.164). */
|
||||
|
||||
@ -315,6 +315,8 @@ export const GroupPolicySchema = z.enum(["open", "disabled", "allowlist"]);
|
||||
|
||||
export const DmPolicySchema = z.enum(["pairing", "allowlist", "open", "disabled"]);
|
||||
|
||||
export const UnpairedResponseSchema = z.enum(["silent", "code-only", "branded"]);
|
||||
|
||||
export const BlockStreamingCoalesceSchema = z
|
||||
.object({
|
||||
minChars: z.number().int().positive().optional(),
|
||||
|
||||
@ -30,6 +30,7 @@ import {
|
||||
ReplyToModeSchema,
|
||||
RetryConfigSchema,
|
||||
TtsConfigSchema,
|
||||
UnpairedResponseSchema,
|
||||
requireAllowlistAllowFrom,
|
||||
requireOpenAllowFrom,
|
||||
} from "./zod-schema.core.js";
|
||||
@ -159,6 +160,7 @@ export const TelegramAccountSchemaBase = z
|
||||
customCommands: z.array(TelegramCustomCommandSchema).optional(),
|
||||
configWrites: z.boolean().optional(),
|
||||
dmPolicy: DmPolicySchema.optional().default("pairing"),
|
||||
unpairedResponse: UnpairedResponseSchema.optional().default("branded"),
|
||||
botToken: SecretInputSchema.optional().register(sensitive),
|
||||
tokenFile: z.string().optional(),
|
||||
replyToMode: ReplyToModeSchema.optional(),
|
||||
@ -955,6 +957,7 @@ export const SignalAccountSchemaBase = z
|
||||
ignoreStories: z.boolean().optional(),
|
||||
sendReadReceipts: z.boolean().optional(),
|
||||
dmPolicy: DmPolicySchema.optional().default("pairing"),
|
||||
unpairedResponse: UnpairedResponseSchema.optional().default("branded"),
|
||||
allowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
||||
defaultTo: z.string().optional(),
|
||||
groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
||||
@ -1075,6 +1078,7 @@ export const IrcAccountSchemaBase = z
|
||||
nickserv: IrcNickServSchema.optional(),
|
||||
channels: z.array(z.string()).optional(),
|
||||
dmPolicy: DmPolicySchema.optional().default("pairing"),
|
||||
unpairedResponse: UnpairedResponseSchema.optional().default("branded"),
|
||||
allowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
||||
defaultTo: z.string().optional(),
|
||||
groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
||||
@ -1184,6 +1188,7 @@ export const IMessageAccountSchemaBase = z
|
||||
service: z.union([z.literal("imessage"), z.literal("sms"), z.literal("auto")]).optional(),
|
||||
region: z.string().optional(),
|
||||
dmPolicy: DmPolicySchema.optional().default("pairing"),
|
||||
unpairedResponse: UnpairedResponseSchema.optional().default("branded"),
|
||||
allowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
||||
defaultTo: z.string().optional(),
|
||||
groupAllowFrom: z.array(z.union([z.string(), z.number()])).optional(),
|
||||
@ -1313,6 +1318,7 @@ export const BlueBubblesAccountSchemaBase = z
|
||||
password: SecretInputSchema.optional().register(sensitive),
|
||||
webhookPath: z.string().optional(),
|
||||
dmPolicy: DmPolicySchema.optional().default("pairing"),
|
||||
unpairedResponse: UnpairedResponseSchema.optional().default("branded"),
|
||||
allowFrom: z.array(BlueBubblesAllowFromEntry).optional(),
|
||||
groupAllowFrom: z.array(BlueBubblesAllowFromEntry).optional(),
|
||||
groupPolicy: GroupPolicySchema.optional().default("allowlist"),
|
||||
@ -1424,6 +1430,7 @@ export const MSTeamsConfigSchema = z
|
||||
.strict()
|
||||
.optional(),
|
||||
dmPolicy: DmPolicySchema.optional().default("pairing"),
|
||||
unpairedResponse: UnpairedResponseSchema.optional().default("branded"),
|
||||
allowFrom: z.array(z.string()).optional(),
|
||||
defaultTo: z.string().optional(),
|
||||
groupAllowFrom: z.array(z.string()).optional(),
|
||||
|
||||
@ -7,6 +7,7 @@ import {
|
||||
DmPolicySchema,
|
||||
GroupPolicySchema,
|
||||
MarkdownConfigSchema,
|
||||
UnpairedResponseSchema,
|
||||
} from "./zod-schema.core.js";
|
||||
|
||||
const ToolPolicyBySenderSchema = z.record(z.string(), ToolPolicySchema).optional();
|
||||
@ -40,6 +41,7 @@ const WhatsAppSharedSchema = z.object({
|
||||
messagePrefix: z.string().optional(),
|
||||
responsePrefix: z.string().optional(),
|
||||
dmPolicy: DmPolicySchema.optional().default("pairing"),
|
||||
unpairedResponse: UnpairedResponseSchema.optional().default("branded"),
|
||||
selfChatMode: z.boolean().optional(),
|
||||
allowFrom: z.array(z.string()).optional(),
|
||||
defaultTo: z.string().optional(),
|
||||
|
||||
@ -519,6 +519,7 @@ async function ensureDmComponentAuthorized(params: {
|
||||
}
|
||||
|
||||
if (dmPolicy === "pairing") {
|
||||
const unpairedResponse = ctx.discordConfig?.unpairedResponse ?? "branded";
|
||||
const { code, created } = await upsertChannelPairingRequest({
|
||||
channel: "discord",
|
||||
id: user.id,
|
||||
@ -529,14 +530,19 @@ async function ensureDmComponentAuthorized(params: {
|
||||
},
|
||||
});
|
||||
try {
|
||||
const replyText = created
|
||||
? buildPairingReply({
|
||||
channel: "discord",
|
||||
idLine: `Your Discord user id: ${user.id}`,
|
||||
code,
|
||||
mode: unpairedResponse,
|
||||
})
|
||||
: "Pairing already requested. Ask the bot owner to approve your code.";
|
||||
if (!replyText) {
|
||||
return false;
|
||||
}
|
||||
await interaction.reply({
|
||||
content: created
|
||||
? buildPairingReply({
|
||||
channel: "discord",
|
||||
idLine: `Your Discord user id: ${user.id}`,
|
||||
code,
|
||||
})
|
||||
: "Pairing already requested. Ask the bot owner to approve your code.",
|
||||
content: replyText,
|
||||
...replyOpts,
|
||||
});
|
||||
} catch {
|
||||
|
||||
@ -209,6 +209,7 @@ export async function preflightDiscordMessage(
|
||||
}
|
||||
|
||||
const dmPolicy = params.discordConfig?.dmPolicy ?? params.discordConfig?.dm?.policy ?? "pairing";
|
||||
const unpairedResponse = params.discordConfig?.unpairedResponse ?? "branded";
|
||||
const useAccessGroups = params.cfg.commands?.useAccessGroups !== false;
|
||||
const resolvedAccountId = params.accountId ?? DEFAULT_ACCOUNT_ID;
|
||||
const allowNameMatching = isDangerousNameMatchingEnabled(params.discordConfig);
|
||||
@ -251,19 +252,19 @@ export async function preflightDiscordMessage(
|
||||
`discord pairing request sender=${author.id} tag=${formatDiscordUserTag(author)} (${allowMatchMeta})`,
|
||||
);
|
||||
try {
|
||||
await sendMessageDiscord(
|
||||
`user:${author.id}`,
|
||||
buildPairingReply({
|
||||
channel: "discord",
|
||||
idLine: `Your Discord user id: ${author.id}`,
|
||||
code,
|
||||
}),
|
||||
{
|
||||
const replyText = buildPairingReply({
|
||||
channel: "discord",
|
||||
idLine: `Your Discord user id: ${author.id}`,
|
||||
code,
|
||||
mode: unpairedResponse,
|
||||
});
|
||||
if (replyText) {
|
||||
await sendMessageDiscord(`user:${author.id}`, replyText, {
|
||||
token: params.token,
|
||||
rest: params.client.rest,
|
||||
accountId: params.accountId,
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
logVerbose(`discord pairing reply failed for ${author.id}: ${String(err)}`);
|
||||
}
|
||||
|
||||
@ -1369,6 +1369,7 @@ async function dispatchDiscordCommandInteraction(params: {
|
||||
await respond("Discord DMs are disabled.");
|
||||
return;
|
||||
}
|
||||
const unpairedResponse = discordConfig?.unpairedResponse ?? "branded";
|
||||
const dmAccess = await resolveDiscordDmCommandAccess({
|
||||
accountId,
|
||||
dmPolicy,
|
||||
@ -1392,14 +1393,15 @@ async function dispatchDiscordCommandInteraction(params: {
|
||||
name: sender.name,
|
||||
},
|
||||
onPairingCreated: async (code) => {
|
||||
await respond(
|
||||
buildPairingReply({
|
||||
channel: "discord",
|
||||
idLine: `Your Discord user id: ${user.id}`,
|
||||
code,
|
||||
}),
|
||||
{ ephemeral: true },
|
||||
);
|
||||
const replyText = buildPairingReply({
|
||||
channel: "discord",
|
||||
idLine: `Your Discord user id: ${user.id}`,
|
||||
code,
|
||||
mode: unpairedResponse,
|
||||
});
|
||||
if (replyText) {
|
||||
await respond(replyText, { ephemeral: true });
|
||||
}
|
||||
},
|
||||
onUnauthorized: async () => {
|
||||
await respond("You are not authorized to use this command.", { ephemeral: true });
|
||||
|
||||
@ -300,20 +300,21 @@ export async function monitorIMessageProvider(opts: MonitorIMessageOpts = {}): P
|
||||
if (created) {
|
||||
logVerbose(`imessage pairing request sender=${decision.senderId}`);
|
||||
try {
|
||||
await sendMessageIMessage(
|
||||
sender,
|
||||
buildPairingReply({
|
||||
channel: "imessage",
|
||||
idLine: `Your iMessage sender id: ${decision.senderId}`,
|
||||
code,
|
||||
}),
|
||||
{
|
||||
client,
|
||||
maxBytes: mediaMaxBytes,
|
||||
accountId: accountInfo.accountId,
|
||||
...(chatId ? { chatId } : {}),
|
||||
},
|
||||
);
|
||||
const replyText = buildPairingReply({
|
||||
channel: "imessage",
|
||||
idLine: `Your iMessage sender id: ${decision.senderId}`,
|
||||
code,
|
||||
mode: accountInfo.config.unpairedResponse,
|
||||
});
|
||||
if (!replyText) {
|
||||
return;
|
||||
}
|
||||
await sendMessageIMessage(sender, replyText, {
|
||||
client,
|
||||
maxBytes: mediaMaxBytes,
|
||||
accountId: accountInfo.accountId,
|
||||
...(chatId ? { chatId } : {}),
|
||||
});
|
||||
} catch (err) {
|
||||
logVerbose(`imessage pairing reply failed for ${decision.senderId}: ${String(err)}`);
|
||||
}
|
||||
|
||||
@ -249,7 +249,11 @@ async function sendLinePairingReply(params: {
|
||||
channel: "line",
|
||||
idLine: `Your ${idLabel}: ${senderId}`,
|
||||
code,
|
||||
mode: context.account.config.unpairedResponse,
|
||||
});
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (replyToken) {
|
||||
await replyMessageLine(replyToken, [{ type: "text", text }], {
|
||||
|
||||
@ -8,6 +8,7 @@ import type {
|
||||
LocationMessage,
|
||||
} from "@line/bot-sdk";
|
||||
import type { BaseProbeResult } from "../channels/plugins/types.js";
|
||||
import type { UnpairedResponseMode } from "../config/types.js";
|
||||
|
||||
export type LineTokenSource = "config" | "env" | "file" | "none";
|
||||
|
||||
@ -21,6 +22,7 @@ interface LineAccountBaseConfig {
|
||||
allowFrom?: Array<string | number>;
|
||||
groupAllowFrom?: Array<string | number>;
|
||||
dmPolicy?: "open" | "allowlist" | "pairing" | "disabled";
|
||||
unpairedResponse?: UnpairedResponseMode;
|
||||
groupPolicy?: "open" | "allowlist" | "disabled";
|
||||
/** Outbound response prefix override for this account. */
|
||||
responsePrefix?: string;
|
||||
|
||||
20
src/pairing/pairing-challenge.test.ts
Normal file
20
src/pairing/pairing-challenge.test.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { issuePairingChallenge } from "./pairing-challenge.js";
|
||||
|
||||
describe("issuePairingChallenge", () => {
|
||||
it("skips sending a reply in silent mode", async () => {
|
||||
const sendPairingReply = vi.fn(async (_text: string) => {});
|
||||
|
||||
const result = await issuePairingChallenge({
|
||||
channel: "discord",
|
||||
senderId: "123",
|
||||
senderIdLine: "Your Discord user id: 123",
|
||||
responseMode: "silent",
|
||||
upsertPairingRequest: async () => ({ code: "PAIR123", created: true }),
|
||||
sendPairingReply,
|
||||
});
|
||||
|
||||
expect(result).toEqual({ created: true, code: "PAIR123" });
|
||||
expect(sendPairingReply).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@ -1,3 +1,4 @@
|
||||
import type { UnpairedResponseMode } from "../config/types.base.js";
|
||||
import { buildPairingReply } from "./pairing-messages.js";
|
||||
|
||||
type PairingMeta = Record<string, string | undefined>;
|
||||
@ -12,7 +13,8 @@ export type PairingChallengeParams = {
|
||||
meta?: PairingMeta;
|
||||
}) => Promise<{ code: string; created: boolean }>;
|
||||
sendPairingReply: (text: string) => Promise<void>;
|
||||
buildReplyText?: (params: { code: string; senderIdLine: string }) => string;
|
||||
responseMode?: UnpairedResponseMode;
|
||||
buildReplyText?: (params: { code: string; senderIdLine: string }) => string | null;
|
||||
onCreated?: (params: { code: string }) => void;
|
||||
onReplyError?: (err: unknown) => void;
|
||||
};
|
||||
@ -38,7 +40,11 @@ export async function issuePairingChallenge(
|
||||
channel: params.channel,
|
||||
idLine: params.senderIdLine,
|
||||
code,
|
||||
mode: params.responseMode,
|
||||
});
|
||||
if (replyText == null) {
|
||||
return { created: true, code };
|
||||
}
|
||||
try {
|
||||
await params.sendPairingReply(replyText);
|
||||
} catch (err) {
|
||||
|
||||
@ -50,6 +50,7 @@ describe("buildPairingReply", () => {
|
||||
for (const testCase of cases) {
|
||||
it(`formats pairing reply for ${testCase.channel}`, () => {
|
||||
const text = buildPairingReply(testCase);
|
||||
expect(text).not.toBeNull();
|
||||
expect(text).toContain(testCase.idLine);
|
||||
expect(text).toContain(`Pairing code: ${testCase.code}`);
|
||||
// CLI commands should respect OPENCLAW_PROFILE when set (most tests run with isolated profile)
|
||||
@ -59,4 +60,29 @@ describe("buildPairingReply", () => {
|
||||
expect(text).toMatch(commandRe);
|
||||
});
|
||||
}
|
||||
|
||||
it("omits branding in code-only mode", () => {
|
||||
const text = buildPairingReply({
|
||||
channel: "discord",
|
||||
idLine: "Your Discord user id: 1",
|
||||
code: "ABC123",
|
||||
mode: "code-only",
|
||||
});
|
||||
|
||||
expect(text).not.toBeNull();
|
||||
expect(text).not.toContain("OpenClaw: access not configured.");
|
||||
expect(text).toContain("Your Discord user id: 1");
|
||||
expect(text).toContain("Pairing code: ABC123");
|
||||
});
|
||||
|
||||
it("returns null in silent mode", () => {
|
||||
const text = buildPairingReply({
|
||||
channel: "discord",
|
||||
idLine: "Your Discord user id: 1",
|
||||
code: "ABC123",
|
||||
mode: "silent",
|
||||
});
|
||||
|
||||
expect(text).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,20 +1,36 @@
|
||||
import { formatCliCommand } from "../cli/command-format.js";
|
||||
import type { UnpairedResponseMode } from "../config/types.base.js";
|
||||
import type { PairingChannel } from "./pairing-store.js";
|
||||
|
||||
export function buildPairingReply(params: {
|
||||
channel: PairingChannel;
|
||||
idLine: string;
|
||||
code: string;
|
||||
}): string {
|
||||
const { channel, idLine, code } = params;
|
||||
return [
|
||||
"OpenClaw: access not configured.",
|
||||
"",
|
||||
idLine,
|
||||
"",
|
||||
`Pairing code: ${code}`,
|
||||
"",
|
||||
"Ask the bot owner to approve with:",
|
||||
formatCliCommand(`openclaw pairing approve ${channel} ${code}`),
|
||||
].join("\n");
|
||||
mode?: UnpairedResponseMode;
|
||||
}): string | null {
|
||||
const { channel, idLine, code, mode = "branded" } = params;
|
||||
if (mode === "silent") {
|
||||
return null;
|
||||
}
|
||||
const lines =
|
||||
mode === "code-only"
|
||||
? [
|
||||
idLine,
|
||||
"",
|
||||
`Pairing code: ${code}`,
|
||||
"",
|
||||
"Ask the bot owner to approve with:",
|
||||
formatCliCommand(`openclaw pairing approve ${channel} ${code}`),
|
||||
]
|
||||
: [
|
||||
"OpenClaw: access not configured.",
|
||||
"",
|
||||
idLine,
|
||||
"",
|
||||
`Pairing code: ${code}`,
|
||||
"",
|
||||
"Ask the bot owner to approve with:",
|
||||
formatCliCommand(`openclaw pairing approve ${channel} ${code}`),
|
||||
];
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
@ -430,6 +430,7 @@ export async function monitorSignalProvider(opts: MonitorSignalOpts = {}): Promi
|
||||
groupHistories,
|
||||
textLimit,
|
||||
dmPolicy,
|
||||
unpairedResponse: accountInfo.config.unpairedResponse,
|
||||
allowFrom,
|
||||
groupAllowFrom,
|
||||
groupPolicy,
|
||||
|
||||
@ -8,6 +8,7 @@ import { isSignalSenderAllowed, type SignalSender } from "../identity.js";
|
||||
|
||||
type SignalDmPolicy = "open" | "pairing" | "allowlist" | "disabled";
|
||||
type SignalGroupPolicy = "open" | "allowlist" | "disabled";
|
||||
type SignalUnpairedResponseMode = "silent" | "code-only" | "branded";
|
||||
|
||||
export async function resolveSignalAccessState(params: {
|
||||
accountId: string;
|
||||
@ -49,6 +50,7 @@ export async function handleSignalDirectMessageAccess(params: {
|
||||
senderDisplay: string;
|
||||
senderName?: string;
|
||||
accountId: string;
|
||||
unpairedResponse?: SignalUnpairedResponseMode;
|
||||
sendPairingReply: (text: string) => Promise<void>;
|
||||
log: (message: string) => void;
|
||||
}): Promise<boolean> {
|
||||
@ -66,6 +68,7 @@ export async function handleSignalDirectMessageAccess(params: {
|
||||
channel: "signal",
|
||||
senderId: params.senderId,
|
||||
senderIdLine: params.senderIdLine,
|
||||
responseMode: params.unpairedResponse,
|
||||
meta: { name: params.senderName },
|
||||
upsertPairingRequest: async ({ id, meta }) =>
|
||||
await upsertChannelPairingRequest({
|
||||
|
||||
@ -530,6 +530,7 @@ export function createSignalEventHandler(deps: SignalEventHandlerDeps) {
|
||||
senderDisplay,
|
||||
senderName: envelope.sourceName ?? undefined,
|
||||
accountId: deps.accountId,
|
||||
unpairedResponse: deps.unpairedResponse,
|
||||
sendPairingReply: async (text) => {
|
||||
await sendMessageSignal(`signal:${senderRecipient}`, text, {
|
||||
baseUrl: deps.baseUrl,
|
||||
|
||||
@ -1,7 +1,12 @@
|
||||
import type { HistoryEntry } from "../../auto-reply/reply/history.js";
|
||||
import type { ReplyPayload } from "../../auto-reply/types.js";
|
||||
import type { OpenClawConfig } from "../../config/config.js";
|
||||
import type { DmPolicy, GroupPolicy, SignalReactionNotificationMode } from "../../config/types.js";
|
||||
import type {
|
||||
DmPolicy,
|
||||
GroupPolicy,
|
||||
SignalReactionNotificationMode,
|
||||
UnpairedResponseMode,
|
||||
} from "../../config/types.js";
|
||||
import type { RuntimeEnv } from "../../runtime.js";
|
||||
import type { SignalSender } from "../identity.js";
|
||||
|
||||
@ -79,6 +84,7 @@ export type SignalEventHandlerDeps = {
|
||||
groupHistories: Map<string, HistoryEntry[]>;
|
||||
textLimit: number;
|
||||
dmPolicy: DmPolicy;
|
||||
unpairedResponse?: UnpairedResponseMode;
|
||||
allowFrom: string[];
|
||||
groupAllowFrom: string[];
|
||||
groupPolicy: GroupPolicy;
|
||||
|
||||
@ -3,7 +3,7 @@ import type { HistoryEntry } from "../../auto-reply/reply/history.js";
|
||||
import { formatAllowlistMatchMeta } from "../../channels/allowlist-match.js";
|
||||
import type { OpenClawConfig, SlackReactionNotificationMode } from "../../config/config.js";
|
||||
import { resolveSessionKey, type SessionScope } from "../../config/sessions.js";
|
||||
import type { DmPolicy, GroupPolicy } from "../../config/types.js";
|
||||
import type { DmPolicy, GroupPolicy, UnpairedResponseMode } from "../../config/types.js";
|
||||
import { logVerbose } from "../../globals.js";
|
||||
import { createDedupeCache } from "../../infra/dedupe.js";
|
||||
import { getChildLogger } from "../../logging.js";
|
||||
@ -36,6 +36,7 @@ export type SlackMonitorContext = {
|
||||
|
||||
dmEnabled: boolean;
|
||||
dmPolicy: DmPolicy;
|
||||
unpairedResponse: UnpairedResponseMode;
|
||||
allowFrom: string[];
|
||||
allowNameMatching: boolean;
|
||||
groupDmEnabled: boolean;
|
||||
@ -101,6 +102,7 @@ export function createSlackMonitorContext(params: {
|
||||
|
||||
dmEnabled: boolean;
|
||||
dmPolicy: DmPolicy;
|
||||
unpairedResponse?: UnpairedResponseMode;
|
||||
allowFrom: Array<string | number> | undefined;
|
||||
allowNameMatching: boolean;
|
||||
groupDmEnabled: boolean;
|
||||
@ -399,6 +401,7 @@ export function createSlackMonitorContext(params: {
|
||||
mainKey: params.mainKey,
|
||||
dmEnabled: params.dmEnabled,
|
||||
dmPolicy: params.dmPolicy,
|
||||
unpairedResponse: params.unpairedResponse ?? "branded",
|
||||
allowFrom,
|
||||
allowNameMatching: params.allowNameMatching,
|
||||
groupDmEnabled: params.groupDmEnabled,
|
||||
|
||||
@ -41,6 +41,7 @@ export async function authorizeSlackDirectMessage(params: {
|
||||
channel: "slack",
|
||||
senderId: params.senderId,
|
||||
senderIdLine: `Your Slack user id: ${params.senderId}`,
|
||||
responseMode: params.ctx.unpairedResponse,
|
||||
meta: { name: senderName },
|
||||
upsertPairingRequest: async ({ id, meta }) =>
|
||||
await upsertChannelPairingRequest({
|
||||
|
||||
@ -247,6 +247,7 @@ export async function monitorSlackProvider(opts: MonitorSlackOpts = {}) {
|
||||
mainKey,
|
||||
dmEnabled,
|
||||
dmPolicy,
|
||||
unpairedResponse: slackCfg.unpairedResponse,
|
||||
allowFrom,
|
||||
allowNameMatching: isDangerousNameMatchingEnabled(slackCfg),
|
||||
groupDmEnabled,
|
||||
|
||||
@ -1447,6 +1447,7 @@ export const registerTelegramHandlers = ({
|
||||
const dmAuthorized = await enforceTelegramDmAccess({
|
||||
isGroup: event.isGroup,
|
||||
dmPolicy,
|
||||
unpairedResponse: telegramCfg.unpairedResponse,
|
||||
msg: event.msg,
|
||||
chatId: event.chatId,
|
||||
effectiveDmAllow,
|
||||
|
||||
@ -36,6 +36,7 @@ import type {
|
||||
TelegramDirectConfig,
|
||||
TelegramGroupConfig,
|
||||
TelegramTopicConfig,
|
||||
UnpairedResponseMode,
|
||||
} from "../config/types.js";
|
||||
import { logVerbose, shouldLogVerbose } from "../globals.js";
|
||||
import { recordChannelActivity } from "../infra/channel-activity.js";
|
||||
@ -120,6 +121,7 @@ export type BuildTelegramMessageContextParams = {
|
||||
historyLimit: number;
|
||||
groupHistories: Map<string, HistoryEntry[]>;
|
||||
dmPolicy: DmPolicy;
|
||||
unpairedResponse?: UnpairedResponseMode;
|
||||
allowFrom?: Array<string | number>;
|
||||
groupAllowFrom?: Array<string | number>;
|
||||
ackReactionScope: "off" | "none" | "group-mentions" | "group-all" | "direct" | "all";
|
||||
@ -163,6 +165,7 @@ export const buildTelegramMessageContext = async ({
|
||||
historyLimit,
|
||||
groupHistories,
|
||||
dmPolicy,
|
||||
unpairedResponse,
|
||||
allowFrom,
|
||||
groupAllowFrom,
|
||||
ackReactionScope,
|
||||
@ -301,6 +304,7 @@ export const buildTelegramMessageContext = async ({
|
||||
!(await enforceTelegramDmAccess({
|
||||
isGroup,
|
||||
dmPolicy: effectiveDmPolicy,
|
||||
unpairedResponse,
|
||||
msg,
|
||||
chatId,
|
||||
effectiveDmAllow,
|
||||
|
||||
@ -66,6 +66,7 @@ export const createTelegramMessageProcessor = (deps: TelegramMessageProcessorDep
|
||||
historyLimit,
|
||||
groupHistories,
|
||||
dmPolicy,
|
||||
unpairedResponse: telegramCfg.unpairedResponse,
|
||||
allowFrom,
|
||||
groupAllowFrom,
|
||||
ackReactionScope,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { Message } from "@grammyjs/types";
|
||||
import type { Bot } from "grammy";
|
||||
import type { DmPolicy } from "../config/types.js";
|
||||
import type { DmPolicy, UnpairedResponseMode } from "../config/types.js";
|
||||
import { logVerbose } from "../globals.js";
|
||||
import { buildPairingReply } from "../pairing/pairing-messages.js";
|
||||
import { upsertChannelPairingRequest } from "../pairing/pairing-store.js";
|
||||
@ -34,6 +34,7 @@ function resolveTelegramSenderIdentity(msg: Message, chatId: number): TelegramSe
|
||||
export async function enforceTelegramDmAccess(params: {
|
||||
isGroup: boolean;
|
||||
dmPolicy: DmPolicy;
|
||||
unpairedResponse?: UnpairedResponseMode;
|
||||
msg: Message;
|
||||
chatId: number;
|
||||
effectiveDmAllow: NormalizedAllowFrom;
|
||||
@ -93,18 +94,18 @@ export async function enforceTelegramDmAccess(params: {
|
||||
},
|
||||
"telegram pairing request",
|
||||
);
|
||||
await withTelegramApiErrorLogging({
|
||||
operation: "sendMessage",
|
||||
fn: () =>
|
||||
bot.api.sendMessage(
|
||||
chatId,
|
||||
buildPairingReply({
|
||||
channel: "telegram",
|
||||
idLine: `Your Telegram user id: ${telegramUserId}`,
|
||||
code,
|
||||
}),
|
||||
),
|
||||
const replyText = buildPairingReply({
|
||||
channel: "telegram",
|
||||
idLine: `Your Telegram user id: ${telegramUserId}`,
|
||||
code,
|
||||
mode: params.unpairedResponse,
|
||||
});
|
||||
if (replyText) {
|
||||
await withTelegramApiErrorLogging({
|
||||
operation: "sendMessage",
|
||||
fn: () => bot.api.sendMessage(chatId, replyText),
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logVerbose(`telegram pairing reply failed for chat ${chatId}: ${String(err)}`);
|
||||
|
||||
@ -3,7 +3,12 @@ import path from "node:path";
|
||||
import { createAccountListHelpers } from "../channels/plugins/account-helpers.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { resolveOAuthDir } from "../config/paths.js";
|
||||
import type { DmPolicy, GroupPolicy, WhatsAppAccountConfig } from "../config/types.js";
|
||||
import type {
|
||||
DmPolicy,
|
||||
GroupPolicy,
|
||||
UnpairedResponseMode,
|
||||
WhatsAppAccountConfig,
|
||||
} from "../config/types.js";
|
||||
import { resolveAccountEntry } from "../routing/account-lookup.js";
|
||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.js";
|
||||
import { resolveUserPath } from "../utils.js";
|
||||
@ -22,6 +27,7 @@ export type ResolvedWhatsAppAccount = {
|
||||
groupAllowFrom?: string[];
|
||||
groupPolicy?: GroupPolicy;
|
||||
dmPolicy?: DmPolicy;
|
||||
unpairedResponse?: UnpairedResponseMode;
|
||||
textChunkLimit?: number;
|
||||
chunkMode?: "length" | "newline";
|
||||
mediaMaxMb?: number;
|
||||
@ -136,6 +142,7 @@ export function resolveWhatsAppAccount(params: {
|
||||
isLegacyAuthDir: isLegacy,
|
||||
selfChatMode: accountCfg?.selfChatMode ?? rootCfg?.selfChatMode,
|
||||
dmPolicy: accountCfg?.dmPolicy ?? rootCfg?.dmPolicy,
|
||||
unpairedResponse: accountCfg?.unpairedResponse ?? rootCfg?.unpairedResponse,
|
||||
allowFrom: accountCfg?.allowFrom ?? rootCfg?.allowFrom,
|
||||
groupAllowFrom: accountCfg?.groupAllowFrom ?? rootCfg?.groupAllowFrom,
|
||||
groupPolicy: accountCfg?.groupPolicy ?? rootCfg?.groupPolicy,
|
||||
|
||||
@ -60,6 +60,7 @@ export async function checkInboundAccessControl(params: {
|
||||
accountId: params.accountId,
|
||||
});
|
||||
const dmPolicy = account.dmPolicy ?? "pairing";
|
||||
const unpairedResponse = account.unpairedResponse ?? "branded";
|
||||
const configuredAllowFrom = account.allowFrom ?? [];
|
||||
const storeAllowFrom = await readStoreAllowFromForDmPolicy({
|
||||
provider: "whatsapp",
|
||||
@ -182,13 +183,17 @@ export async function checkInboundAccessControl(params: {
|
||||
`whatsapp pairing request sender=${candidate} name=${params.pushName ?? "unknown"}`,
|
||||
);
|
||||
try {
|
||||
await params.sock.sendMessage(params.remoteJid, {
|
||||
text: buildPairingReply({
|
||||
channel: "whatsapp",
|
||||
idLine: `Your WhatsApp phone number: ${candidate}`,
|
||||
code,
|
||||
}),
|
||||
const replyText = buildPairingReply({
|
||||
channel: "whatsapp",
|
||||
idLine: `Your WhatsApp phone number: ${candidate}`,
|
||||
code,
|
||||
mode: unpairedResponse,
|
||||
});
|
||||
if (replyText) {
|
||||
await params.sock.sendMessage(params.remoteJid, {
|
||||
text: replyText,
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
logVerbose(`whatsapp pairing reply failed for ${candidate}: ${String(err)}`);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user