chore: migrate to oxlint and oxfmt
Co-authored-by: Christoph Nakazawa <christoph.pojer@gmail.com>
This commit is contained in:
@@ -1,9 +1,6 @@
|
||||
import { createActionGate } from "../../../agents/tools/common.js";
|
||||
import { listEnabledDiscordAccounts } from "../../../discord/accounts.js";
|
||||
import type {
|
||||
ChannelMessageActionAdapter,
|
||||
ChannelMessageActionName,
|
||||
} from "../types.js";
|
||||
import type { ChannelMessageActionAdapter, ChannelMessageActionName } from "../types.js";
|
||||
import { handleDiscordMessageAction } from "./discord/handle-action.js";
|
||||
|
||||
export const discordMessageActions: ChannelMessageActionAdapter = {
|
||||
@@ -78,8 +75,7 @@ export const discordMessageActions: ChannelMessageActionAdapter = {
|
||||
return to ? { to } : null;
|
||||
}
|
||||
if (action === "threadReply") {
|
||||
const channelId =
|
||||
typeof args.channelId === "string" ? args.channelId.trim() : "";
|
||||
const channelId = typeof args.channelId === "string" ? args.channelId.trim() : "";
|
||||
return channelId ? { to: `channel:${channelId}` } : null;
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -12,9 +12,7 @@ type Ctx = Pick<ChannelMessageActionContext, "action" | "params" | "cfg">;
|
||||
export async function tryHandleDiscordMessageActionGuildAdmin(params: {
|
||||
ctx: Ctx;
|
||||
resolveChannelId: () => string;
|
||||
readParentIdParam: (
|
||||
params: Record<string, unknown>,
|
||||
) => string | null | undefined;
|
||||
readParentIdParam: (params: Record<string, unknown>) => string | null | undefined;
|
||||
}): Promise<AgentToolResult<unknown> | undefined> {
|
||||
const { ctx, resolveChannelId, readParentIdParam } = params;
|
||||
const { action, params: actionParams, cfg } = ctx;
|
||||
@@ -24,10 +22,7 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
|
||||
const guildId = readStringParam(actionParams, "guildId", {
|
||||
required: true,
|
||||
});
|
||||
return await handleDiscordAction(
|
||||
{ action: "memberInfo", guildId, userId },
|
||||
cfg,
|
||||
);
|
||||
return await handleDiscordAction({ action: "memberInfo", guildId, userId }, cfg);
|
||||
}
|
||||
|
||||
if (action === "role-info") {
|
||||
@@ -125,8 +120,7 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
|
||||
const position = readNumberParam(actionParams, "position", {
|
||||
integer: true,
|
||||
});
|
||||
const nsfw =
|
||||
typeof actionParams.nsfw === "boolean" ? actionParams.nsfw : undefined;
|
||||
const nsfw = typeof actionParams.nsfw === "boolean" ? actionParams.nsfw : undefined;
|
||||
return await handleDiscordAction(
|
||||
{
|
||||
action: "channelCreate",
|
||||
@@ -152,8 +146,7 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
|
||||
integer: true,
|
||||
});
|
||||
const parentId = readParentIdParam(actionParams);
|
||||
const nsfw =
|
||||
typeof actionParams.nsfw === "boolean" ? actionParams.nsfw : undefined;
|
||||
const nsfw = typeof actionParams.nsfw === "boolean" ? actionParams.nsfw : undefined;
|
||||
const rateLimitPerUser = readNumberParam(actionParams, "rateLimitPerUser", {
|
||||
integer: true,
|
||||
});
|
||||
@@ -176,10 +169,7 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
|
||||
const channelId = readStringParam(actionParams, "channelId", {
|
||||
required: true,
|
||||
});
|
||||
return await handleDiscordAction(
|
||||
{ action: "channelDelete", channelId },
|
||||
cfg,
|
||||
);
|
||||
return await handleDiscordAction({ action: "channelDelete", channelId }, cfg);
|
||||
}
|
||||
|
||||
if (action === "channel-move") {
|
||||
@@ -247,10 +237,7 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
|
||||
const categoryId = readStringParam(actionParams, "categoryId", {
|
||||
required: true,
|
||||
});
|
||||
return await handleDiscordAction(
|
||||
{ action: "categoryDelete", categoryId },
|
||||
cfg,
|
||||
);
|
||||
return await handleDiscordAction({ action: "categoryDelete", categoryId }, cfg);
|
||||
}
|
||||
|
||||
if (action === "voice-status") {
|
||||
@@ -258,10 +245,7 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
|
||||
required: true,
|
||||
});
|
||||
const userId = readStringParam(actionParams, "userId", { required: true });
|
||||
return await handleDiscordAction(
|
||||
{ action: "voiceStatus", guildId, userId },
|
||||
cfg,
|
||||
);
|
||||
return await handleDiscordAction({ action: "voiceStatus", guildId, userId }, cfg);
|
||||
}
|
||||
|
||||
if (action === "event-list") {
|
||||
@@ -335,9 +319,7 @@ export async function tryHandleDiscordMessageActionGuildAdmin(params: {
|
||||
});
|
||||
const channelId = readStringParam(actionParams, "channelId");
|
||||
const includeArchived =
|
||||
typeof actionParams.includeArchived === "boolean"
|
||||
? actionParams.includeArchived
|
||||
: undefined;
|
||||
typeof actionParams.includeArchived === "boolean" ? actionParams.includeArchived : undefined;
|
||||
const before = readStringParam(actionParams, "before");
|
||||
const limit = readNumberParam(actionParams, "limit", { integer: true });
|
||||
return await handleDiscordAction(
|
||||
|
||||
@@ -10,9 +10,7 @@ import { tryHandleDiscordMessageActionGuildAdmin } from "./handle-action.guild-a
|
||||
|
||||
const providerId = "discord";
|
||||
|
||||
function readParentIdParam(
|
||||
params: Record<string, unknown>,
|
||||
): string | null | undefined {
|
||||
function readParentIdParam(params: Record<string, unknown>): string | null | undefined {
|
||||
if (params.clearParent === true) return null;
|
||||
if (params.parentId === null) return null;
|
||||
return readStringParam(params, "parentId");
|
||||
@@ -24,8 +22,7 @@ export async function handleDiscordMessageAction(
|
||||
const { action, params, cfg } = ctx;
|
||||
|
||||
const resolveChannelId = () =>
|
||||
readStringParam(params, "channelId") ??
|
||||
readStringParam(params, "to", { required: true });
|
||||
readStringParam(params, "channelId") ?? readStringParam(params, "to", { required: true });
|
||||
|
||||
if (action === "send") {
|
||||
const to = readStringParam(params, "to", { required: true });
|
||||
@@ -52,10 +49,8 @@ export async function handleDiscordMessageAction(
|
||||
const question = readStringParam(params, "pollQuestion", {
|
||||
required: true,
|
||||
});
|
||||
const answers =
|
||||
readStringArrayParam(params, "pollOption", { required: true }) ?? [];
|
||||
const allowMultiselect =
|
||||
typeof params.pollMulti === "boolean" ? params.pollMulti : undefined;
|
||||
const answers = readStringArrayParam(params, "pollOption", { required: true }) ?? [];
|
||||
const allowMultiselect = typeof params.pollMulti === "boolean" ? params.pollMulti : undefined;
|
||||
const durationHours = readNumberParam(params, "pollDurationHours", {
|
||||
integer: true,
|
||||
});
|
||||
@@ -76,8 +71,7 @@ export async function handleDiscordMessageAction(
|
||||
if (action === "react") {
|
||||
const messageId = readStringParam(params, "messageId", { required: true });
|
||||
const emoji = readStringParam(params, "emoji", { allowEmpty: true });
|
||||
const remove =
|
||||
typeof params.remove === "boolean" ? params.remove : undefined;
|
||||
const remove = typeof params.remove === "boolean" ? params.remove : undefined;
|
||||
return await handleDiscordAction(
|
||||
{
|
||||
action: "react",
|
||||
@@ -138,17 +132,10 @@ export async function handleDiscordMessageAction(
|
||||
|
||||
if (action === "pin" || action === "unpin" || action === "list-pins") {
|
||||
const messageId =
|
||||
action === "list-pins"
|
||||
? undefined
|
||||
: readStringParam(params, "messageId", { required: true });
|
||||
action === "list-pins" ? undefined : readStringParam(params, "messageId", { required: true });
|
||||
return await handleDiscordAction(
|
||||
{
|
||||
action:
|
||||
action === "pin"
|
||||
? "pinMessage"
|
||||
: action === "unpin"
|
||||
? "unpinMessage"
|
||||
: "listPins",
|
||||
action: action === "pin" ? "pinMessage" : action === "unpin" ? "unpinMessage" : "listPins",
|
||||
channelId: resolveChannelId(),
|
||||
messageId,
|
||||
},
|
||||
@@ -157,10 +144,7 @@ export async function handleDiscordMessageAction(
|
||||
}
|
||||
|
||||
if (action === "permissions") {
|
||||
return await handleDiscordAction(
|
||||
{ action: "permissions", channelId: resolveChannelId() },
|
||||
cfg,
|
||||
);
|
||||
return await handleDiscordAction({ action: "permissions", channelId: resolveChannelId() }, cfg);
|
||||
}
|
||||
|
||||
if (action === "thread-create") {
|
||||
@@ -205,7 +189,5 @@ export async function handleDiscordMessageAction(
|
||||
});
|
||||
if (adminResult !== undefined) return adminResult;
|
||||
|
||||
throw new Error(
|
||||
`Action ${String(action)} is not supported for provider ${providerId}.`,
|
||||
);
|
||||
throw new Error(`Action ${String(action)} is not supported for provider ${providerId}.`);
|
||||
}
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
import {
|
||||
createActionGate,
|
||||
readStringParam,
|
||||
} from "../../../agents/tools/common.js";
|
||||
import { createActionGate, readStringParam } from "../../../agents/tools/common.js";
|
||||
import { handleTelegramAction } from "../../../agents/tools/telegram-actions.js";
|
||||
import type { ClawdbotConfig } from "../../../config/config.js";
|
||||
import { listEnabledTelegramAccounts } from "../../../telegram/accounts.js";
|
||||
import type {
|
||||
ChannelMessageActionAdapter,
|
||||
ChannelMessageActionName,
|
||||
} from "../types.js";
|
||||
import type { ChannelMessageActionAdapter, ChannelMessageActionName } from "../types.js";
|
||||
|
||||
const providerId = "telegram";
|
||||
|
||||
@@ -49,8 +43,7 @@ export const telegramMessageActions: ChannelMessageActionAdapter = {
|
||||
if (action !== "sendMessage") return null;
|
||||
const to = typeof args.to === "string" ? args.to : undefined;
|
||||
if (!to) return null;
|
||||
const accountId =
|
||||
typeof args.accountId === "string" ? args.accountId.trim() : undefined;
|
||||
const accountId = typeof args.accountId === "string" ? args.accountId.trim() : undefined;
|
||||
return { to, accountId };
|
||||
},
|
||||
handleAction: async ({ action, params, cfg, accountId }) => {
|
||||
@@ -84,14 +77,12 @@ export const telegramMessageActions: ChannelMessageActionAdapter = {
|
||||
required: true,
|
||||
});
|
||||
const emoji = readStringParam(params, "emoji", { allowEmpty: true });
|
||||
const remove =
|
||||
typeof params.remove === "boolean" ? params.remove : undefined;
|
||||
const remove = typeof params.remove === "boolean" ? params.remove : undefined;
|
||||
return await handleTelegramAction(
|
||||
{
|
||||
action: "react",
|
||||
chatId:
|
||||
readStringParam(params, "chatId") ??
|
||||
readStringParam(params, "to", { required: true }),
|
||||
readStringParam(params, "chatId") ?? readStringParam(params, "to", { required: true }),
|
||||
messageId,
|
||||
emoji,
|
||||
remove,
|
||||
@@ -101,8 +92,6 @@ export const telegramMessageActions: ChannelMessageActionAdapter = {
|
||||
);
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Action ${action} is not supported for provider ${providerId}.`,
|
||||
);
|
||||
throw new Error(`Action ${action} is not supported for provider ${providerId}.`);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -5,8 +5,7 @@ export function createWhatsAppLoginTool(): ChannelAgentTool {
|
||||
return {
|
||||
label: "WhatsApp Login",
|
||||
name: "whatsapp_login",
|
||||
description:
|
||||
"Generate a WhatsApp QR code for linking, or wait for the scan to complete.",
|
||||
description: "Generate a WhatsApp QR code for linking, or wait for the scan to complete.",
|
||||
// NOTE: Using Type.Unsafe for action enum instead of Type.Union([Type.Literal(...)]
|
||||
// because Claude API on Vertex AI rejects nested anyOf schemas as invalid JSON Schema.
|
||||
parameters: Type.Object({
|
||||
@@ -18,9 +17,7 @@ export function createWhatsAppLoginTool(): ChannelAgentTool {
|
||||
force: Type.Optional(Type.Boolean()),
|
||||
}),
|
||||
execute: async (_toolCallId, args) => {
|
||||
const { startWebLoginWithQr, waitForWebLogin } = await import(
|
||||
"../../../web/login-qr.js"
|
||||
);
|
||||
const { startWebLoginWithQr, waitForWebLogin } = await import("../../../web/login-qr.js");
|
||||
const action = (args as { action?: string })?.action ?? "start";
|
||||
if (action === "wait") {
|
||||
const result = await waitForWebLogin({
|
||||
|
||||
@@ -17,11 +17,7 @@ export function setAccountEnabledInConfigSection(params: {
|
||||
const channels = params.cfg.channels as Record<string, unknown> | undefined;
|
||||
const base = channels?.[params.sectionKey] as ChannelSection | undefined;
|
||||
const hasAccounts = Boolean(base?.accounts);
|
||||
if (
|
||||
params.allowTopLevel &&
|
||||
accountKey === DEFAULT_ACCOUNT_ID &&
|
||||
!hasAccounts
|
||||
) {
|
||||
if (params.allowTopLevel && accountKey === DEFAULT_ACCOUNT_ID && !hasAccounts) {
|
||||
return {
|
||||
...params.cfg,
|
||||
channels: {
|
||||
@@ -34,10 +30,7 @@ export function setAccountEnabledInConfigSection(params: {
|
||||
} as ClawdbotConfig;
|
||||
}
|
||||
|
||||
const baseAccounts = (base?.accounts ?? {}) as Record<
|
||||
string,
|
||||
Record<string, unknown>
|
||||
>;
|
||||
const baseAccounts = (base?.accounts ?? {}) as Record<string, Record<string, unknown>>;
|
||||
const existing = baseAccounts[accountKey] ?? {};
|
||||
return {
|
||||
...params.cfg,
|
||||
@@ -69,9 +62,7 @@ export function deleteAccountFromConfigSection(params: {
|
||||
if (!base) return params.cfg;
|
||||
|
||||
const baseAccounts =
|
||||
base.accounts && typeof base.accounts === "object"
|
||||
? { ...base.accounts }
|
||||
: undefined;
|
||||
base.accounts && typeof base.accounts === "object" ? { ...base.accounts } : undefined;
|
||||
|
||||
if (accountKey !== DEFAULT_ACCOUNT_ID) {
|
||||
const accounts = baseAccounts ? { ...baseAccounts } : {};
|
||||
|
||||
@@ -11,10 +11,7 @@ import {
|
||||
import { probeDiscord } from "../../discord/probe.js";
|
||||
import { sendMessageDiscord, sendPollDiscord } from "../../discord/send.js";
|
||||
import { shouldLogVerbose } from "../../globals.js";
|
||||
import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
normalizeAccountId,
|
||||
} from "../../routing/session-key.js";
|
||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../routing/session-key.js";
|
||||
import { getChatChannelMeta } from "../registry.js";
|
||||
import { discordMessageActions } from "./actions/discord.js";
|
||||
import {
|
||||
@@ -62,8 +59,7 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
|
||||
reload: { configPrefixes: ["channels.discord"] },
|
||||
config: {
|
||||
listAccountIds: (cfg) => listDiscordAccountIds(cfg),
|
||||
resolveAccount: (cfg, accountId) =>
|
||||
resolveDiscordAccount({ cfg, accountId }),
|
||||
resolveAccount: (cfg, accountId) => resolveDiscordAccount({ cfg, accountId }),
|
||||
defaultAccountId: (cfg) => resolveDefaultDiscordAccountId(cfg),
|
||||
setAccountEnabled: ({ cfg, accountId, enabled }) =>
|
||||
setAccountEnabledInConfigSection({
|
||||
@@ -89,9 +85,9 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
|
||||
tokenSource: account.tokenSource,
|
||||
}),
|
||||
resolveAllowFrom: ({ cfg, accountId }) =>
|
||||
(
|
||||
resolveDiscordAccount({ cfg, accountId }).config.dm?.allowFrom ?? []
|
||||
).map((entry) => String(entry)),
|
||||
(resolveDiscordAccount({ cfg, accountId }).config.dm?.allowFrom ?? []).map((entry) =>
|
||||
String(entry),
|
||||
),
|
||||
formatAllowFrom: ({ allowFrom }) =>
|
||||
allowFrom
|
||||
.map((entry) => String(entry).trim())
|
||||
@@ -100,11 +96,8 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
|
||||
},
|
||||
security: {
|
||||
resolveDmPolicy: ({ cfg, accountId, account }) => {
|
||||
const resolvedAccountId =
|
||||
accountId ?? account.accountId ?? DEFAULT_ACCOUNT_ID;
|
||||
const useAccountPath = Boolean(
|
||||
cfg.channels?.discord?.accounts?.[resolvedAccountId],
|
||||
);
|
||||
const resolvedAccountId = accountId ?? account.accountId ?? DEFAULT_ACCOUNT_ID;
|
||||
const useAccountPath = Boolean(cfg.channels?.discord?.accounts?.[resolvedAccountId]);
|
||||
const allowFromPath = useAccountPath
|
||||
? `channels.discord.accounts.${resolvedAccountId}.dm.`
|
||||
: "channels.discord.dm.";
|
||||
@@ -113,16 +106,14 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
|
||||
allowFrom: account.config.dm?.allowFrom ?? [],
|
||||
allowFromPath,
|
||||
approveHint: formatPairingApproveHint("discord"),
|
||||
normalizeEntry: (raw) =>
|
||||
raw.replace(/^(discord|user):/i, "").replace(/^<@!?(\d+)>$/, "$1"),
|
||||
normalizeEntry: (raw) => raw.replace(/^(discord|user):/i, "").replace(/^<@!?(\d+)>$/, "$1"),
|
||||
};
|
||||
},
|
||||
collectWarnings: ({ account }) => {
|
||||
const groupPolicy = account.config.groupPolicy ?? "allowlist";
|
||||
if (groupPolicy !== "open") return [];
|
||||
const channelAllowlistConfigured =
|
||||
Boolean(account.config.guilds) &&
|
||||
Object.keys(account.config.guilds ?? {}).length > 0;
|
||||
Boolean(account.config.guilds) && Object.keys(account.config.guilds ?? {}).length > 0;
|
||||
if (channelAllowlistConfigured) {
|
||||
return [
|
||||
`- Discord guilds: groupPolicy="open" allows any channel not explicitly denied to trigger (mention-gated). Set channels.discord.groupPolicy="allowlist" and configure channels.discord.guilds.<id>.channels.`,
|
||||
@@ -140,8 +131,7 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
|
||||
stripPatterns: () => ["<@!?\\d+>"],
|
||||
},
|
||||
threading: {
|
||||
resolveReplyToMode: ({ cfg }) =>
|
||||
cfg.channels?.discord?.replyToMode ?? "off",
|
||||
resolveReplyToMode: ({ cfg }) => cfg.channels?.discord?.replyToMode ?? "off",
|
||||
},
|
||||
messaging: {
|
||||
normalizeTarget: normalizeDiscordMessagingTarget,
|
||||
@@ -187,11 +177,7 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
|
||||
discord: {
|
||||
...next.channels?.discord,
|
||||
enabled: true,
|
||||
...(input.useEnv
|
||||
? {}
|
||||
: input.token
|
||||
? { token: input.token }
|
||||
: {}),
|
||||
...(input.useEnv ? {} : input.token ? { token: input.token } : {}),
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -226,9 +212,7 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
|
||||
if (!trimmed) {
|
||||
return {
|
||||
ok: false,
|
||||
error: new Error(
|
||||
"Delivering to Discord requires --to <channelId|user:ID|channel:ID>",
|
||||
),
|
||||
error: new Error("Delivering to Discord requires --to <channelId|user:ID|channel:ID>"),
|
||||
};
|
||||
}
|
||||
return { ok: true, to: trimmed };
|
||||
@@ -304,9 +288,7 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
|
||||
},
|
||||
buildAccountSnapshot: ({ account, runtime, probe, audit }) => {
|
||||
const configured = Boolean(account.token?.trim());
|
||||
const app =
|
||||
runtime?.application ??
|
||||
(probe as { application?: unknown })?.application;
|
||||
const app = runtime?.application ?? (probe as { application?: unknown })?.application;
|
||||
const bot = runtime?.bot ?? (probe as { bot?: unknown })?.bot;
|
||||
return {
|
||||
accountId: account.accountId,
|
||||
@@ -355,14 +337,10 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
|
||||
}
|
||||
} catch (err) {
|
||||
if (shouldLogVerbose()) {
|
||||
ctx.log?.debug?.(
|
||||
`[${account.accountId}] bot probe failed: ${String(err)}`,
|
||||
);
|
||||
ctx.log?.debug?.(`[${account.accountId}] bot probe failed: ${String(err)}`);
|
||||
}
|
||||
}
|
||||
ctx.log?.info(
|
||||
`[${account.accountId}] starting provider${discordBotLabel}`,
|
||||
);
|
||||
ctx.log?.info(`[${account.accountId}] starting provider${discordBotLabel}`);
|
||||
// Lazy import: the monitor pulls the reply pipeline; avoid ESM init cycles.
|
||||
const { monitorDiscordProvider } = await import("../../discord/index.js");
|
||||
return monitorDiscordProvider({
|
||||
|
||||
@@ -57,8 +57,7 @@ function resolveTelegramRequireMention(params: {
|
||||
if (!chatId) return undefined;
|
||||
const groupConfig = cfg.channels?.telegram?.groups?.[chatId];
|
||||
const groupDefault = cfg.channels?.telegram?.groups?.["*"];
|
||||
const topicConfig =
|
||||
topicId && groupConfig?.topics ? groupConfig.topics[topicId] : undefined;
|
||||
const topicConfig = topicId && groupConfig?.topics ? groupConfig.topics[topicId] : undefined;
|
||||
const defaultTopicConfig =
|
||||
topicId && groupDefault?.topics ? groupDefault.topics[topicId] : undefined;
|
||||
if (typeof topicConfig?.requireMention === "boolean") {
|
||||
@@ -76,10 +75,7 @@ function resolveTelegramRequireMention(params: {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function resolveDiscordGuildEntry(
|
||||
guilds: DiscordConfig["guilds"],
|
||||
groupSpace?: string | null,
|
||||
) {
|
||||
function resolveDiscordGuildEntry(guilds: DiscordConfig["guilds"], groupSpace?: string | null) {
|
||||
if (!guilds || Object.keys(guilds).length === 0) return null;
|
||||
const space = groupSpace?.trim() ?? "";
|
||||
if (space && guilds[space]) return guilds[space];
|
||||
@@ -112,9 +108,7 @@ export function resolveTelegramGroupRequireMention(
|
||||
});
|
||||
}
|
||||
|
||||
export function resolveWhatsAppGroupRequireMention(
|
||||
params: GroupMentionParams,
|
||||
): boolean {
|
||||
export function resolveWhatsAppGroupRequireMention(params: GroupMentionParams): boolean {
|
||||
return resolveChannelGroupRequireMention({
|
||||
cfg: params.cfg,
|
||||
channel: "whatsapp",
|
||||
@@ -123,9 +117,7 @@ export function resolveWhatsAppGroupRequireMention(
|
||||
});
|
||||
}
|
||||
|
||||
export function resolveIMessageGroupRequireMention(
|
||||
params: GroupMentionParams,
|
||||
): boolean {
|
||||
export function resolveIMessageGroupRequireMention(params: GroupMentionParams): boolean {
|
||||
return resolveChannelGroupRequireMention({
|
||||
cfg: params.cfg,
|
||||
channel: "imessage",
|
||||
@@ -134,9 +126,7 @@ export function resolveIMessageGroupRequireMention(
|
||||
});
|
||||
}
|
||||
|
||||
export function resolveDiscordGroupRequireMention(
|
||||
params: GroupMentionParams,
|
||||
): boolean {
|
||||
export function resolveDiscordGroupRequireMention(params: GroupMentionParams): boolean {
|
||||
const guildEntry = resolveDiscordGuildEntry(
|
||||
params.cfg.channels?.discord?.guilds,
|
||||
params.groupSpace,
|
||||
@@ -149,9 +139,7 @@ export function resolveDiscordGroupRequireMention(
|
||||
(channelSlug
|
||||
? (channelEntries[channelSlug] ?? channelEntries[`#${channelSlug}`])
|
||||
: undefined) ??
|
||||
(params.groupRoom
|
||||
? channelEntries[normalizeDiscordSlug(params.groupRoom)]
|
||||
: undefined);
|
||||
(params.groupRoom ? channelEntries[normalizeDiscordSlug(params.groupRoom)] : undefined);
|
||||
if (entry && typeof entry.requireMention === "boolean") {
|
||||
return entry.requireMention;
|
||||
}
|
||||
@@ -162,9 +150,7 @@ export function resolveDiscordGroupRequireMention(
|
||||
return true;
|
||||
}
|
||||
|
||||
export function resolveSlackGroupRequireMention(
|
||||
params: GroupMentionParams,
|
||||
): boolean {
|
||||
export function resolveSlackGroupRequireMention(params: GroupMentionParams): boolean {
|
||||
const account = resolveSlackAccount({
|
||||
cfg: params.cfg,
|
||||
accountId: params.accountId,
|
||||
|
||||
@@ -8,13 +8,8 @@ export function resolveChannelDefaultAccountId<ResolvedAccount>(params: {
|
||||
cfg: ClawdbotConfig;
|
||||
accountIds?: string[];
|
||||
}): string {
|
||||
const accountIds =
|
||||
params.accountIds ?? params.plugin.config.listAccountIds(params.cfg);
|
||||
return (
|
||||
params.plugin.config.defaultAccountId?.(params.cfg) ??
|
||||
accountIds[0] ??
|
||||
DEFAULT_ACCOUNT_ID
|
||||
);
|
||||
const accountIds = params.accountIds ?? params.plugin.config.listAccountIds(params.cfg);
|
||||
return params.plugin.config.defaultAccountId?.(params.cfg) ?? accountIds[0] ?? DEFAULT_ACCOUNT_ID;
|
||||
}
|
||||
|
||||
export function formatPairingApproveHint(channelId: string): string {
|
||||
|
||||
@@ -7,10 +7,7 @@ import {
|
||||
} from "../../imessage/accounts.js";
|
||||
import { probeIMessage } from "../../imessage/probe.js";
|
||||
import { sendMessageIMessage } from "../../imessage/send.js";
|
||||
import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
normalizeAccountId,
|
||||
} from "../../routing/session-key.js";
|
||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../routing/session-key.js";
|
||||
import { getChatChannelMeta } from "../registry.js";
|
||||
import {
|
||||
deleteAccountFromConfigSection,
|
||||
@@ -49,8 +46,7 @@ export const imessagePlugin: ChannelPlugin<ResolvedIMessageAccount> = {
|
||||
reload: { configPrefixes: ["channels.imessage"] },
|
||||
config: {
|
||||
listAccountIds: (cfg) => listIMessageAccountIds(cfg),
|
||||
resolveAccount: (cfg, accountId) =>
|
||||
resolveIMessageAccount({ cfg, accountId }),
|
||||
resolveAccount: (cfg, accountId) => resolveIMessageAccount({ cfg, accountId }),
|
||||
defaultAccountId: (cfg) => resolveDefaultIMessageAccountId(cfg),
|
||||
setAccountEnabled: ({ cfg, accountId, enabled }) =>
|
||||
setAccountEnabledInConfigSection({
|
||||
@@ -75,19 +71,16 @@ export const imessagePlugin: ChannelPlugin<ResolvedIMessageAccount> = {
|
||||
configured: account.configured,
|
||||
}),
|
||||
resolveAllowFrom: ({ cfg, accountId }) =>
|
||||
(resolveIMessageAccount({ cfg, accountId }).config.allowFrom ?? []).map(
|
||||
(entry) => String(entry),
|
||||
(resolveIMessageAccount({ cfg, accountId }).config.allowFrom ?? []).map((entry) =>
|
||||
String(entry),
|
||||
),
|
||||
formatAllowFrom: ({ allowFrom }) =>
|
||||
allowFrom.map((entry) => String(entry).trim()).filter(Boolean),
|
||||
},
|
||||
security: {
|
||||
resolveDmPolicy: ({ cfg, accountId, account }) => {
|
||||
const resolvedAccountId =
|
||||
accountId ?? account.accountId ?? DEFAULT_ACCOUNT_ID;
|
||||
const useAccountPath = Boolean(
|
||||
cfg.channels?.imessage?.accounts?.[resolvedAccountId],
|
||||
);
|
||||
const resolvedAccountId = accountId ?? account.accountId ?? DEFAULT_ACCOUNT_ID;
|
||||
const useAccountPath = Boolean(cfg.channels?.imessage?.accounts?.[resolvedAccountId]);
|
||||
const basePath = useAccountPath
|
||||
? `channels.imessage.accounts.${resolvedAccountId}.`
|
||||
: "channels.imessage.";
|
||||
@@ -181,9 +174,7 @@ export const imessagePlugin: ChannelPlugin<ResolvedIMessageAccount> = {
|
||||
if (!trimmed) {
|
||||
return {
|
||||
ok: false,
|
||||
error: new Error(
|
||||
"Delivering to iMessage requires --to <handle|chat_id:ID>",
|
||||
),
|
||||
error: new Error("Delivering to iMessage requires --to <handle|chat_id:ID>"),
|
||||
};
|
||||
}
|
||||
return { ok: true, to: trimmed };
|
||||
@@ -232,8 +223,7 @@ export const imessagePlugin: ChannelPlugin<ResolvedIMessageAccount> = {
|
||||
},
|
||||
collectStatusIssues: (accounts) =>
|
||||
accounts.flatMap((account) => {
|
||||
const lastError =
|
||||
typeof account.lastError === "string" ? account.lastError.trim() : "";
|
||||
const lastError = typeof account.lastError === "string" ? account.lastError.trim() : "";
|
||||
if (!lastError) return [];
|
||||
return [
|
||||
{
|
||||
@@ -287,9 +277,7 @@ export const imessagePlugin: ChannelPlugin<ResolvedIMessageAccount> = {
|
||||
`[${account.accountId}] starting provider (${cliPath}${dbPath ? ` db=${dbPath}` : ""})`,
|
||||
);
|
||||
// Lazy import: the monitor pulls the reply pipeline; avoid ESM init cycles.
|
||||
const { monitorIMessageProvider } = await import(
|
||||
"../../imessage/index.js"
|
||||
);
|
||||
const { monitorIMessageProvider } = await import("../../imessage/index.js");
|
||||
return monitorIMessageProvider({
|
||||
accountId: account.accountId,
|
||||
config: ctx.cfg,
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
CHAT_CHANNEL_ORDER,
|
||||
type ChatChannelId,
|
||||
normalizeChatChannelId,
|
||||
} from "../registry.js";
|
||||
import { CHAT_CHANNEL_ORDER, type ChatChannelId, normalizeChatChannelId } from "../registry.js";
|
||||
import { discordPlugin } from "./discord.js";
|
||||
import { imessagePlugin } from "./imessage.js";
|
||||
import { msteamsPlugin } from "./msteams.js";
|
||||
|
||||
@@ -18,9 +18,7 @@ const LOADERS: Record<ChannelId, PluginLoader> = {
|
||||
|
||||
const cache = new Map<ChannelId, ChannelPlugin>();
|
||||
|
||||
export async function loadChannelPlugin(
|
||||
id: ChannelId,
|
||||
): Promise<ChannelPlugin | undefined> {
|
||||
export async function loadChannelPlugin(id: ChannelId): Promise<ChannelPlugin | undefined> {
|
||||
const cached = cache.get(id);
|
||||
if (cached) return cached;
|
||||
const loader = LOADERS[id];
|
||||
|
||||
@@ -7,10 +7,7 @@ export function resolveChannelMediaMaxBytes(params: {
|
||||
cfg: ClawdbotConfig;
|
||||
// Channel-specific config lives under different keys; keep this helper generic
|
||||
// so shared plugin helpers don't need channel-id branching.
|
||||
resolveChannelLimitMb: (params: {
|
||||
cfg: ClawdbotConfig;
|
||||
accountId: string;
|
||||
}) => number | undefined;
|
||||
resolveChannelLimitMb: (params: { cfg: ClawdbotConfig; accountId: string }) => number | undefined;
|
||||
accountId?: string | null;
|
||||
}): number | undefined {
|
||||
const accountId = normalizeAccountId(params.accountId);
|
||||
|
||||
@@ -39,5 +39,4 @@ export const CHANNEL_MESSAGE_ACTION_NAMES = [
|
||||
"ban",
|
||||
] as const;
|
||||
|
||||
export type ChannelMessageActionName =
|
||||
(typeof CHANNEL_MESSAGE_ACTION_NAMES)[number];
|
||||
export type ChannelMessageActionName = (typeof CHANNEL_MESSAGE_ACTION_NAMES)[number];
|
||||
|
||||
@@ -2,14 +2,9 @@ import type { AgentToolResult } from "@mariozechner/pi-agent-core";
|
||||
|
||||
import type { ClawdbotConfig } from "../../config/config.js";
|
||||
import { getChannelPlugin, listChannelPlugins } from "./index.js";
|
||||
import type {
|
||||
ChannelMessageActionContext,
|
||||
ChannelMessageActionName,
|
||||
} from "./types.js";
|
||||
import type { ChannelMessageActionContext, ChannelMessageActionName } from "./types.js";
|
||||
|
||||
export function listChannelMessageActions(
|
||||
cfg: ClawdbotConfig,
|
||||
): ChannelMessageActionName[] {
|
||||
export function listChannelMessageActions(cfg: ClawdbotConfig): ChannelMessageActionName[] {
|
||||
const actions = new Set<ChannelMessageActionName>(["send"]);
|
||||
for (const plugin of listChannelPlugins()) {
|
||||
const list = plugin.actions?.listActions?.({ cfg });
|
||||
@@ -31,10 +26,7 @@ export async function dispatchChannelMessageAction(
|
||||
): Promise<AgentToolResult<unknown> | null> {
|
||||
const plugin = getChannelPlugin(ctx.channel);
|
||||
if (!plugin?.actions?.handleAction) return null;
|
||||
if (
|
||||
plugin.actions.supportsAction &&
|
||||
!plugin.actions.supportsAction({ action: ctx.action })
|
||||
) {
|
||||
if (plugin.actions.supportsAction && !plugin.actions.supportsAction({ action: ctx.action })) {
|
||||
return null;
|
||||
}
|
||||
return await plugin.actions.handleAction(ctx);
|
||||
|
||||
@@ -76,8 +76,7 @@ export const msteamsPlugin: ChannelPlugin<ResolvedMSTeamsAccount> = {
|
||||
}
|
||||
return next;
|
||||
},
|
||||
isConfigured: (_account, cfg) =>
|
||||
Boolean(resolveMSTeamsCredentials(cfg.channels?.msteams)),
|
||||
isConfigured: (_account, cfg) => Boolean(resolveMSTeamsCredentials(cfg.channels?.msteams)),
|
||||
describeAccount: (account) => ({
|
||||
accountId: account.accountId,
|
||||
enabled: account.enabled,
|
||||
@@ -139,17 +138,14 @@ export const msteamsPlugin: ChannelPlugin<ResolvedMSTeamsAccount> = {
|
||||
return { ok: true, to: trimmed };
|
||||
},
|
||||
sendText: async ({ cfg, to, text, deps }) => {
|
||||
const send =
|
||||
deps?.sendMSTeams ??
|
||||
((to, text) => sendMessageMSTeams({ cfg, to, text }));
|
||||
const send = deps?.sendMSTeams ?? ((to, text) => sendMessageMSTeams({ cfg, to, text }));
|
||||
const result = await send(to, text);
|
||||
return { channel: "msteams", ...result };
|
||||
},
|
||||
sendMedia: async ({ cfg, to, text, mediaUrl, deps }) => {
|
||||
const send =
|
||||
deps?.sendMSTeams ??
|
||||
((to, text, opts) =>
|
||||
sendMessageMSTeams({ cfg, to, text, mediaUrl: opts?.mediaUrl }));
|
||||
((to, text, opts) => sendMessageMSTeams({ cfg, to, text, mediaUrl: opts?.mediaUrl }));
|
||||
const result = await send(to, text, { mediaUrl });
|
||||
return { channel: "msteams", ...result };
|
||||
},
|
||||
|
||||
@@ -32,9 +32,7 @@ export function normalizeSlackMessagingTarget(raw: string): string | undefined {
|
||||
return `channel:${trimmed}`.toLowerCase();
|
||||
}
|
||||
|
||||
export function normalizeDiscordMessagingTarget(
|
||||
raw: string,
|
||||
): string | undefined {
|
||||
export function normalizeDiscordMessagingTarget(raw: string): string | undefined {
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) return undefined;
|
||||
const mentionMatch = trimmed.match(/^<@!?(\d+)>$/);
|
||||
@@ -62,9 +60,7 @@ export function normalizeDiscordMessagingTarget(
|
||||
return `channel:${trimmed}`.toLowerCase();
|
||||
}
|
||||
|
||||
export function normalizeTelegramMessagingTarget(
|
||||
raw: string,
|
||||
): string | undefined {
|
||||
export function normalizeTelegramMessagingTarget(raw: string): string | undefined {
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) return undefined;
|
||||
let normalized = trimmed;
|
||||
@@ -84,9 +80,7 @@ export function normalizeTelegramMessagingTarget(
|
||||
return `telegram:${normalized}`.toLowerCase();
|
||||
}
|
||||
|
||||
export function normalizeSignalMessagingTarget(
|
||||
raw: string,
|
||||
): string | undefined {
|
||||
export function normalizeSignalMessagingTarget(raw: string): string | undefined {
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) return undefined;
|
||||
let normalized = trimmed;
|
||||
@@ -110,9 +104,7 @@ export function normalizeSignalMessagingTarget(
|
||||
return normalized.toLowerCase();
|
||||
}
|
||||
|
||||
export function normalizeWhatsAppMessagingTarget(
|
||||
raw: string,
|
||||
): string | undefined {
|
||||
export function normalizeWhatsAppMessagingTarget(raw: string): string | undefined {
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) return undefined;
|
||||
return normalizeWhatsAppTarget(trimmed) ?? undefined;
|
||||
|
||||
@@ -30,9 +30,7 @@ export type PromptAccountIdParams = {
|
||||
defaultAccountId: string;
|
||||
};
|
||||
|
||||
export type PromptAccountId = (
|
||||
params: PromptAccountIdParams,
|
||||
) => Promise<string>;
|
||||
export type PromptAccountId = (params: PromptAccountIdParams) => Promise<string>;
|
||||
|
||||
export type ChannelOnboardingStatus = {
|
||||
channel: ChatChannelId;
|
||||
@@ -74,16 +72,9 @@ export type ChannelOnboardingDmPolicy = {
|
||||
|
||||
export type ChannelOnboardingAdapter = {
|
||||
channel: ChatChannelId;
|
||||
getStatus: (
|
||||
ctx: ChannelOnboardingStatusContext,
|
||||
) => Promise<ChannelOnboardingStatus>;
|
||||
configure: (
|
||||
ctx: ChannelOnboardingConfigureContext,
|
||||
) => Promise<ChannelOnboardingResult>;
|
||||
getStatus: (ctx: ChannelOnboardingStatusContext) => Promise<ChannelOnboardingStatus>;
|
||||
configure: (ctx: ChannelOnboardingConfigureContext) => Promise<ChannelOnboardingResult>;
|
||||
dmPolicy?: ChannelOnboardingDmPolicy;
|
||||
onAccountRecorded?: (
|
||||
accountId: string,
|
||||
options?: SetupChannelsOptions,
|
||||
) => void;
|
||||
onAccountRecorded?: (accountId: string, options?: SetupChannelsOptions) => void;
|
||||
disable?: (cfg: ClawdbotConfig) => ClawdbotConfig;
|
||||
};
|
||||
|
||||
@@ -5,25 +5,17 @@ import {
|
||||
resolveDefaultDiscordAccountId,
|
||||
resolveDiscordAccount,
|
||||
} from "../../../discord/accounts.js";
|
||||
import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
normalizeAccountId,
|
||||
} from "../../../routing/session-key.js";
|
||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../../routing/session-key.js";
|
||||
import { formatDocsLink } from "../../../terminal/links.js";
|
||||
import type { WizardPrompter } from "../../../wizard/prompts.js";
|
||||
import type {
|
||||
ChannelOnboardingAdapter,
|
||||
ChannelOnboardingDmPolicy,
|
||||
} from "../onboarding-types.js";
|
||||
import type { ChannelOnboardingAdapter, ChannelOnboardingDmPolicy } from "../onboarding-types.js";
|
||||
import { addWildcardAllowFrom, promptAccountId } from "./helpers.js";
|
||||
|
||||
const channel = "discord" as const;
|
||||
|
||||
function setDiscordDmPolicy(cfg: ClawdbotConfig, dmPolicy: DmPolicy) {
|
||||
const allowFrom =
|
||||
dmPolicy === "open"
|
||||
? addWildcardAllowFrom(cfg.channels?.discord?.dm?.allowFrom)
|
||||
: undefined;
|
||||
dmPolicy === "open" ? addWildcardAllowFrom(cfg.channels?.discord?.dm?.allowFrom) : undefined;
|
||||
return {
|
||||
...cfg,
|
||||
channels: {
|
||||
@@ -77,12 +69,7 @@ export const discordOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
quickstartScore: configured ? 2 : 1,
|
||||
};
|
||||
},
|
||||
configure: async ({
|
||||
cfg,
|
||||
prompter,
|
||||
accountOverrides,
|
||||
shouldPromptAccountIds,
|
||||
}) => {
|
||||
configure: async ({ cfg, prompter, accountOverrides, shouldPromptAccountIds }) => {
|
||||
const discordOverride = accountOverrides.discord?.trim();
|
||||
const defaultDiscordAccountId = resolveDefaultDiscordAccountId(cfg);
|
||||
let discordAccountId = discordOverride
|
||||
@@ -106,8 +93,7 @@ export const discordOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
});
|
||||
const accountConfigured = Boolean(resolvedAccount.token);
|
||||
const allowEnv = discordAccountId === DEFAULT_ACCOUNT_ID;
|
||||
const canUseEnv =
|
||||
allowEnv && Boolean(process.env.DISCORD_BOT_TOKEN?.trim());
|
||||
const canUseEnv = allowEnv && Boolean(process.env.DISCORD_BOT_TOKEN?.trim());
|
||||
const hasConfigToken = Boolean(resolvedAccount.config.token);
|
||||
|
||||
let token: string | null = null;
|
||||
@@ -178,9 +164,7 @@ export const discordOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
...next.channels?.discord?.accounts,
|
||||
[discordAccountId]: {
|
||||
...next.channels?.discord?.accounts?.[discordAccountId],
|
||||
enabled:
|
||||
next.channels?.discord?.accounts?.[discordAccountId]
|
||||
?.enabled ?? true,
|
||||
enabled: next.channels?.discord?.accounts?.[discordAccountId]?.enabled ?? true,
|
||||
token,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,18 +1,9 @@
|
||||
import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
normalizeAccountId,
|
||||
} from "../../../routing/session-key.js";
|
||||
import type {
|
||||
PromptAccountId,
|
||||
PromptAccountIdParams,
|
||||
} from "../onboarding-types.js";
|
||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../../routing/session-key.js";
|
||||
import type { PromptAccountId, PromptAccountIdParams } from "../onboarding-types.js";
|
||||
|
||||
export const promptAccountId: PromptAccountId = async (
|
||||
params: PromptAccountIdParams,
|
||||
) => {
|
||||
export const promptAccountId: PromptAccountId = async (params: PromptAccountIdParams) => {
|
||||
const existingIds = params.listAccountIds(params.cfg);
|
||||
const initial =
|
||||
params.currentId?.trim() || params.defaultAccountId || DEFAULT_ACCOUNT_ID;
|
||||
const initial = params.currentId?.trim() || params.defaultAccountId || DEFAULT_ACCOUNT_ID;
|
||||
const choice = (await params.prompter.select({
|
||||
message: `${params.label} account`,
|
||||
options: [
|
||||
|
||||
@@ -6,24 +6,16 @@ import {
|
||||
resolveDefaultIMessageAccountId,
|
||||
resolveIMessageAccount,
|
||||
} from "../../../imessage/accounts.js";
|
||||
import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
normalizeAccountId,
|
||||
} from "../../../routing/session-key.js";
|
||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../../routing/session-key.js";
|
||||
import { formatDocsLink } from "../../../terminal/links.js";
|
||||
import type {
|
||||
ChannelOnboardingAdapter,
|
||||
ChannelOnboardingDmPolicy,
|
||||
} from "../onboarding-types.js";
|
||||
import type { ChannelOnboardingAdapter, ChannelOnboardingDmPolicy } from "../onboarding-types.js";
|
||||
import { addWildcardAllowFrom, promptAccountId } from "./helpers.js";
|
||||
|
||||
const channel = "imessage" as const;
|
||||
|
||||
function setIMessageDmPolicy(cfg: ClawdbotConfig, dmPolicy: DmPolicy) {
|
||||
const allowFrom =
|
||||
dmPolicy === "open"
|
||||
? addWildcardAllowFrom(cfg.channels?.imessage?.allowFrom)
|
||||
: undefined;
|
||||
dmPolicy === "open" ? addWildcardAllowFrom(cfg.channels?.imessage?.allowFrom) : undefined;
|
||||
return {
|
||||
...cfg,
|
||||
channels: {
|
||||
@@ -53,10 +45,10 @@ export const imessageOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
const account = resolveIMessageAccount({ cfg, accountId });
|
||||
return Boolean(
|
||||
account.config.cliPath ||
|
||||
account.config.dbPath ||
|
||||
account.config.allowFrom ||
|
||||
account.config.service ||
|
||||
account.config.region,
|
||||
account.config.dbPath ||
|
||||
account.config.allowFrom ||
|
||||
account.config.service ||
|
||||
account.config.region,
|
||||
);
|
||||
});
|
||||
const imessageCliPath = cfg.channels?.imessage?.cliPath ?? "imsg";
|
||||
@@ -72,12 +64,7 @@ export const imessageOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
quickstartScore: imessageCliDetected ? 1 : 0,
|
||||
};
|
||||
},
|
||||
configure: async ({
|
||||
cfg,
|
||||
prompter,
|
||||
accountOverrides,
|
||||
shouldPromptAccountIds,
|
||||
}) => {
|
||||
configure: async ({ cfg, prompter, accountOverrides, shouldPromptAccountIds }) => {
|
||||
const imessageOverride = accountOverrides.imessage?.trim();
|
||||
const defaultIMessageAccountId = resolveDefaultIMessageAccountId(cfg);
|
||||
let imessageAccountId = imessageOverride
|
||||
@@ -109,10 +96,7 @@ export const imessageOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
});
|
||||
resolvedCliPath = String(entered).trim();
|
||||
if (!resolvedCliPath) {
|
||||
await prompter.note(
|
||||
"imsg CLI path required to enable iMessage.",
|
||||
"iMessage",
|
||||
);
|
||||
await prompter.note("imsg CLI path required to enable iMessage.", "iMessage");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,9 +125,7 @@ export const imessageOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
...next.channels?.imessage?.accounts,
|
||||
[imessageAccountId]: {
|
||||
...next.channels?.imessage?.accounts?.[imessageAccountId],
|
||||
enabled:
|
||||
next.channels?.imessage?.accounts?.[imessageAccountId]
|
||||
?.enabled ?? true,
|
||||
enabled: next.channels?.imessage?.accounts?.[imessageAccountId]?.enabled ?? true,
|
||||
cliPath: resolvedCliPath,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -4,10 +4,7 @@ import { resolveMSTeamsCredentials } from "../../../msteams/token.js";
|
||||
import { DEFAULT_ACCOUNT_ID } from "../../../routing/session-key.js";
|
||||
import { formatDocsLink } from "../../../terminal/links.js";
|
||||
import type { WizardPrompter } from "../../../wizard/prompts.js";
|
||||
import type {
|
||||
ChannelOnboardingAdapter,
|
||||
ChannelOnboardingDmPolicy,
|
||||
} from "../onboarding-types.js";
|
||||
import type { ChannelOnboardingAdapter, ChannelOnboardingDmPolicy } from "../onboarding-types.js";
|
||||
import { addWildcardAllowFrom } from "./helpers.js";
|
||||
|
||||
const channel = "msteams" as const;
|
||||
@@ -15,9 +12,7 @@ const channel = "msteams" as const;
|
||||
function setMSTeamsDmPolicy(cfg: ClawdbotConfig, dmPolicy: DmPolicy) {
|
||||
const allowFrom =
|
||||
dmPolicy === "open"
|
||||
? addWildcardAllowFrom(cfg.channels?.msteams?.allowFrom)?.map((entry) =>
|
||||
String(entry),
|
||||
)
|
||||
? addWildcardAllowFrom(cfg.channels?.msteams?.allowFrom)?.map((entry) => String(entry))
|
||||
: undefined;
|
||||
return {
|
||||
...cfg,
|
||||
@@ -32,9 +27,7 @@ function setMSTeamsDmPolicy(cfg: ClawdbotConfig, dmPolicy: DmPolicy) {
|
||||
};
|
||||
}
|
||||
|
||||
async function noteMSTeamsCredentialHelp(
|
||||
prompter: WizardPrompter,
|
||||
): Promise<void> {
|
||||
async function noteMSTeamsCredentialHelp(prompter: WizardPrompter): Promise<void> {
|
||||
await prompter.note(
|
||||
[
|
||||
"1) Azure Bot registration → get App ID + Tenant ID",
|
||||
@@ -59,15 +52,11 @@ const dmPolicy: ChannelOnboardingDmPolicy = {
|
||||
export const msteamsOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
channel,
|
||||
getStatus: async ({ cfg }) => {
|
||||
const configured = Boolean(
|
||||
resolveMSTeamsCredentials(cfg.channels?.msteams),
|
||||
);
|
||||
const configured = Boolean(resolveMSTeamsCredentials(cfg.channels?.msteams));
|
||||
return {
|
||||
channel,
|
||||
configured,
|
||||
statusLines: [
|
||||
`MS Teams: ${configured ? "configured" : "needs app credentials"}`,
|
||||
],
|
||||
statusLines: [`MS Teams: ${configured ? "configured" : "needs app credentials"}`],
|
||||
selectionHint: configured ? "configured" : "needs app creds",
|
||||
quickstartScore: configured ? 2 : 0,
|
||||
};
|
||||
@@ -76,14 +65,14 @@ export const msteamsOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
const resolved = resolveMSTeamsCredentials(cfg.channels?.msteams);
|
||||
const hasConfigCreds = Boolean(
|
||||
cfg.channels?.msteams?.appId?.trim() &&
|
||||
cfg.channels?.msteams?.appPassword?.trim() &&
|
||||
cfg.channels?.msteams?.tenantId?.trim(),
|
||||
cfg.channels?.msteams?.appPassword?.trim() &&
|
||||
cfg.channels?.msteams?.tenantId?.trim(),
|
||||
);
|
||||
const canUseEnv = Boolean(
|
||||
!hasConfigCreds &&
|
||||
process.env.MSTEAMS_APP_ID?.trim() &&
|
||||
process.env.MSTEAMS_APP_PASSWORD?.trim() &&
|
||||
process.env.MSTEAMS_TENANT_ID?.trim(),
|
||||
process.env.MSTEAMS_APP_ID?.trim() &&
|
||||
process.env.MSTEAMS_APP_PASSWORD?.trim() &&
|
||||
process.env.MSTEAMS_TENANT_ID?.trim(),
|
||||
);
|
||||
|
||||
let next = cfg;
|
||||
|
||||
@@ -2,29 +2,21 @@ import { detectBinary } from "../../../commands/onboard-helpers.js";
|
||||
import { installSignalCli } from "../../../commands/signal-install.js";
|
||||
import type { ClawdbotConfig } from "../../../config/config.js";
|
||||
import type { DmPolicy } from "../../../config/types.js";
|
||||
import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
normalizeAccountId,
|
||||
} from "../../../routing/session-key.js";
|
||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../../routing/session-key.js";
|
||||
import {
|
||||
listSignalAccountIds,
|
||||
resolveDefaultSignalAccountId,
|
||||
resolveSignalAccount,
|
||||
} from "../../../signal/accounts.js";
|
||||
import { formatDocsLink } from "../../../terminal/links.js";
|
||||
import type {
|
||||
ChannelOnboardingAdapter,
|
||||
ChannelOnboardingDmPolicy,
|
||||
} from "../onboarding-types.js";
|
||||
import type { ChannelOnboardingAdapter, ChannelOnboardingDmPolicy } from "../onboarding-types.js";
|
||||
import { addWildcardAllowFrom, promptAccountId } from "./helpers.js";
|
||||
|
||||
const channel = "signal" as const;
|
||||
|
||||
function setSignalDmPolicy(cfg: ClawdbotConfig, dmPolicy: DmPolicy) {
|
||||
const allowFrom =
|
||||
dmPolicy === "open"
|
||||
? addWildcardAllowFrom(cfg.channels?.signal?.allowFrom)
|
||||
: undefined;
|
||||
dmPolicy === "open" ? addWildcardAllowFrom(cfg.channels?.signal?.allowFrom) : undefined;
|
||||
return {
|
||||
...cfg,
|
||||
channels: {
|
||||
@@ -62,9 +54,7 @@ export const signalOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
`Signal: ${configured ? "configured" : "needs setup"}`,
|
||||
`signal-cli: ${signalCliDetected ? "found" : "missing"} (${signalCliPath})`,
|
||||
],
|
||||
selectionHint: signalCliDetected
|
||||
? "signal-cli found"
|
||||
: "signal-cli missing",
|
||||
selectionHint: signalCliDetected ? "signal-cli found" : "signal-cli missing",
|
||||
quickstartScore: signalCliDetected ? 1 : 0,
|
||||
};
|
||||
},
|
||||
@@ -113,21 +103,12 @@ export const signalOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
if (result.ok && result.cliPath) {
|
||||
cliDetected = true;
|
||||
resolvedCliPath = result.cliPath;
|
||||
await prompter.note(
|
||||
`Installed signal-cli at ${result.cliPath}`,
|
||||
"Signal",
|
||||
);
|
||||
await prompter.note(`Installed signal-cli at ${result.cliPath}`, "Signal");
|
||||
} else if (!result.ok) {
|
||||
await prompter.note(
|
||||
result.error ?? "signal-cli install failed.",
|
||||
"Signal",
|
||||
);
|
||||
await prompter.note(result.error ?? "signal-cli install failed.", "Signal");
|
||||
}
|
||||
} catch (err) {
|
||||
await prompter.note(
|
||||
`signal-cli install failed: ${String(err)}`,
|
||||
"Signal",
|
||||
);
|
||||
await prompter.note(`signal-cli install failed: ${String(err)}`, "Signal");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -183,9 +164,7 @@ export const signalOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
...next.channels?.signal?.accounts,
|
||||
[signalAccountId]: {
|
||||
...next.channels?.signal?.accounts?.[signalAccountId],
|
||||
enabled:
|
||||
next.channels?.signal?.accounts?.[signalAccountId]
|
||||
?.enabled ?? true,
|
||||
enabled: next.channels?.signal?.accounts?.[signalAccountId]?.enabled ?? true,
|
||||
account,
|
||||
cliPath: resolvedCliPath ?? "signal-cli",
|
||||
},
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import type { ClawdbotConfig } from "../../../config/config.js";
|
||||
import type { DmPolicy } from "../../../config/types.js";
|
||||
import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
normalizeAccountId,
|
||||
} from "../../../routing/session-key.js";
|
||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../../routing/session-key.js";
|
||||
import {
|
||||
listSlackAccountIds,
|
||||
resolveDefaultSlackAccountId,
|
||||
@@ -11,19 +8,14 @@ import {
|
||||
} from "../../../slack/accounts.js";
|
||||
import { formatDocsLink } from "../../../terminal/links.js";
|
||||
import type { WizardPrompter } from "../../../wizard/prompts.js";
|
||||
import type {
|
||||
ChannelOnboardingAdapter,
|
||||
ChannelOnboardingDmPolicy,
|
||||
} from "../onboarding-types.js";
|
||||
import type { ChannelOnboardingAdapter, ChannelOnboardingDmPolicy } from "../onboarding-types.js";
|
||||
import { addWildcardAllowFrom, promptAccountId } from "./helpers.js";
|
||||
|
||||
const channel = "slack" as const;
|
||||
|
||||
function setSlackDmPolicy(cfg: ClawdbotConfig, dmPolicy: DmPolicy) {
|
||||
const allowFrom =
|
||||
dmPolicy === "open"
|
||||
? addWildcardAllowFrom(cfg.channels?.slack?.dm?.allowFrom)
|
||||
: undefined;
|
||||
dmPolicy === "open" ? addWildcardAllowFrom(cfg.channels?.slack?.dm?.allowFrom) : undefined;
|
||||
return {
|
||||
...cfg,
|
||||
channels: {
|
||||
@@ -110,10 +102,7 @@ function buildSlackManifest(botName: string) {
|
||||
return JSON.stringify(manifest, null, 2);
|
||||
}
|
||||
|
||||
async function noteSlackTokenHelp(
|
||||
prompter: WizardPrompter,
|
||||
botName: string,
|
||||
): Promise<void> {
|
||||
async function noteSlackTokenHelp(prompter: WizardPrompter, botName: string): Promise<void> {
|
||||
const manifest = buildSlackManifest(botName);
|
||||
await prompter.note(
|
||||
[
|
||||
@@ -156,17 +145,10 @@ export const slackOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
quickstartScore: configured ? 2 : 1,
|
||||
};
|
||||
},
|
||||
configure: async ({
|
||||
cfg,
|
||||
prompter,
|
||||
accountOverrides,
|
||||
shouldPromptAccountIds,
|
||||
}) => {
|
||||
configure: async ({ cfg, prompter, accountOverrides, shouldPromptAccountIds }) => {
|
||||
const slackOverride = accountOverrides.slack?.trim();
|
||||
const defaultSlackAccountId = resolveDefaultSlackAccountId(cfg);
|
||||
let slackAccountId = slackOverride
|
||||
? normalizeAccountId(slackOverride)
|
||||
: defaultSlackAccountId;
|
||||
let slackAccountId = slackOverride ? normalizeAccountId(slackOverride) : defaultSlackAccountId;
|
||||
if (shouldPromptAccountIds && !slackOverride) {
|
||||
slackAccountId = await promptAccountId({
|
||||
cfg,
|
||||
@@ -183,9 +165,7 @@ export const slackOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
cfg: next,
|
||||
accountId: slackAccountId,
|
||||
});
|
||||
const accountConfigured = Boolean(
|
||||
resolvedAccount.botToken && resolvedAccount.appToken,
|
||||
);
|
||||
const accountConfigured = Boolean(resolvedAccount.botToken && resolvedAccount.appToken);
|
||||
const allowEnv = slackAccountId === DEFAULT_ACCOUNT_ID;
|
||||
const canUseEnv =
|
||||
allowEnv &&
|
||||
@@ -206,10 +186,7 @@ export const slackOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
if (!accountConfigured) {
|
||||
await noteSlackTokenHelp(prompter, slackBotName);
|
||||
}
|
||||
if (
|
||||
canUseEnv &&
|
||||
(!resolvedAccount.config.botToken || !resolvedAccount.config.appToken)
|
||||
) {
|
||||
if (canUseEnv && (!resolvedAccount.config.botToken || !resolvedAccount.config.appToken)) {
|
||||
const keepEnv = await prompter.confirm({
|
||||
message: "SLACK_BOT_TOKEN + SLACK_APP_TOKEN detected. Use env vars?",
|
||||
initialValue: true,
|
||||
@@ -296,9 +273,7 @@ export const slackOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
...next.channels?.slack?.accounts,
|
||||
[slackAccountId]: {
|
||||
...next.channels?.slack?.accounts?.[slackAccountId],
|
||||
enabled:
|
||||
next.channels?.slack?.accounts?.[slackAccountId]?.enabled ??
|
||||
true,
|
||||
enabled: next.channels?.slack?.accounts?.[slackAccountId]?.enabled ?? true,
|
||||
botToken,
|
||||
appToken,
|
||||
},
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import type { ClawdbotConfig } from "../../../config/config.js";
|
||||
import type { DmPolicy } from "../../../config/types.js";
|
||||
import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
normalizeAccountId,
|
||||
} from "../../../routing/session-key.js";
|
||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../../routing/session-key.js";
|
||||
import {
|
||||
listTelegramAccountIds,
|
||||
resolveDefaultTelegramAccountId,
|
||||
@@ -11,19 +8,14 @@ import {
|
||||
} from "../../../telegram/accounts.js";
|
||||
import { formatDocsLink } from "../../../terminal/links.js";
|
||||
import type { WizardPrompter } from "../../../wizard/prompts.js";
|
||||
import type {
|
||||
ChannelOnboardingAdapter,
|
||||
ChannelOnboardingDmPolicy,
|
||||
} from "../onboarding-types.js";
|
||||
import type { ChannelOnboardingAdapter, ChannelOnboardingDmPolicy } from "../onboarding-types.js";
|
||||
import { addWildcardAllowFrom, promptAccountId } from "./helpers.js";
|
||||
|
||||
const channel = "telegram" as const;
|
||||
|
||||
function setTelegramDmPolicy(cfg: ClawdbotConfig, dmPolicy: DmPolicy) {
|
||||
const allowFrom =
|
||||
dmPolicy === "open"
|
||||
? addWildcardAllowFrom(cfg.channels?.telegram?.allowFrom)
|
||||
: undefined;
|
||||
dmPolicy === "open" ? addWildcardAllowFrom(cfg.channels?.telegram?.allowFrom) : undefined;
|
||||
return {
|
||||
...cfg,
|
||||
channels: {
|
||||
@@ -62,9 +54,7 @@ async function promptTelegramAllowFrom(params: {
|
||||
const entry = await prompter.text({
|
||||
message: "Telegram allowFrom (user id)",
|
||||
placeholder: "123456789",
|
||||
initialValue: existingAllowFrom[0]
|
||||
? String(existingAllowFrom[0])
|
||||
: undefined,
|
||||
initialValue: existingAllowFrom[0] ? String(existingAllowFrom[0]) : undefined,
|
||||
validate: (value) => {
|
||||
const raw = String(value ?? "").trim();
|
||||
if (!raw) return "Required";
|
||||
@@ -105,8 +95,7 @@ async function promptTelegramAllowFrom(params: {
|
||||
...cfg.channels?.telegram?.accounts,
|
||||
[accountId]: {
|
||||
...cfg.channels?.telegram?.accounts?.[accountId],
|
||||
enabled:
|
||||
cfg.channels?.telegram?.accounts?.[accountId]?.enabled ?? true,
|
||||
enabled: cfg.channels?.telegram?.accounts?.[accountId]?.enabled ?? true,
|
||||
dmPolicy: "allowlist",
|
||||
allowFrom: unique,
|
||||
},
|
||||
@@ -135,9 +124,7 @@ export const telegramOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
channel,
|
||||
configured,
|
||||
statusLines: [`Telegram: ${configured ? "configured" : "needs token"}`],
|
||||
selectionHint: configured
|
||||
? "recommended · configured"
|
||||
: "recommended · newcomer-friendly",
|
||||
selectionHint: configured ? "recommended · configured" : "recommended · newcomer-friendly",
|
||||
quickstartScore: configured ? 1 : 10,
|
||||
};
|
||||
},
|
||||
@@ -171,8 +158,7 @@ export const telegramOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
});
|
||||
const accountConfigured = Boolean(resolvedAccount.token);
|
||||
const allowEnv = telegramAccountId === DEFAULT_ACCOUNT_ID;
|
||||
const canUseEnv =
|
||||
allowEnv && Boolean(process.env.TELEGRAM_BOT_TOKEN?.trim());
|
||||
const canUseEnv = allowEnv && Boolean(process.env.TELEGRAM_BOT_TOKEN?.trim());
|
||||
const hasConfigToken = Boolean(
|
||||
resolvedAccount.config.botToken || resolvedAccount.config.tokenFile,
|
||||
);
|
||||
@@ -252,9 +238,7 @@ export const telegramOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
...next.channels?.telegram?.accounts,
|
||||
[telegramAccountId]: {
|
||||
...next.channels?.telegram?.accounts?.[telegramAccountId],
|
||||
enabled:
|
||||
next.channels?.telegram?.accounts?.[telegramAccountId]
|
||||
?.enabled ?? true,
|
||||
enabled: next.channels?.telegram?.accounts?.[telegramAccountId]?.enabled ?? true,
|
||||
botToken: token,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -4,10 +4,7 @@ import { loginWeb } from "../../../channel-web.js";
|
||||
import type { ClawdbotConfig } from "../../../config/config.js";
|
||||
import { mergeWhatsAppConfig } from "../../../config/merge-config.js";
|
||||
import type { DmPolicy } from "../../../config/types.js";
|
||||
import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
normalizeAccountId,
|
||||
} from "../../../routing/session-key.js";
|
||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../../routing/session-key.js";
|
||||
import type { RuntimeEnv } from "../../../runtime.js";
|
||||
import { formatDocsLink } from "../../../terminal/links.js";
|
||||
import { normalizeE164 } from "../../../utils.js";
|
||||
@@ -22,28 +19,15 @@ import { promptAccountId } from "./helpers.js";
|
||||
|
||||
const channel = "whatsapp" as const;
|
||||
|
||||
function setWhatsAppDmPolicy(
|
||||
cfg: ClawdbotConfig,
|
||||
dmPolicy: DmPolicy,
|
||||
): ClawdbotConfig {
|
||||
function setWhatsAppDmPolicy(cfg: ClawdbotConfig, dmPolicy: DmPolicy): ClawdbotConfig {
|
||||
return mergeWhatsAppConfig(cfg, { dmPolicy });
|
||||
}
|
||||
|
||||
function setWhatsAppAllowFrom(
|
||||
cfg: ClawdbotConfig,
|
||||
allowFrom?: string[],
|
||||
): ClawdbotConfig {
|
||||
return mergeWhatsAppConfig(
|
||||
cfg,
|
||||
{ allowFrom },
|
||||
{ unsetOnUndefined: ["allowFrom"] },
|
||||
);
|
||||
function setWhatsAppAllowFrom(cfg: ClawdbotConfig, allowFrom?: string[]): ClawdbotConfig {
|
||||
return mergeWhatsAppConfig(cfg, { allowFrom }, { unsetOnUndefined: ["allowFrom"] });
|
||||
}
|
||||
|
||||
function setMessagesResponsePrefix(
|
||||
cfg: ClawdbotConfig,
|
||||
responsePrefix?: string,
|
||||
): ClawdbotConfig {
|
||||
function setMessagesResponsePrefix(cfg: ClawdbotConfig, responsePrefix?: string): ClawdbotConfig {
|
||||
return {
|
||||
...cfg,
|
||||
messages: {
|
||||
@@ -53,10 +37,7 @@ function setMessagesResponsePrefix(
|
||||
};
|
||||
}
|
||||
|
||||
function setWhatsAppSelfChatMode(
|
||||
cfg: ClawdbotConfig,
|
||||
selfChatMode: boolean,
|
||||
): ClawdbotConfig {
|
||||
function setWhatsAppSelfChatMode(cfg: ClawdbotConfig, selfChatMode: boolean): ClawdbotConfig {
|
||||
return mergeWhatsAppConfig(cfg, { selfChatMode });
|
||||
}
|
||||
|
||||
@@ -69,10 +50,7 @@ async function pathExists(filePath: string): Promise<boolean> {
|
||||
}
|
||||
}
|
||||
|
||||
async function detectWhatsAppLinked(
|
||||
cfg: ClawdbotConfig,
|
||||
accountId: string,
|
||||
): Promise<boolean> {
|
||||
async function detectWhatsAppLinked(cfg: ClawdbotConfig, accountId: string): Promise<boolean> {
|
||||
const { authDir } = resolveWhatsAppAuthDir({ cfg, accountId });
|
||||
const credsPath = path.join(authDir, "creds.json");
|
||||
return await pathExists(credsPath);
|
||||
@@ -86,8 +64,7 @@ async function promptWhatsAppAllowFrom(
|
||||
): Promise<ClawdbotConfig> {
|
||||
const existingPolicy = cfg.channels?.whatsapp?.dmPolicy ?? "pairing";
|
||||
const existingAllowFrom = cfg.channels?.whatsapp?.allowFrom ?? [];
|
||||
const existingLabel =
|
||||
existingAllowFrom.length > 0 ? existingAllowFrom.join(", ") : "unset";
|
||||
const existingLabel = existingAllowFrom.length > 0 ? existingAllowFrom.join(", ") : "unset";
|
||||
const existingResponsePrefix = cfg.messages?.responsePrefix;
|
||||
|
||||
if (options?.forceAllowlist) {
|
||||
@@ -96,8 +73,7 @@ async function promptWhatsAppAllowFrom(
|
||||
"WhatsApp number",
|
||||
);
|
||||
const entry = await prompter.text({
|
||||
message:
|
||||
"Your personal WhatsApp number (the phone you will message from)",
|
||||
message: "Your personal WhatsApp number (the phone you will message from)",
|
||||
placeholder: "+15555550123",
|
||||
initialValue: existingAllowFrom[0],
|
||||
validate: (value) => {
|
||||
@@ -164,8 +140,7 @@ async function promptWhatsAppAllowFrom(
|
||||
"WhatsApp number",
|
||||
);
|
||||
const entry = await prompter.text({
|
||||
message:
|
||||
"Your personal WhatsApp number (the phone you will message from)",
|
||||
message: "Your personal WhatsApp number (the phone you will message from)",
|
||||
placeholder: "+15555550123",
|
||||
initialValue: existingAllowFrom[0],
|
||||
validate: (value) => {
|
||||
@@ -274,9 +249,7 @@ async function promptWhatsAppAllowFrom(
|
||||
.split(/[\n,;]+/g)
|
||||
.map((p) => p.trim())
|
||||
.filter(Boolean);
|
||||
const normalized = parts.map((part) =>
|
||||
part === "*" ? "*" : normalizeE164(part),
|
||||
);
|
||||
const normalized = parts.map((part) => (part === "*" ? "*" : normalizeE164(part)));
|
||||
const unique = [...new Set(normalized.filter(Boolean))];
|
||||
next = setWhatsAppAllowFrom(next, unique);
|
||||
}
|
||||
@@ -289,18 +262,13 @@ export const whatsappOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
getStatus: async ({ cfg, accountOverrides }) => {
|
||||
const overrideId = accountOverrides.whatsapp?.trim();
|
||||
const defaultAccountId = resolveDefaultWhatsAppAccountId(cfg);
|
||||
const accountId = overrideId
|
||||
? normalizeAccountId(overrideId)
|
||||
: defaultAccountId;
|
||||
const accountId = overrideId ? normalizeAccountId(overrideId) : defaultAccountId;
|
||||
const linked = await detectWhatsAppLinked(cfg, accountId);
|
||||
const accountLabel =
|
||||
accountId === DEFAULT_ACCOUNT_ID ? "default" : accountId;
|
||||
const accountLabel = accountId === DEFAULT_ACCOUNT_ID ? "default" : accountId;
|
||||
return {
|
||||
channel,
|
||||
configured: linked,
|
||||
statusLines: [
|
||||
`WhatsApp (${accountLabel}): ${linked ? "linked" : "not linked"}`,
|
||||
],
|
||||
statusLines: [`WhatsApp (${accountLabel}): ${linked ? "linked" : "not linked"}`],
|
||||
selectionHint: linked ? "linked" : "not linked",
|
||||
quickstartScore: linked ? 5 : 4,
|
||||
};
|
||||
@@ -343,9 +311,7 @@ export const whatsappOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
...next.channels?.whatsapp?.accounts,
|
||||
[accountId]: {
|
||||
...next.channels?.whatsapp?.accounts?.[accountId],
|
||||
enabled:
|
||||
next.channels?.whatsapp?.accounts?.[accountId]?.enabled ??
|
||||
true,
|
||||
enabled: next.channels?.whatsapp?.accounts?.[accountId]?.enabled ?? true,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -370,9 +336,7 @@ export const whatsappOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
);
|
||||
}
|
||||
const wantsLink = await prompter.confirm({
|
||||
message: linked
|
||||
? "WhatsApp already linked. Re-link now?"
|
||||
: "Link WhatsApp now (QR)?",
|
||||
message: linked ? "WhatsApp already linked. Re-link now?" : "Link WhatsApp now (QR)?",
|
||||
initialValue: !linked,
|
||||
});
|
||||
if (wantsLink) {
|
||||
@@ -380,16 +344,10 @@ export const whatsappOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
await loginWeb(false, undefined, runtime, accountId);
|
||||
} catch (err) {
|
||||
runtime.error(`WhatsApp login failed: ${String(err)}`);
|
||||
await prompter.note(
|
||||
`Docs: ${formatDocsLink("/whatsapp", "whatsapp")}`,
|
||||
"WhatsApp help",
|
||||
);
|
||||
await prompter.note(`Docs: ${formatDocsLink("/whatsapp", "whatsapp")}`, "WhatsApp help");
|
||||
}
|
||||
} else if (!linked) {
|
||||
await prompter.note(
|
||||
"Run `clawdbot channels login` later to link WhatsApp.",
|
||||
"WhatsApp",
|
||||
);
|
||||
await prompter.note("Run `clawdbot channels login` later to link WhatsApp.", "WhatsApp");
|
||||
}
|
||||
|
||||
next = await promptWhatsAppAllowFrom(next, runtime, prompter, {
|
||||
|
||||
@@ -11,9 +11,7 @@ export const discordOutbound: ChannelOutboundAdapter = {
|
||||
if (!trimmed) {
|
||||
return {
|
||||
ok: false,
|
||||
error: new Error(
|
||||
"Delivering to Discord requires --to <channelId|user:ID|channel:ID>",
|
||||
),
|
||||
error: new Error("Delivering to Discord requires --to <channelId|user:ID|channel:ID>"),
|
||||
};
|
||||
}
|
||||
return { ok: true, to: trimmed };
|
||||
|
||||
@@ -12,9 +12,7 @@ export const imessageOutbound: ChannelOutboundAdapter = {
|
||||
if (!trimmed) {
|
||||
return {
|
||||
ok: false,
|
||||
error: new Error(
|
||||
"Delivering to iMessage requires --to <handle|chat_id:ID>",
|
||||
),
|
||||
error: new Error("Delivering to iMessage requires --to <handle|chat_id:ID>"),
|
||||
};
|
||||
}
|
||||
return { ok: true, to: trimmed };
|
||||
|
||||
@@ -21,17 +21,14 @@ export const msteamsOutbound: ChannelOutboundAdapter = {
|
||||
return { ok: true, to: trimmed };
|
||||
},
|
||||
sendText: async ({ cfg, to, text, deps }) => {
|
||||
const send =
|
||||
deps?.sendMSTeams ??
|
||||
((to, text) => sendMessageMSTeams({ cfg, to, text }));
|
||||
const send = deps?.sendMSTeams ?? ((to, text) => sendMessageMSTeams({ cfg, to, text }));
|
||||
const result = await send(to, text);
|
||||
return { channel: "msteams", ...result };
|
||||
},
|
||||
sendMedia: async ({ cfg, to, text, mediaUrl, deps }) => {
|
||||
const send =
|
||||
deps?.sendMSTeams ??
|
||||
((to, text, opts) =>
|
||||
sendMessageMSTeams({ cfg, to, text, mediaUrl: opts?.mediaUrl }));
|
||||
((to, text, opts) => sendMessageMSTeams({ cfg, to, text, mediaUrl: opts?.mediaUrl }));
|
||||
const result = await send(to, text, { mediaUrl });
|
||||
return { channel: "msteams", ...result };
|
||||
},
|
||||
|
||||
@@ -24,8 +24,7 @@ export const signalOutbound: ChannelOutboundAdapter = {
|
||||
const maxBytes = resolveChannelMediaMaxBytes({
|
||||
cfg,
|
||||
resolveChannelLimitMb: ({ cfg, accountId }) =>
|
||||
cfg.channels?.signal?.accounts?.[accountId]?.mediaMaxMb ??
|
||||
cfg.channels?.signal?.mediaMaxMb,
|
||||
cfg.channels?.signal?.accounts?.[accountId]?.mediaMaxMb ?? cfg.channels?.signal?.mediaMaxMb,
|
||||
accountId,
|
||||
});
|
||||
const result = await send(to, text, {
|
||||
@@ -39,8 +38,7 @@ export const signalOutbound: ChannelOutboundAdapter = {
|
||||
const maxBytes = resolveChannelMediaMaxBytes({
|
||||
cfg,
|
||||
resolveChannelLimitMb: ({ cfg, accountId }) =>
|
||||
cfg.channels?.signal?.accounts?.[accountId]?.mediaMaxMb ??
|
||||
cfg.channels?.signal?.mediaMaxMb,
|
||||
cfg.channels?.signal?.accounts?.[accountId]?.mediaMaxMb ?? cfg.channels?.signal?.mediaMaxMb,
|
||||
accountId,
|
||||
});
|
||||
const result = await send(to, text, {
|
||||
|
||||
@@ -10,9 +10,7 @@ export const slackOutbound: ChannelOutboundAdapter = {
|
||||
if (!trimmed) {
|
||||
return {
|
||||
ok: false,
|
||||
error: new Error(
|
||||
"Delivering to Slack requires --to <channelId|user:ID|channel:ID>",
|
||||
),
|
||||
error: new Error("Delivering to Slack requires --to <channelId|user:ID|channel:ID>"),
|
||||
};
|
||||
}
|
||||
return { ok: true, to: trimmed };
|
||||
|
||||
@@ -33,15 +33,7 @@ export const telegramOutbound: ChannelOutboundAdapter = {
|
||||
});
|
||||
return { channel: "telegram", ...result };
|
||||
},
|
||||
sendMedia: async ({
|
||||
to,
|
||||
text,
|
||||
mediaUrl,
|
||||
accountId,
|
||||
deps,
|
||||
replyToId,
|
||||
threadId,
|
||||
}) => {
|
||||
sendMedia: async ({ to, text, mediaUrl, accountId, deps, replyToId, threadId }) => {
|
||||
const send = deps?.sendTelegram ?? sendMessageTelegram;
|
||||
const replyToMessageId = parseReplyToMessageId(replyToId);
|
||||
const result = await send(to, text, {
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
import { chunkText } from "../../../auto-reply/chunk.js";
|
||||
import { shouldLogVerbose } from "../../../globals.js";
|
||||
import {
|
||||
sendMessageWhatsApp,
|
||||
sendPollWhatsApp,
|
||||
} from "../../../web/outbound.js";
|
||||
import {
|
||||
isWhatsAppGroupJid,
|
||||
normalizeWhatsAppTarget,
|
||||
} from "../../../whatsapp/normalize.js";
|
||||
import { sendMessageWhatsApp, sendPollWhatsApp } from "../../../web/outbound.js";
|
||||
import { isWhatsAppGroupJid, normalizeWhatsAppTarget } from "../../../whatsapp/normalize.js";
|
||||
import type { ChannelOutboundAdapter } from "../types.js";
|
||||
|
||||
export const whatsappOutbound: ChannelOutboundAdapter = {
|
||||
@@ -17,9 +11,7 @@ export const whatsappOutbound: ChannelOutboundAdapter = {
|
||||
pollMaxOptions: 12,
|
||||
resolveTarget: ({ to, allowFrom, mode }) => {
|
||||
const trimmed = to?.trim() ?? "";
|
||||
const allowListRaw = (allowFrom ?? [])
|
||||
.map((entry) => String(entry).trim())
|
||||
.filter(Boolean);
|
||||
const allowListRaw = (allowFrom ?? []).map((entry) => String(entry).trim()).filter(Boolean);
|
||||
const hasWildcard = allowListRaw.includes("*");
|
||||
const allowList = allowListRaw
|
||||
.filter((entry) => entry !== "*")
|
||||
@@ -29,10 +21,7 @@ export const whatsappOutbound: ChannelOutboundAdapter = {
|
||||
if (trimmed) {
|
||||
const normalizedTo = normalizeWhatsAppTarget(trimmed);
|
||||
if (!normalizedTo) {
|
||||
if (
|
||||
(mode === "implicit" || mode === "heartbeat") &&
|
||||
allowList.length > 0
|
||||
) {
|
||||
if ((mode === "implicit" || mode === "heartbeat") && allowList.length > 0) {
|
||||
return { ok: true, to: allowList[0] };
|
||||
}
|
||||
return {
|
||||
|
||||
@@ -15,16 +15,12 @@ export function listPairingChannels(): ChannelId[] {
|
||||
.map((plugin) => plugin.id);
|
||||
}
|
||||
|
||||
export function getPairingAdapter(
|
||||
channelId: ChannelId,
|
||||
): ChannelPairingAdapter | null {
|
||||
export function getPairingAdapter(channelId: ChannelId): ChannelPairingAdapter | null {
|
||||
const plugin = getChannelPlugin(channelId);
|
||||
return plugin?.pairing ?? null;
|
||||
}
|
||||
|
||||
export function requirePairingAdapter(
|
||||
channelId: ChannelId,
|
||||
): ChannelPairingAdapter {
|
||||
export function requirePairingAdapter(channelId: ChannelId): ChannelPairingAdapter {
|
||||
const adapter = getPairingAdapter(channelId);
|
||||
if (!adapter) {
|
||||
throw new Error(`Channel ${channelId} does not support pairing`);
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import type { ClawdbotConfig } from "../../config/config.js";
|
||||
import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
normalizeAccountId,
|
||||
} from "../../routing/session-key.js";
|
||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../routing/session-key.js";
|
||||
|
||||
type ChannelSectionBase = {
|
||||
name?: string;
|
||||
@@ -39,9 +36,7 @@ export function applyAccountNameToChannelSection(params: {
|
||||
const channels = params.cfg.channels as Record<string, unknown> | undefined;
|
||||
const baseConfig = channels?.[params.channelKey];
|
||||
const base =
|
||||
typeof baseConfig === "object" && baseConfig
|
||||
? (baseConfig as ChannelSectionBase)
|
||||
: undefined;
|
||||
typeof baseConfig === "object" && baseConfig ? (baseConfig as ChannelSectionBase) : undefined;
|
||||
const useAccounts = shouldStoreNameInAccounts({
|
||||
cfg: params.cfg,
|
||||
channelKey: params.channelKey,
|
||||
@@ -61,10 +56,7 @@ export function applyAccountNameToChannelSection(params: {
|
||||
},
|
||||
} as ClawdbotConfig;
|
||||
}
|
||||
const baseAccounts: Record<
|
||||
string,
|
||||
Record<string, unknown>
|
||||
> = base?.accounts ?? {};
|
||||
const baseAccounts: Record<string, Record<string, unknown>> = base?.accounts ?? {};
|
||||
const existingAccount = baseAccounts[accountId] ?? {};
|
||||
const baseWithoutName =
|
||||
accountId === DEFAULT_ACCOUNT_ID
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { chunkText } from "../../auto-reply/chunk.js";
|
||||
import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
normalizeAccountId,
|
||||
} from "../../routing/session-key.js";
|
||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../routing/session-key.js";
|
||||
import {
|
||||
listSignalAccountIds,
|
||||
type ResolvedSignalAccount,
|
||||
@@ -53,8 +50,7 @@ export const signalPlugin: ChannelPlugin<ResolvedSignalAccount> = {
|
||||
reload: { configPrefixes: ["channels.signal"] },
|
||||
config: {
|
||||
listAccountIds: (cfg) => listSignalAccountIds(cfg),
|
||||
resolveAccount: (cfg, accountId) =>
|
||||
resolveSignalAccount({ cfg, accountId }),
|
||||
resolveAccount: (cfg, accountId) => resolveSignalAccount({ cfg, accountId }),
|
||||
defaultAccountId: (cfg) => resolveDefaultSignalAccountId(cfg),
|
||||
setAccountEnabled: ({ cfg, accountId, enabled }) =>
|
||||
setAccountEnabledInConfigSection({
|
||||
@@ -69,14 +65,7 @@ export const signalPlugin: ChannelPlugin<ResolvedSignalAccount> = {
|
||||
cfg,
|
||||
sectionKey: "signal",
|
||||
accountId,
|
||||
clearBaseFields: [
|
||||
"account",
|
||||
"httpUrl",
|
||||
"httpHost",
|
||||
"httpPort",
|
||||
"cliPath",
|
||||
"name",
|
||||
],
|
||||
clearBaseFields: ["account", "httpUrl", "httpHost", "httpPort", "cliPath", "name"],
|
||||
}),
|
||||
isConfigured: (account) => account.configured,
|
||||
describeAccount: (account) => ({
|
||||
@@ -87,25 +76,20 @@ export const signalPlugin: ChannelPlugin<ResolvedSignalAccount> = {
|
||||
baseUrl: account.baseUrl,
|
||||
}),
|
||||
resolveAllowFrom: ({ cfg, accountId }) =>
|
||||
(resolveSignalAccount({ cfg, accountId }).config.allowFrom ?? []).map(
|
||||
(entry) => String(entry),
|
||||
(resolveSignalAccount({ cfg, accountId }).config.allowFrom ?? []).map((entry) =>
|
||||
String(entry),
|
||||
),
|
||||
formatAllowFrom: ({ allowFrom }) =>
|
||||
allowFrom
|
||||
.map((entry) => String(entry).trim())
|
||||
.filter(Boolean)
|
||||
.map((entry) =>
|
||||
entry === "*" ? "*" : normalizeE164(entry.replace(/^signal:/i, "")),
|
||||
)
|
||||
.map((entry) => (entry === "*" ? "*" : normalizeE164(entry.replace(/^signal:/i, ""))))
|
||||
.filter(Boolean),
|
||||
},
|
||||
security: {
|
||||
resolveDmPolicy: ({ cfg, accountId, account }) => {
|
||||
const resolvedAccountId =
|
||||
accountId ?? account.accountId ?? DEFAULT_ACCOUNT_ID;
|
||||
const useAccountPath = Boolean(
|
||||
cfg.channels?.signal?.accounts?.[resolvedAccountId],
|
||||
);
|
||||
const resolvedAccountId = accountId ?? account.accountId ?? DEFAULT_ACCOUNT_ID;
|
||||
const useAccountPath = Boolean(cfg.channels?.signal?.accounts?.[resolvedAccountId]);
|
||||
const basePath = useAccountPath
|
||||
? `channels.signal.accounts.${resolvedAccountId}.`
|
||||
: "channels.signal.";
|
||||
@@ -115,8 +99,7 @@ export const signalPlugin: ChannelPlugin<ResolvedSignalAccount> = {
|
||||
policyPath: `${basePath}dmPolicy`,
|
||||
allowFromPath: basePath,
|
||||
approveHint: formatPairingApproveHint("signal"),
|
||||
normalizeEntry: (raw) =>
|
||||
normalizeE164(raw.replace(/^signal:/i, "").trim()),
|
||||
normalizeEntry: (raw) => normalizeE164(raw.replace(/^signal:/i, "").trim()),
|
||||
};
|
||||
},
|
||||
collectWarnings: ({ account }) => {
|
||||
@@ -264,8 +247,7 @@ export const signalPlugin: ChannelPlugin<ResolvedSignalAccount> = {
|
||||
},
|
||||
collectStatusIssues: (accounts) =>
|
||||
accounts.flatMap((account) => {
|
||||
const lastError =
|
||||
typeof account.lastError === "string" ? account.lastError.trim() : "";
|
||||
const lastError = typeof account.lastError === "string" ? account.lastError.trim() : "";
|
||||
if (!lastError) return [];
|
||||
return [
|
||||
{
|
||||
@@ -312,9 +294,7 @@ export const signalPlugin: ChannelPlugin<ResolvedSignalAccount> = {
|
||||
accountId: account.accountId,
|
||||
baseUrl: account.baseUrl,
|
||||
});
|
||||
ctx.log?.info(
|
||||
`[${account.accountId}] starting provider (${account.baseUrl})`,
|
||||
);
|
||||
ctx.log?.info(`[${account.accountId}] starting provider (${account.baseUrl})`);
|
||||
// Lazy import: the monitor pulls the reply pipeline; avoid ESM init cycles.
|
||||
const { monitorSignalProvider } = await import("../../signal/index.js");
|
||||
return monitorSignalProvider({
|
||||
|
||||
@@ -1,12 +1,5 @@
|
||||
import {
|
||||
createActionGate,
|
||||
readNumberParam,
|
||||
readStringParam,
|
||||
} from "../../agents/tools/common.js";
|
||||
import {
|
||||
handleSlackAction,
|
||||
type SlackActionContext,
|
||||
} from "../../agents/tools/slack-actions.js";
|
||||
import { createActionGate, readNumberParam, readStringParam } from "../../agents/tools/common.js";
|
||||
import { handleSlackAction, type SlackActionContext } from "../../agents/tools/slack-actions.js";
|
||||
import { listEnabledSlackAccounts } from "../../slack/accounts.js";
|
||||
import type {
|
||||
ChannelMessageActionAdapter,
|
||||
@@ -15,9 +8,7 @@ import type {
|
||||
ChannelToolSend,
|
||||
} from "./types.js";
|
||||
|
||||
export function createSlackActions(
|
||||
providerId: string,
|
||||
): ChannelMessageActionAdapter {
|
||||
export function createSlackActions(providerId: string): ChannelMessageActionAdapter {
|
||||
return {
|
||||
listActions: ({ cfg }) => {
|
||||
const accounts = listEnabledSlackAccounts(cfg).filter(
|
||||
@@ -61,8 +52,7 @@ export function createSlackActions(
|
||||
if (action !== "sendMessage") return null;
|
||||
const to = typeof args.to === "string" ? args.to : undefined;
|
||||
if (!to) return null;
|
||||
const accountId =
|
||||
typeof args.accountId === "string" ? args.accountId.trim() : undefined;
|
||||
const accountId = typeof args.accountId === "string" ? args.accountId.trim() : undefined;
|
||||
return { to, accountId };
|
||||
},
|
||||
handleAction: async (ctx: ChannelMessageActionContext) => {
|
||||
@@ -70,8 +60,7 @@ export function createSlackActions(
|
||||
const accountId = ctx.accountId ?? undefined;
|
||||
const toolContext = ctx.toolContext as SlackActionContext | undefined;
|
||||
const resolveChannelId = () =>
|
||||
readStringParam(params, "channelId") ??
|
||||
readStringParam(params, "to", { required: true });
|
||||
readStringParam(params, "channelId") ?? readStringParam(params, "to", { required: true });
|
||||
|
||||
if (action === "send") {
|
||||
const to = readStringParam(params, "to", { required: true });
|
||||
@@ -101,8 +90,7 @@ export function createSlackActions(
|
||||
required: true,
|
||||
});
|
||||
const emoji = readStringParam(params, "emoji", { allowEmpty: true });
|
||||
const remove =
|
||||
typeof params.remove === "boolean" ? params.remove : undefined;
|
||||
const remove = typeof params.remove === "boolean" ? params.remove : undefined;
|
||||
return await handleSlackAction(
|
||||
{
|
||||
action: "react",
|
||||
@@ -188,11 +176,7 @@ export function createSlackActions(
|
||||
return await handleSlackAction(
|
||||
{
|
||||
action:
|
||||
action === "pin"
|
||||
? "pinMessage"
|
||||
: action === "unpin"
|
||||
? "unpinMessage"
|
||||
: "listPins",
|
||||
action === "pin" ? "pinMessage" : action === "unpin" ? "unpinMessage" : "listPins",
|
||||
channelId: resolveChannelId(),
|
||||
messageId,
|
||||
accountId: accountId ?? undefined,
|
||||
@@ -216,9 +200,7 @@ export function createSlackActions(
|
||||
);
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Action ${action} is not supported for provider ${providerId}.`,
|
||||
);
|
||||
throw new Error(`Action ${action} is not supported for provider ${providerId}.`);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
normalizeAccountId,
|
||||
} from "../../routing/session-key.js";
|
||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../routing/session-key.js";
|
||||
import {
|
||||
listSlackAccountIds,
|
||||
type ResolvedSlackAccount,
|
||||
@@ -82,9 +79,7 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount> = {
|
||||
appTokenSource: account.appTokenSource,
|
||||
}),
|
||||
resolveAllowFrom: ({ cfg, accountId }) =>
|
||||
(resolveSlackAccount({ cfg, accountId }).dm?.allowFrom ?? []).map(
|
||||
(entry) => String(entry),
|
||||
),
|
||||
(resolveSlackAccount({ cfg, accountId }).dm?.allowFrom ?? []).map((entry) => String(entry)),
|
||||
formatAllowFrom: ({ allowFrom }) =>
|
||||
allowFrom
|
||||
.map((entry) => String(entry).trim())
|
||||
@@ -93,11 +88,8 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount> = {
|
||||
},
|
||||
security: {
|
||||
resolveDmPolicy: ({ cfg, accountId, account }) => {
|
||||
const resolvedAccountId =
|
||||
accountId ?? account.accountId ?? DEFAULT_ACCOUNT_ID;
|
||||
const useAccountPath = Boolean(
|
||||
cfg.channels?.slack?.accounts?.[resolvedAccountId],
|
||||
);
|
||||
const resolvedAccountId = accountId ?? account.accountId ?? DEFAULT_ACCOUNT_ID;
|
||||
const useAccountPath = Boolean(cfg.channels?.slack?.accounts?.[resolvedAccountId]);
|
||||
const allowFromPath = useAccountPath
|
||||
? `channels.slack.accounts.${resolvedAccountId}.dm.`
|
||||
: "channels.slack.dm.";
|
||||
@@ -113,8 +105,7 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount> = {
|
||||
const groupPolicy = account.config.groupPolicy ?? "allowlist";
|
||||
if (groupPolicy !== "open") return [];
|
||||
const channelAllowlistConfigured =
|
||||
Boolean(account.config.channels) &&
|
||||
Object.keys(account.config.channels ?? {}).length > 0;
|
||||
Boolean(account.config.channels) && Object.keys(account.config.channels ?? {}).length > 0;
|
||||
if (channelAllowlistConfigured) {
|
||||
return [
|
||||
`- Slack channels: groupPolicy="open" allows any channel not explicitly denied to trigger (mention-gated). Set channels.slack.groupPolicy="allowlist" and configure channels.slack.channels.`,
|
||||
@@ -133,11 +124,8 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount> = {
|
||||
resolveSlackAccount({ cfg, accountId }).replyToMode ?? "off",
|
||||
allowTagsWhenOff: true,
|
||||
buildToolContext: ({ cfg, accountId, context, hasRepliedRef }) => {
|
||||
const configuredReplyToMode =
|
||||
resolveSlackAccount({ cfg, accountId }).replyToMode ?? "off";
|
||||
const effectiveReplyToMode = context.ThreadLabel
|
||||
? "all"
|
||||
: configuredReplyToMode;
|
||||
const configuredReplyToMode = resolveSlackAccount({ cfg, accountId }).replyToMode ?? "off";
|
||||
const effectiveReplyToMode = context.ThreadLabel ? "all" : configuredReplyToMode;
|
||||
return {
|
||||
currentChannelId: context.To?.startsWith("channel:")
|
||||
? context.To.slice("channel:".length)
|
||||
@@ -232,9 +220,7 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount> = {
|
||||
if (!trimmed) {
|
||||
return {
|
||||
ok: false,
|
||||
error: new Error(
|
||||
"Delivering to Slack requires --to <channelId|user:ID|channel:ID>",
|
||||
),
|
||||
error: new Error("Delivering to Slack requires --to <channelId|user:ID|channel:ID>"),
|
||||
};
|
||||
}
|
||||
return { ok: true, to: trimmed };
|
||||
|
||||
@@ -27,9 +27,7 @@ type DiscordPermissionsAuditSummary = {
|
||||
}>;
|
||||
};
|
||||
|
||||
function readDiscordAccountStatus(
|
||||
value: ChannelAccountSnapshot,
|
||||
): DiscordAccountStatus | null {
|
||||
function readDiscordAccountStatus(value: ChannelAccountSnapshot): DiscordAccountStatus | null {
|
||||
if (!isRecord(value)) return null;
|
||||
return {
|
||||
accountId: value.accountId,
|
||||
@@ -40,9 +38,7 @@ function readDiscordAccountStatus(
|
||||
};
|
||||
}
|
||||
|
||||
function readDiscordApplicationSummary(
|
||||
value: unknown,
|
||||
): DiscordApplicationSummary {
|
||||
function readDiscordApplicationSummary(value: unknown): DiscordApplicationSummary {
|
||||
if (!isRecord(value)) return {};
|
||||
const intentsRaw = value.intents;
|
||||
if (!isRecord(intentsRaw)) return {};
|
||||
@@ -58,13 +54,10 @@ function readDiscordApplicationSummary(
|
||||
};
|
||||
}
|
||||
|
||||
function readDiscordPermissionsAuditSummary(
|
||||
value: unknown,
|
||||
): DiscordPermissionsAuditSummary {
|
||||
function readDiscordPermissionsAuditSummary(value: unknown): DiscordPermissionsAuditSummary {
|
||||
if (!isRecord(value)) return {};
|
||||
const unresolvedChannels =
|
||||
typeof value.unresolvedChannels === "number" &&
|
||||
Number.isFinite(value.unresolvedChannels)
|
||||
typeof value.unresolvedChannels === "number" && Number.isFinite(value.unresolvedChannels)
|
||||
? value.unresolvedChannels
|
||||
: undefined;
|
||||
const channelsRaw = value.channels;
|
||||
@@ -110,8 +103,7 @@ export function collectDiscordStatusIssues(
|
||||
channel: "discord",
|
||||
accountId,
|
||||
kind: "intent",
|
||||
message:
|
||||
"Message Content Intent is disabled. Bot may not see normal channel messages.",
|
||||
message: "Message Content Intent is disabled. Bot may not see normal channel messages.",
|
||||
fix: "Enable Message Content Intent in Discord Dev Portal → Bot → Privileged Gateway Intents, or require mention-only operation.",
|
||||
});
|
||||
}
|
||||
@@ -128,9 +120,7 @@ export function collectDiscordStatusIssues(
|
||||
}
|
||||
for (const channel of audit.channels ?? []) {
|
||||
if (channel.ok === true) continue;
|
||||
const missing = channel.missing?.length
|
||||
? ` missing ${channel.missing.join(", ")}`
|
||||
: "";
|
||||
const missing = channel.missing?.length ? ` missing ${channel.missing.join(", ")}` : "";
|
||||
const error = channel.error ? `: ${channel.error}` : "";
|
||||
issues.push({
|
||||
channel: "discord",
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
export function asString(value: unknown): string | undefined {
|
||||
return typeof value === "string" && value.trim().length > 0
|
||||
? value.trim()
|
||||
: undefined;
|
||||
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
||||
}
|
||||
|
||||
export function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
|
||||
@@ -20,9 +20,7 @@ type TelegramGroupMembershipAuditSummary = {
|
||||
}>;
|
||||
};
|
||||
|
||||
function readTelegramAccountStatus(
|
||||
value: ChannelAccountSnapshot,
|
||||
): TelegramAccountStatus | null {
|
||||
function readTelegramAccountStatus(value: ChannelAccountSnapshot): TelegramAccountStatus | null {
|
||||
if (!isRecord(value)) return null;
|
||||
return {
|
||||
accountId: value.accountId,
|
||||
@@ -38,8 +36,7 @@ function readTelegramGroupMembershipAuditSummary(
|
||||
): TelegramGroupMembershipAuditSummary {
|
||||
if (!isRecord(value)) return {};
|
||||
const unresolvedGroups =
|
||||
typeof value.unresolvedGroups === "number" &&
|
||||
Number.isFinite(value.unresolvedGroups)
|
||||
typeof value.unresolvedGroups === "number" && Number.isFinite(value.unresolvedGroups)
|
||||
? value.unresolvedGroups
|
||||
: undefined;
|
||||
const hasWildcardUnmentionedGroups =
|
||||
|
||||
@@ -11,9 +11,7 @@ type WhatsAppAccountStatus = {
|
||||
lastError?: unknown;
|
||||
};
|
||||
|
||||
function readWhatsAppAccountStatus(
|
||||
value: ChannelAccountSnapshot,
|
||||
): WhatsAppAccountStatus | null {
|
||||
function readWhatsAppAccountStatus(value: ChannelAccountSnapshot): WhatsAppAccountStatus | null {
|
||||
if (!isRecord(value)) return null;
|
||||
return {
|
||||
accountId: value.accountId,
|
||||
@@ -40,9 +38,7 @@ export function collectWhatsAppStatusIssues(
|
||||
const running = account.running === true;
|
||||
const connected = account.connected === true;
|
||||
const reconnectAttempts =
|
||||
typeof account.reconnectAttempts === "number"
|
||||
? account.reconnectAttempts
|
||||
: null;
|
||||
typeof account.reconnectAttempts === "number" ? account.reconnectAttempts : null;
|
||||
const lastError = asString(account.lastError);
|
||||
|
||||
if (!linked) {
|
||||
|
||||
@@ -10,10 +10,7 @@ export async function buildChannelAccountSnapshot<ResolvedAccount>(params: {
|
||||
probe?: unknown;
|
||||
audit?: unknown;
|
||||
}): Promise<ChannelAccountSnapshot> {
|
||||
const account = params.plugin.config.resolveAccount(
|
||||
params.cfg,
|
||||
params.accountId,
|
||||
);
|
||||
const account = params.plugin.config.resolveAccount(params.cfg, params.accountId);
|
||||
if (params.plugin.status?.buildAccountSnapshot) {
|
||||
return await params.plugin.status.buildAccountSnapshot({
|
||||
account,
|
||||
|
||||
@@ -2,10 +2,7 @@ import { chunkMarkdownText } from "../../auto-reply/chunk.js";
|
||||
import type { ClawdbotConfig } from "../../config/config.js";
|
||||
import { writeConfigFile } from "../../config/config.js";
|
||||
import { shouldLogVerbose } from "../../globals.js";
|
||||
import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
normalizeAccountId,
|
||||
} from "../../routing/session-key.js";
|
||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../routing/session-key.js";
|
||||
import {
|
||||
listTelegramAccountIds,
|
||||
type ResolvedTelegramAccount,
|
||||
@@ -66,8 +63,7 @@ export const telegramPlugin: ChannelPlugin<ResolvedTelegramAccount> = {
|
||||
reload: { configPrefixes: ["channels.telegram"] },
|
||||
config: {
|
||||
listAccountIds: (cfg) => listTelegramAccountIds(cfg),
|
||||
resolveAccount: (cfg, accountId) =>
|
||||
resolveTelegramAccount({ cfg, accountId }),
|
||||
resolveAccount: (cfg, accountId) => resolveTelegramAccount({ cfg, accountId }),
|
||||
defaultAccountId: (cfg) => resolveDefaultTelegramAccountId(cfg),
|
||||
setAccountEnabled: ({ cfg, accountId, enabled }) =>
|
||||
setAccountEnabledInConfigSection({
|
||||
@@ -93,8 +89,8 @@ export const telegramPlugin: ChannelPlugin<ResolvedTelegramAccount> = {
|
||||
tokenSource: account.tokenSource,
|
||||
}),
|
||||
resolveAllowFrom: ({ cfg, accountId }) =>
|
||||
(resolveTelegramAccount({ cfg, accountId }).config.allowFrom ?? []).map(
|
||||
(entry) => String(entry),
|
||||
(resolveTelegramAccount({ cfg, accountId }).config.allowFrom ?? []).map((entry) =>
|
||||
String(entry),
|
||||
),
|
||||
formatAllowFrom: ({ allowFrom }) =>
|
||||
allowFrom
|
||||
@@ -105,11 +101,8 @@ export const telegramPlugin: ChannelPlugin<ResolvedTelegramAccount> = {
|
||||
},
|
||||
security: {
|
||||
resolveDmPolicy: ({ cfg, accountId, account }) => {
|
||||
const resolvedAccountId =
|
||||
accountId ?? account.accountId ?? DEFAULT_ACCOUNT_ID;
|
||||
const useAccountPath = Boolean(
|
||||
cfg.channels?.telegram?.accounts?.[resolvedAccountId],
|
||||
);
|
||||
const resolvedAccountId = accountId ?? account.accountId ?? DEFAULT_ACCOUNT_ID;
|
||||
const useAccountPath = Boolean(cfg.channels?.telegram?.accounts?.[resolvedAccountId]);
|
||||
const basePath = useAccountPath
|
||||
? `channels.telegram.accounts.${resolvedAccountId}.`
|
||||
: "channels.telegram.";
|
||||
@@ -141,8 +134,7 @@ export const telegramPlugin: ChannelPlugin<ResolvedTelegramAccount> = {
|
||||
resolveRequireMention: resolveTelegramGroupRequireMention,
|
||||
},
|
||||
threading: {
|
||||
resolveReplyToMode: ({ cfg }) =>
|
||||
cfg.channels?.telegram?.replyToMode ?? "first",
|
||||
resolveReplyToMode: ({ cfg }) => cfg.channels?.telegram?.replyToMode ?? "first",
|
||||
},
|
||||
messaging: {
|
||||
normalizeTarget: normalizeTelegramMessagingTarget,
|
||||
@@ -239,9 +231,7 @@ export const telegramPlugin: ChannelPlugin<ResolvedTelegramAccount> = {
|
||||
},
|
||||
sendText: async ({ to, text, accountId, deps, replyToId, threadId }) => {
|
||||
const send = deps?.sendTelegram ?? sendMessageTelegram;
|
||||
const replyToMessageId = replyToId
|
||||
? Number.parseInt(replyToId, 10)
|
||||
: undefined;
|
||||
const replyToMessageId = replyToId ? Number.parseInt(replyToId, 10) : undefined;
|
||||
const resolvedReplyToMessageId = Number.isFinite(replyToMessageId)
|
||||
? replyToMessageId
|
||||
: undefined;
|
||||
@@ -253,19 +243,9 @@ export const telegramPlugin: ChannelPlugin<ResolvedTelegramAccount> = {
|
||||
});
|
||||
return { channel: "telegram", ...result };
|
||||
},
|
||||
sendMedia: async ({
|
||||
to,
|
||||
text,
|
||||
mediaUrl,
|
||||
accountId,
|
||||
deps,
|
||||
replyToId,
|
||||
threadId,
|
||||
}) => {
|
||||
sendMedia: async ({ to, text, mediaUrl, accountId, deps, replyToId, threadId }) => {
|
||||
const send = deps?.sendTelegram ?? sendMessageTelegram;
|
||||
const replyToMessageId = replyToId
|
||||
? Number.parseInt(replyToId, 10)
|
||||
: undefined;
|
||||
const replyToMessageId = replyToId ? Number.parseInt(replyToId, 10) : undefined;
|
||||
const resolvedReplyToMessageId = Number.isFinite(replyToMessageId)
|
||||
? replyToMessageId
|
||||
: undefined;
|
||||
@@ -307,11 +287,7 @@ export const telegramPlugin: ChannelPlugin<ResolvedTelegramAccount> = {
|
||||
cfg.channels?.telegram?.groups;
|
||||
const { groupIds, unresolvedGroups, hasWildcardUnmentionedGroups } =
|
||||
collectTelegramUnmentionedGroupIds(groups);
|
||||
if (
|
||||
!groupIds.length &&
|
||||
unresolvedGroups === 0 &&
|
||||
!hasWildcardUnmentionedGroups
|
||||
) {
|
||||
if (!groupIds.length && unresolvedGroups === 0 && !hasWildcardUnmentionedGroups) {
|
||||
return undefined;
|
||||
}
|
||||
const botId =
|
||||
@@ -345,9 +321,7 @@ export const telegramPlugin: ChannelPlugin<ResolvedTelegramAccount> = {
|
||||
cfg.channels?.telegram?.groups;
|
||||
const allowUnmentionedGroups =
|
||||
Boolean(
|
||||
groups?.["*"] &&
|
||||
(groups["*"] as { requireMention?: boolean }).requireMention ===
|
||||
false,
|
||||
groups?.["*"] && (groups["*"] as { requireMention?: boolean }).requireMention === false,
|
||||
) ||
|
||||
Object.entries(groups ?? {}).some(
|
||||
([key, value]) =>
|
||||
@@ -366,8 +340,7 @@ export const telegramPlugin: ChannelPlugin<ResolvedTelegramAccount> = {
|
||||
lastStartAt: runtime?.lastStartAt ?? null,
|
||||
lastStopAt: runtime?.lastStopAt ?? null,
|
||||
lastError: runtime?.lastError ?? null,
|
||||
mode:
|
||||
runtime?.mode ?? (account.config.webhookUrl ? "webhook" : "polling"),
|
||||
mode: runtime?.mode ?? (account.config.webhookUrl ? "webhook" : "polling"),
|
||||
probe,
|
||||
audit,
|
||||
allowUnmentionedGroups,
|
||||
@@ -387,18 +360,12 @@ export const telegramPlugin: ChannelPlugin<ResolvedTelegramAccount> = {
|
||||
if (username) telegramBotLabel = ` (@${username})`;
|
||||
} catch (err) {
|
||||
if (shouldLogVerbose()) {
|
||||
ctx.log?.debug?.(
|
||||
`[${account.accountId}] bot probe failed: ${String(err)}`,
|
||||
);
|
||||
ctx.log?.debug?.(`[${account.accountId}] bot probe failed: ${String(err)}`);
|
||||
}
|
||||
}
|
||||
ctx.log?.info(
|
||||
`[${account.accountId}] starting provider${telegramBotLabel}`,
|
||||
);
|
||||
ctx.log?.info(`[${account.accountId}] starting provider${telegramBotLabel}`);
|
||||
// Lazy import: the monitor pulls the reply pipeline; avoid ESM init cycles.
|
||||
const { monitorTelegramProvider } = await import(
|
||||
"../../telegram/monitor.js"
|
||||
);
|
||||
const { monitorTelegramProvider } = await import("../../telegram/monitor.js");
|
||||
return monitorTelegramProvider({
|
||||
token,
|
||||
accountId: account.accountId,
|
||||
@@ -414,9 +381,7 @@ export const telegramPlugin: ChannelPlugin<ResolvedTelegramAccount> = {
|
||||
logoutAccount: async ({ accountId, cfg }) => {
|
||||
const envToken = process.env.TELEGRAM_BOT_TOKEN?.trim() ?? "";
|
||||
const nextCfg = { ...cfg } as ClawdbotConfig;
|
||||
const nextTelegram = cfg.channels?.telegram
|
||||
? { ...cfg.channels.telegram }
|
||||
: undefined;
|
||||
const nextTelegram = cfg.channels?.telegram ? { ...cfg.channels.telegram } : undefined;
|
||||
let cleared = false;
|
||||
let changed = false;
|
||||
if (nextTelegram) {
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import type { ClawdbotConfig } from "../../config/config.js";
|
||||
import type {
|
||||
OutboundDeliveryResult,
|
||||
OutboundSendDeps,
|
||||
} from "../../infra/outbound/deliver.js";
|
||||
import type { OutboundDeliveryResult, OutboundSendDeps } from "../../infra/outbound/deliver.js";
|
||||
import type { RuntimeEnv } from "../../runtime.js";
|
||||
import type {
|
||||
ChannelAccountSnapshot,
|
||||
@@ -20,10 +17,7 @@ import type {
|
||||
} from "./types.core.js";
|
||||
|
||||
export type ChannelSetupAdapter = {
|
||||
resolveAccountId?: (params: {
|
||||
cfg: ClawdbotConfig;
|
||||
accountId?: string;
|
||||
}) => string;
|
||||
resolveAccountId?: (params: { cfg: ClawdbotConfig; accountId?: string }) => string;
|
||||
applyAccountName?: (params: {
|
||||
cfg: ClawdbotConfig;
|
||||
accountId: string;
|
||||
@@ -43,34 +37,19 @@ export type ChannelSetupAdapter = {
|
||||
|
||||
export type ChannelConfigAdapter<ResolvedAccount> = {
|
||||
listAccountIds: (cfg: ClawdbotConfig) => string[];
|
||||
resolveAccount: (
|
||||
cfg: ClawdbotConfig,
|
||||
accountId?: string | null,
|
||||
) => ResolvedAccount;
|
||||
resolveAccount: (cfg: ClawdbotConfig, accountId?: string | null) => ResolvedAccount;
|
||||
defaultAccountId?: (cfg: ClawdbotConfig) => string;
|
||||
setAccountEnabled?: (params: {
|
||||
cfg: ClawdbotConfig;
|
||||
accountId: string;
|
||||
enabled: boolean;
|
||||
}) => ClawdbotConfig;
|
||||
deleteAccount?: (params: {
|
||||
cfg: ClawdbotConfig;
|
||||
accountId: string;
|
||||
}) => ClawdbotConfig;
|
||||
deleteAccount?: (params: { cfg: ClawdbotConfig; accountId: string }) => ClawdbotConfig;
|
||||
isEnabled?: (account: ResolvedAccount, cfg: ClawdbotConfig) => boolean;
|
||||
disabledReason?: (account: ResolvedAccount, cfg: ClawdbotConfig) => string;
|
||||
isConfigured?: (
|
||||
account: ResolvedAccount,
|
||||
cfg: ClawdbotConfig,
|
||||
) => boolean | Promise<boolean>;
|
||||
unconfiguredReason?: (
|
||||
account: ResolvedAccount,
|
||||
cfg: ClawdbotConfig,
|
||||
) => string;
|
||||
describeAccount?: (
|
||||
account: ResolvedAccount,
|
||||
cfg: ClawdbotConfig,
|
||||
) => ChannelAccountSnapshot;
|
||||
isConfigured?: (account: ResolvedAccount, cfg: ClawdbotConfig) => boolean | Promise<boolean>;
|
||||
unconfiguredReason?: (account: ResolvedAccount, cfg: ClawdbotConfig) => string;
|
||||
describeAccount?: (account: ResolvedAccount, cfg: ClawdbotConfig) => ChannelAccountSnapshot;
|
||||
resolveAllowFrom?: (params: {
|
||||
cfg: ClawdbotConfig;
|
||||
accountId?: string | null;
|
||||
@@ -154,9 +133,7 @@ export type ChannelStatusAdapter<ResolvedAccount> = {
|
||||
configured: boolean;
|
||||
enabled: boolean;
|
||||
}) => ChannelAccountState;
|
||||
collectStatusIssues?: (
|
||||
accounts: ChannelAccountSnapshot[],
|
||||
) => ChannelStatusIssue[];
|
||||
collectStatusIssues?: (accounts: ChannelAccountSnapshot[]) => ChannelStatusIssue[];
|
||||
};
|
||||
|
||||
export type ChannelGatewayContext<ResolvedAccount = unknown> = {
|
||||
@@ -205,9 +182,7 @@ export type ChannelPairingAdapter = {
|
||||
};
|
||||
|
||||
export type ChannelGatewayAdapter<ResolvedAccount = unknown> = {
|
||||
startAccount?: (
|
||||
ctx: ChannelGatewayContext<ResolvedAccount>,
|
||||
) => Promise<unknown>;
|
||||
startAccount?: (ctx: ChannelGatewayContext<ResolvedAccount>) => Promise<unknown>;
|
||||
stopAccount?: (ctx: ChannelGatewayContext<ResolvedAccount>) => Promise<void>;
|
||||
loginWithQrStart?: (params: {
|
||||
accountId?: string;
|
||||
@@ -219,9 +194,7 @@ export type ChannelGatewayAdapter<ResolvedAccount = unknown> = {
|
||||
accountId?: string;
|
||||
timeoutMs?: number;
|
||||
}) => Promise<ChannelLoginWithQrWaitResult>;
|
||||
logoutAccount?: (
|
||||
ctx: ChannelLogoutContext<ResolvedAccount>,
|
||||
) => Promise<ChannelLogoutResult>;
|
||||
logoutAccount?: (ctx: ChannelLogoutContext<ResolvedAccount>) => Promise<ChannelLogoutResult>;
|
||||
};
|
||||
|
||||
export type ChannelAuthAdapter = {
|
||||
@@ -240,10 +213,10 @@ export type ChannelHeartbeatAdapter = {
|
||||
accountId?: string | null;
|
||||
deps?: ChannelHeartbeatDeps;
|
||||
}) => Promise<{ ok: boolean; reason: string }>;
|
||||
resolveRecipients?: (params: {
|
||||
cfg: ClawdbotConfig;
|
||||
opts?: { to?: string; all?: boolean };
|
||||
}) => { recipients: string[]; source: string };
|
||||
resolveRecipients?: (params: { cfg: ClawdbotConfig; opts?: { to?: string; all?: boolean } }) => {
|
||||
recipients: string[];
|
||||
source: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type ChannelElevatedAdapter = {
|
||||
@@ -262,7 +235,5 @@ export type ChannelSecurityAdapter<ResolvedAccount = unknown> = {
|
||||
resolveDmPolicy?: (
|
||||
ctx: ChannelSecurityContext<ResolvedAccount>,
|
||||
) => ChannelSecurityDmPolicy | null;
|
||||
collectWarnings?: (
|
||||
ctx: ChannelSecurityContext<ResolvedAccount>,
|
||||
) => Promise<string[]> | string[];
|
||||
collectWarnings?: (ctx: ChannelSecurityContext<ResolvedAccount>) => Promise<string[]> | string[];
|
||||
};
|
||||
|
||||
@@ -3,10 +3,7 @@ import type { TSchema } from "@sinclair/typebox";
|
||||
import type { MsgContext } from "../../auto-reply/templating.js";
|
||||
import type { ClawdbotConfig } from "../../config/config.js";
|
||||
import type { PollInput } from "../../polls.js";
|
||||
import type {
|
||||
GatewayClientMode,
|
||||
GatewayClientName,
|
||||
} from "../../utils/message-channel.js";
|
||||
import type { GatewayClientMode, GatewayClientName } from "../../utils/message-channel.js";
|
||||
import type { ChatChannelId } from "../registry.js";
|
||||
import type { ChannelMessageActionName as ChannelMessageActionNameFromList } from "./message-action-names.js";
|
||||
|
||||
@@ -16,9 +13,7 @@ export type ChannelOutboundTargetMode = "explicit" | "implicit" | "heartbeat";
|
||||
|
||||
export type ChannelAgentTool = AgentTool<TSchema, unknown>;
|
||||
|
||||
export type ChannelAgentToolFactory = (params: {
|
||||
cfg?: ClawdbotConfig;
|
||||
}) => ChannelAgentTool[];
|
||||
export type ChannelAgentToolFactory = (params: { cfg?: ClawdbotConfig }) => ChannelAgentTool[];
|
||||
|
||||
export type ChannelSetupInput = {
|
||||
name?: string;
|
||||
@@ -239,12 +234,8 @@ export type ChannelMessageActionAdapter = {
|
||||
listActions?: (params: { cfg: ClawdbotConfig }) => ChannelMessageActionName[];
|
||||
supportsAction?: (params: { action: ChannelMessageActionName }) => boolean;
|
||||
supportsButtons?: (params: { cfg: ClawdbotConfig }) => boolean;
|
||||
extractToolSend?: (params: {
|
||||
args: Record<string, unknown>;
|
||||
}) => ChannelToolSend | null;
|
||||
handleAction?: (
|
||||
ctx: ChannelMessageActionContext,
|
||||
) => Promise<AgentToolResult<unknown>>;
|
||||
extractToolSend?: (params: { args: Record<string, unknown> }) => ChannelToolSend | null;
|
||||
handleAction?: (ctx: ChannelMessageActionContext) => Promise<AgentToolResult<unknown>>;
|
||||
};
|
||||
|
||||
export type ChannelPollResult = {
|
||||
|
||||
@@ -24,8 +24,7 @@ function getSessionRecipients(cfg: ClawdbotConfig) {
|
||||
.filter(([key]) => !isGroupKey(key) && !isCronKey(key))
|
||||
.map(([_, entry]) => ({
|
||||
to:
|
||||
normalizeChatChannelId(entry?.lastChannel) === "whatsapp" &&
|
||||
entry?.lastTo
|
||||
normalizeChatChannelId(entry?.lastChannel) === "whatsapp" && entry?.lastTo
|
||||
? normalizeE164(entry.lastTo)
|
||||
: "",
|
||||
updatedAt: entry?.updatedAt ?? 0,
|
||||
@@ -52,11 +51,8 @@ export function resolveWhatsAppHeartbeatRecipients(
|
||||
|
||||
const sessionRecipients = getSessionRecipients(cfg);
|
||||
const allowFrom =
|
||||
Array.isArray(cfg.channels?.whatsapp?.allowFrom) &&
|
||||
cfg.channels.whatsapp.allowFrom.length > 0
|
||||
? cfg.channels.whatsapp.allowFrom
|
||||
.filter((v) => v !== "*")
|
||||
.map(normalizeE164)
|
||||
Array.isArray(cfg.channels?.whatsapp?.allowFrom) && cfg.channels.whatsapp.allowFrom.length > 0
|
||||
? cfg.channels.whatsapp.allowFrom.filter((v) => v !== "*").map(normalizeE164)
|
||||
: [];
|
||||
|
||||
const unique = (list: string[]) => [...new Set(list.filter(Boolean))];
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
import {
|
||||
createActionGate,
|
||||
readStringParam,
|
||||
} from "../../agents/tools/common.js";
|
||||
import { createActionGate, readStringParam } from "../../agents/tools/common.js";
|
||||
import { handleWhatsAppAction } from "../../agents/tools/whatsapp-actions.js";
|
||||
import { chunkText } from "../../auto-reply/chunk.js";
|
||||
import { shouldLogVerbose } from "../../globals.js";
|
||||
import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
normalizeAccountId,
|
||||
} from "../../routing/session-key.js";
|
||||
import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../../routing/session-key.js";
|
||||
import { normalizeE164 } from "../../utils.js";
|
||||
import {
|
||||
listWhatsAppAccountIds,
|
||||
@@ -25,10 +19,7 @@ import {
|
||||
webAuthExists,
|
||||
} from "../../web/auth-store.js";
|
||||
import { sendMessageWhatsApp, sendPollWhatsApp } from "../../web/outbound.js";
|
||||
import {
|
||||
isWhatsAppGroupJid,
|
||||
normalizeWhatsAppTarget,
|
||||
} from "../../whatsapp/normalize.js";
|
||||
import { isWhatsAppGroupJid, normalizeWhatsAppTarget } from "../../whatsapp/normalize.js";
|
||||
import { getChatChannelMeta } from "../registry.js";
|
||||
import { createWhatsAppLoginTool } from "./agent-tools/whatsapp-login.js";
|
||||
import { resolveWhatsAppGroupRequireMention } from "./group-mentions.js";
|
||||
@@ -45,8 +36,7 @@ import { resolveWhatsAppHeartbeatRecipients } from "./whatsapp-heartbeat.js";
|
||||
|
||||
const meta = getChatChannelMeta("whatsapp");
|
||||
|
||||
const escapeRegExp = (value: string) =>
|
||||
value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
const escapeRegExp = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
|
||||
export const whatsappPlugin: ChannelPlugin<ResolvedWhatsAppAccount> = {
|
||||
id: "whatsapp",
|
||||
@@ -72,8 +62,7 @@ export const whatsappPlugin: ChannelPlugin<ResolvedWhatsAppAccount> = {
|
||||
gatewayMethods: ["web.login.start", "web.login.wait"],
|
||||
config: {
|
||||
listAccountIds: (cfg) => listWhatsAppAccountIds(cfg),
|
||||
resolveAccount: (cfg, accountId) =>
|
||||
resolveWhatsAppAccount({ cfg, accountId }),
|
||||
resolveAccount: (cfg, accountId) => resolveWhatsAppAccount({ cfg, accountId }),
|
||||
defaultAccountId: (cfg) => resolveDefaultWhatsAppAccountId(cfg),
|
||||
setAccountEnabled: ({ cfg, accountId, enabled }) => {
|
||||
const accountKey = accountId || DEFAULT_ACCOUNT_ID;
|
||||
@@ -111,8 +100,7 @@ export const whatsappPlugin: ChannelPlugin<ResolvedWhatsAppAccount> = {
|
||||
},
|
||||
};
|
||||
},
|
||||
isEnabled: (account, cfg) =>
|
||||
account.enabled !== false && cfg.web?.enabled !== false,
|
||||
isEnabled: (account, cfg) => account.enabled !== false && cfg.web?.enabled !== false,
|
||||
disabledReason: () => "disabled",
|
||||
isConfigured: async (account) => await webAuthExists(account.authDir),
|
||||
unconfiguredReason: () => "not linked",
|
||||
@@ -130,18 +118,13 @@ export const whatsappPlugin: ChannelPlugin<ResolvedWhatsAppAccount> = {
|
||||
allowFrom
|
||||
.map((entry) => String(entry).trim())
|
||||
.filter((entry): entry is string => Boolean(entry))
|
||||
.map((entry) =>
|
||||
entry === "*" ? entry : normalizeWhatsAppTarget(entry),
|
||||
)
|
||||
.map((entry) => (entry === "*" ? entry : normalizeWhatsAppTarget(entry)))
|
||||
.filter((entry): entry is string => Boolean(entry)),
|
||||
},
|
||||
security: {
|
||||
resolveDmPolicy: ({ cfg, accountId, account }) => {
|
||||
const resolvedAccountId =
|
||||
accountId ?? account.accountId ?? DEFAULT_ACCOUNT_ID;
|
||||
const useAccountPath = Boolean(
|
||||
cfg.channels?.whatsapp?.accounts?.[resolvedAccountId],
|
||||
);
|
||||
const resolvedAccountId = accountId ?? account.accountId ?? DEFAULT_ACCOUNT_ID;
|
||||
const useAccountPath = Boolean(cfg.channels?.whatsapp?.accounts?.[resolvedAccountId]);
|
||||
const basePath = useAccountPath
|
||||
? `channels.whatsapp.accounts.${resolvedAccountId}.`
|
||||
: "channels.whatsapp.";
|
||||
@@ -244,29 +227,24 @@ export const whatsappPlugin: ChannelPlugin<ResolvedWhatsAppAccount> = {
|
||||
supportsAction: ({ action }) => action === "react",
|
||||
handleAction: async ({ action, params, cfg, accountId }) => {
|
||||
if (action !== "react") {
|
||||
throw new Error(
|
||||
`Action ${action} is not supported for provider ${meta.id}.`,
|
||||
);
|
||||
throw new Error(`Action ${action} is not supported for provider ${meta.id}.`);
|
||||
}
|
||||
const messageId = readStringParam(params, "messageId", {
|
||||
required: true,
|
||||
});
|
||||
const emoji = readStringParam(params, "emoji", { allowEmpty: true });
|
||||
const remove =
|
||||
typeof params.remove === "boolean" ? params.remove : undefined;
|
||||
const remove = typeof params.remove === "boolean" ? params.remove : undefined;
|
||||
return await handleWhatsAppAction(
|
||||
{
|
||||
action: "react",
|
||||
chatJid:
|
||||
readStringParam(params, "chatJid") ??
|
||||
readStringParam(params, "to", { required: true }),
|
||||
readStringParam(params, "chatJid") ?? readStringParam(params, "to", { required: true }),
|
||||
messageId,
|
||||
emoji,
|
||||
remove,
|
||||
participant: readStringParam(params, "participant"),
|
||||
accountId: accountId ?? undefined,
|
||||
fromMe:
|
||||
typeof params.fromMe === "boolean" ? params.fromMe : undefined,
|
||||
fromMe: typeof params.fromMe === "boolean" ? params.fromMe : undefined,
|
||||
},
|
||||
cfg,
|
||||
);
|
||||
@@ -279,9 +257,7 @@ export const whatsappPlugin: ChannelPlugin<ResolvedWhatsAppAccount> = {
|
||||
pollMaxOptions: 12,
|
||||
resolveTarget: ({ to, allowFrom, mode }) => {
|
||||
const trimmed = to?.trim() ?? "";
|
||||
const allowListRaw = (allowFrom ?? [])
|
||||
.map((entry) => String(entry).trim())
|
||||
.filter(Boolean);
|
||||
const allowListRaw = (allowFrom ?? []).map((entry) => String(entry).trim()).filter(Boolean);
|
||||
const hasWildcard = allowListRaw.includes("*");
|
||||
const allowList = allowListRaw
|
||||
.filter((entry) => entry !== "*")
|
||||
@@ -291,10 +267,7 @@ export const whatsappPlugin: ChannelPlugin<ResolvedWhatsAppAccount> = {
|
||||
if (trimmed) {
|
||||
const normalizedTo = normalizeWhatsAppTarget(trimmed);
|
||||
if (!normalizedTo) {
|
||||
if (
|
||||
(mode === "implicit" || mode === "heartbeat") &&
|
||||
allowList.length > 0
|
||||
) {
|
||||
if ((mode === "implicit" || mode === "heartbeat") && allowList.length > 0) {
|
||||
return { ok: true, to: allowList[0] };
|
||||
}
|
||||
return {
|
||||
@@ -356,8 +329,7 @@ export const whatsappPlugin: ChannelPlugin<ResolvedWhatsAppAccount> = {
|
||||
},
|
||||
auth: {
|
||||
login: async ({ cfg, accountId, runtime, verbose }) => {
|
||||
const resolvedAccountId =
|
||||
accountId?.trim() || resolveDefaultWhatsAppAccountId(cfg);
|
||||
const resolvedAccountId = accountId?.trim() || resolveDefaultWhatsAppAccountId(cfg);
|
||||
const { loginWeb } = await import("../../web/login.js");
|
||||
await loginWeb(Boolean(verbose), undefined, runtime, resolvedAccountId);
|
||||
},
|
||||
@@ -368,9 +340,7 @@ export const whatsappPlugin: ChannelPlugin<ResolvedWhatsAppAccount> = {
|
||||
return { ok: false, reason: "whatsapp-disabled" };
|
||||
}
|
||||
const account = resolveWhatsAppAccount({ cfg, accountId });
|
||||
const authExists = await (deps?.webAuthExists ?? webAuthExists)(
|
||||
account.authDir,
|
||||
);
|
||||
const authExists = await (deps?.webAuthExists ?? webAuthExists)(account.authDir);
|
||||
if (!authExists) {
|
||||
return { ok: false, reason: "whatsapp-not-linked" };
|
||||
}
|
||||
@@ -382,8 +352,7 @@ export const whatsappPlugin: ChannelPlugin<ResolvedWhatsAppAccount> = {
|
||||
}
|
||||
return { ok: true, reason: "ok" };
|
||||
},
|
||||
resolveRecipients: ({ cfg, opts }) =>
|
||||
resolveWhatsAppHeartbeatRecipients(cfg, opts),
|
||||
resolveRecipients: ({ cfg, opts }) => resolveWhatsAppHeartbeatRecipients(cfg, opts),
|
||||
},
|
||||
status: {
|
||||
defaultRuntime: {
|
||||
@@ -407,8 +376,7 @@ export const whatsappPlugin: ChannelPlugin<ResolvedWhatsAppAccount> = {
|
||||
? await webAuthExists(authDir)
|
||||
: false;
|
||||
const authAgeMs = linked && authDir ? getWebAuthAgeMs(authDir) : null;
|
||||
const self =
|
||||
linked && authDir ? readWebSelfId(authDir) : { e164: null, jid: null };
|
||||
const self = linked && authDir ? readWebSelfId(authDir) : { e164: null, jid: null };
|
||||
return {
|
||||
configured: linked,
|
||||
linked,
|
||||
@@ -444,8 +412,7 @@ export const whatsappPlugin: ChannelPlugin<ResolvedWhatsAppAccount> = {
|
||||
allowFrom: account.allowFrom,
|
||||
};
|
||||
},
|
||||
resolveAccountState: ({ configured }) =>
|
||||
configured ? "linked" : "not linked",
|
||||
resolveAccountState: ({ configured }) => (configured ? "linked" : "not linked"),
|
||||
logSelfId: ({ account, runtime, includeChannelPrefix }) => {
|
||||
logWebSelfId(account.authDir, runtime, includeChannelPrefix);
|
||||
},
|
||||
@@ -466,8 +433,7 @@ export const whatsappPlugin: ChannelPlugin<ResolvedWhatsAppAccount> = {
|
||||
ctx.runtime,
|
||||
ctx.abortSignal,
|
||||
{
|
||||
statusSink: (next) =>
|
||||
ctx.setStatus({ accountId: ctx.accountId, ...next }),
|
||||
statusSink: (next) => ctx.setStatus({ accountId: ctx.accountId, ...next }),
|
||||
accountId: account.accountId,
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user