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