chore: Enable "curly" rule to avoid single-statement if confusion/errors.

This commit is contained in:
cpojer
2026-01-31 16:19:20 +09:00
parent 009b16fab8
commit 5ceff756e1
1266 changed files with 27871 additions and 9393 deletions

View File

@@ -15,14 +15,28 @@ export type AckReactionGateParams = {
export function shouldAckReaction(params: AckReactionGateParams): boolean {
const scope = params.scope ?? "group-mentions";
if (scope === "off" || scope === "none") return false;
if (scope === "all") return true;
if (scope === "direct") return params.isDirect;
if (scope === "group-all") return params.isGroup;
if (scope === "off" || scope === "none") {
return false;
}
if (scope === "all") {
return true;
}
if (scope === "direct") {
return params.isDirect;
}
if (scope === "group-all") {
return params.isGroup;
}
if (scope === "group-mentions") {
if (!params.isMentionableGroup) return false;
if (!params.requireMention) return false;
if (!params.canDetectMention) return false;
if (!params.isMentionableGroup) {
return false;
}
if (!params.requireMention) {
return false;
}
if (!params.canDetectMention) {
return false;
}
return params.effectiveWasMentioned || params.shouldBypassMention === true;
}
return false;
@@ -37,11 +51,21 @@ export function shouldAckReactionForWhatsApp(params: {
wasMentioned: boolean;
groupActivated: boolean;
}): boolean {
if (!params.emoji) return false;
if (params.isDirect) return params.directEnabled;
if (!params.isGroup) return false;
if (params.groupMode === "never") return false;
if (params.groupMode === "always") return true;
if (!params.emoji) {
return false;
}
if (params.isDirect) {
return params.directEnabled;
}
if (!params.isGroup) {
return false;
}
if (params.groupMode === "never") {
return false;
}
if (params.groupMode === "always") {
return true;
}
return shouldAckReaction({
scope: "group-mentions",
isDirect: false,
@@ -61,11 +85,19 @@ export function removeAckReactionAfterReply(params: {
remove: () => Promise<void>;
onError?: (err: unknown) => void;
}) {
if (!params.removeAfterReply) return;
if (!params.ackReactionPromise) return;
if (!params.ackReactionValue) return;
if (!params.removeAfterReply) {
return;
}
if (!params.ackReactionPromise) {
return;
}
if (!params.ackReactionValue) {
return;
}
void params.ackReactionPromise.then((didAck) => {
if (!didAck) return;
if (!didAck) {
return;
}
params.remove().catch((err) => params.onError?.(err));
});
}

View File

@@ -8,9 +8,13 @@ export function mergeAllowlist(params: {
const merged: string[] = [];
const push = (value: string) => {
const normalized = value.trim();
if (!normalized) return;
if (!normalized) {
return;
}
const key = normalized.toLowerCase();
if (seen.has(key)) return;
if (seen.has(key)) {
return;
}
seen.add(key);
merged.push(normalized);
};

View File

@@ -25,7 +25,9 @@ export function resolveChannelMatchConfig<
TEntry,
TResult extends { matchKey?: string; matchSource?: ChannelMatchSource },
>(match: ChannelEntryMatch<TEntry>, resolveEntry: (entry: TEntry) => TResult): TResult | null {
if (!match.entry) return null;
if (!match.entry) {
return null;
}
return applyChannelMatchMeta(resolveEntry(match.entry), match);
}
@@ -42,9 +44,13 @@ export function buildChannelKeyCandidates(...keys: Array<string | undefined | nu
const seen = new Set<string>();
const candidates: string[] = [];
for (const key of keys) {
if (typeof key !== "string") continue;
if (typeof key !== "string") {
continue;
}
const trimmed = key.trim();
if (!trimmed || seen.has(trimmed)) continue;
if (!trimmed || seen.has(trimmed)) {
continue;
}
seen.add(trimmed);
candidates.push(trimmed);
}
@@ -59,7 +65,9 @@ export function resolveChannelEntryMatch<T>(params: {
const entries = params.entries ?? {};
const match: ChannelEntryMatch<T> = {};
for (const key of params.keys) {
if (!Object.prototype.hasOwnProperty.call(entries, key)) continue;
if (!Object.prototype.hasOwnProperty.call(entries, key)) {
continue;
}
match.entry = entries[key];
match.key = key;
break;
@@ -161,8 +169,14 @@ export function resolveNestedAllowlistDecision(params: {
innerConfigured: boolean;
innerMatched: boolean;
}): boolean {
if (!params.outerConfigured) return true;
if (!params.outerMatched) return false;
if (!params.innerConfigured) return true;
if (!params.outerConfigured) {
return true;
}
if (!params.outerMatched) {
return false;
}
if (!params.innerConfigured) {
return true;
}
return params.innerMatched;
}

View File

@@ -2,9 +2,17 @@ export type NormalizedChatType = "direct" | "group" | "channel";
export function normalizeChatType(raw?: string): NormalizedChatType | undefined {
const value = raw?.trim().toLowerCase();
if (!value) return undefined;
if (value === "direct" || value === "dm") return "direct";
if (value === "group") return "group";
if (value === "channel") return "channel";
if (!value) {
return undefined;
}
if (value === "direct" || value === "dm") {
return "direct";
}
if (value === "group") {
return "group";
}
if (value === "channel") {
return "channel";
}
return undefined;
}

View File

@@ -13,10 +13,16 @@ export function resolveCommandAuthorizedFromAuthorizers(params: {
const { useAccessGroups, authorizers } = params;
const mode = params.modeWhenAccessGroupsOff ?? "allow";
if (!useAccessGroups) {
if (mode === "allow") return true;
if (mode === "deny") return false;
if (mode === "allow") {
return true;
}
if (mode === "deny") {
return false;
}
const anyConfigured = authorizers.some((entry) => entry.configured);
if (!anyConfigured) return true;
if (!anyConfigured) {
return true;
}
return authorizers.some((entry) => entry.configured && entry.allowed);
}
return authorizers.some((entry) => entry.configured && entry.allowed);

View File

@@ -3,23 +3,33 @@ import { normalizeChatType } from "./chat-type.js";
function extractConversationId(from?: string): string | undefined {
const trimmed = from?.trim();
if (!trimmed) return undefined;
if (!trimmed) {
return undefined;
}
const parts = trimmed.split(":").filter(Boolean);
return parts.length > 0 ? parts[parts.length - 1] : trimmed;
}
function shouldAppendId(id: string): boolean {
if (/^[0-9]+$/.test(id)) return true;
if (id.includes("@g.us")) return true;
if (/^[0-9]+$/.test(id)) {
return true;
}
if (id.includes("@g.us")) {
return true;
}
return false;
}
export function resolveConversationLabel(ctx: MsgContext): string | undefined {
const explicit = ctx.ConversationLabel?.trim();
if (explicit) return explicit;
if (explicit) {
return explicit;
}
const threadLabel = ctx.ThreadLabel?.trim();
if (threadLabel) return threadLabel;
if (threadLabel) {
return threadLabel;
}
const chatType = normalizeChatType(ctx.ChatType);
if (chatType === "direct") {
@@ -32,14 +42,28 @@ export function resolveConversationLabel(ctx: MsgContext): string | undefined {
ctx.GroupSpace?.trim() ||
ctx.From?.trim() ||
"";
if (!base) return undefined;
if (!base) {
return undefined;
}
const id = extractConversationId(ctx.From);
if (!id) return base;
if (!shouldAppendId(id)) return base;
if (base === id) return base;
if (base.includes(id)) return base;
if (base.toLowerCase().includes(" id:")) return base;
if (base.startsWith("#") || base.startsWith("@")) return base;
if (!id) {
return base;
}
if (!shouldAppendId(id)) {
return base;
}
if (base === id) {
return base;
}
if (base.includes(id)) {
return base;
}
if (base.toLowerCase().includes(" id:")) {
return base;
}
if (base.startsWith("#") || base.startsWith("@")) {
return base;
}
return `${base} id:${id}`;
}

View File

@@ -157,7 +157,9 @@ const DOCKS: Record<ChatChannelId, ChannelDock> = {
mentions: {
stripPatterns: ({ ctx }) => {
const selfE164 = (ctx.To ?? "").replace(/^whatsapp:/, "");
if (!selfE164) return [];
if (!selfE164) {
return [];
}
const escaped = escapeRegExp(selfE164);
return [escaped, `@${escaped}`];
},
@@ -403,9 +405,13 @@ function listPluginDockEntries(): Array<{ id: ChannelId; dock: ChannelDock; orde
for (const entry of registry.channels) {
const plugin = entry.plugin;
const id = String(plugin.id).trim();
if (!id || seen.has(id)) continue;
if (!id || seen.has(id)) {
continue;
}
seen.add(id);
if (CHAT_CHANNEL_ORDER.includes(plugin.id as ChatChannelId)) continue;
if (CHAT_CHANNEL_ORDER.includes(plugin.id as ChatChannelId)) {
continue;
}
const dock = entry.dock ?? buildDockFromPlugin(plugin);
entries.push({ id: plugin.id, dock, order: plugin.meta.order });
}
@@ -425,7 +431,9 @@ export function listChannelDocks(): ChannelDock[] {
const indexB = CHAT_CHANNEL_ORDER.indexOf(b.id as ChatChannelId);
const orderA = a.order ?? (indexA === -1 ? 999 : indexA);
const orderB = b.order ?? (indexB === -1 ? 999 : indexB);
if (orderA !== orderB) return orderA - orderB;
if (orderA !== orderB) {
return orderA - orderB;
}
return String(a.id).localeCompare(String(b.id));
});
return combined.map((entry) => entry.dock);
@@ -433,9 +441,13 @@ export function listChannelDocks(): ChannelDock[] {
export function getChannelDock(id: ChannelId): ChannelDock | undefined {
const core = DOCKS[id as ChatChannelId];
if (core) return core;
if (core) {
return core;
}
const registry = requireActivePluginRegistry();
const pluginEntry = registry.channels.find((entry) => entry.plugin.id === id);
if (!pluginEntry) return undefined;
if (!pluginEntry) {
return undefined;
}
return pluginEntry.dock ?? buildDockFromPlugin(pluginEntry.plugin);
}

View File

@@ -25,7 +25,9 @@ function resolveLocation(location: NormalizedLocation): ResolvedLocation {
}
function formatAccuracy(accuracy?: number): string {
if (!Number.isFinite(accuracy)) return "";
if (!Number.isFinite(accuracy)) {
return "";
}
return ` ±${Math.round(accuracy ?? 0)}m`;
}

View File

@@ -8,10 +8,14 @@ export const discordMessageActions: ChannelMessageActionAdapter = {
const accounts = listEnabledDiscordAccounts(cfg).filter(
(account) => account.tokenSource !== "none",
);
if (accounts.length === 0) return [];
if (accounts.length === 0) {
return [];
}
const gate = createActionGate(cfg.channels?.discord?.actions);
const actions = new Set<ChannelMessageActionName>(["send"]);
if (gate("polls")) actions.add("poll");
if (gate("polls")) {
actions.add("poll");
}
if (gate("reactions")) {
actions.add("react");
actions.add("reactions");
@@ -26,19 +30,35 @@ export const discordMessageActions: ChannelMessageActionAdapter = {
actions.add("unpin");
actions.add("list-pins");
}
if (gate("permissions")) actions.add("permissions");
if (gate("permissions")) {
actions.add("permissions");
}
if (gate("threads")) {
actions.add("thread-create");
actions.add("thread-list");
actions.add("thread-reply");
}
if (gate("search")) actions.add("search");
if (gate("stickers")) actions.add("sticker");
if (gate("memberInfo")) actions.add("member-info");
if (gate("roleInfo")) actions.add("role-info");
if (gate("reactions")) actions.add("emoji-list");
if (gate("emojiUploads")) actions.add("emoji-upload");
if (gate("stickerUploads")) actions.add("sticker-upload");
if (gate("search")) {
actions.add("search");
}
if (gate("stickers")) {
actions.add("sticker");
}
if (gate("memberInfo")) {
actions.add("member-info");
}
if (gate("roleInfo")) {
actions.add("role-info");
}
if (gate("reactions")) {
actions.add("emoji-list");
}
if (gate("emojiUploads")) {
actions.add("emoji-upload");
}
if (gate("stickerUploads")) {
actions.add("sticker-upload");
}
if (gate("roles", false)) {
actions.add("role-add");
actions.add("role-remove");
@@ -56,7 +76,9 @@ export const discordMessageActions: ChannelMessageActionAdapter = {
actions.add("category-edit");
actions.add("category-delete");
}
if (gate("voiceStatus")) actions.add("voice-status");
if (gate("voiceStatus")) {
actions.add("voice-status");
}
if (gate("events")) {
actions.add("event-list");
actions.add("event-create");

View File

@@ -12,8 +12,12 @@ import { resolveDiscordChannelId } from "../../../../discord/targets.js";
const providerId = "discord";
function readParentIdParam(params: Record<string, unknown>): string | null | undefined {
if (params.clearParent === true) return null;
if (params.parentId === null) return null;
if (params.clearParent === true) {
return null;
}
if (params.parentId === null) {
return null;
}
return readStringParam(params, "parentId");
}
@@ -219,7 +223,9 @@ export async function handleDiscordMessageAction(
resolveChannelId,
readParentIdParam,
});
if (adminResult !== undefined) return adminResult;
if (adminResult !== undefined) {
return adminResult;
}
throw new Error(`Action ${String(action)} is not supported for provider ${providerId}.`);
}

View File

@@ -9,9 +9,13 @@ const GROUP_PREFIX = "group:";
function normalizeSignalReactionRecipient(raw: string): string {
const trimmed = raw.trim();
if (!trimmed) return trimmed;
if (!trimmed) {
return trimmed;
}
const withoutSignal = trimmed.replace(/^signal:/i, "").trim();
if (!withoutSignal) return withoutSignal;
if (!withoutSignal) {
return withoutSignal;
}
if (withoutSignal.toLowerCase().startsWith("uuid:")) {
return withoutSignal.slice("uuid:".length).trim();
}
@@ -20,9 +24,13 @@ function normalizeSignalReactionRecipient(raw: string): string {
function resolveSignalReactionTarget(raw: string): { recipient?: string; groupId?: string } {
const trimmed = raw.trim();
if (!trimmed) return {};
if (!trimmed) {
return {};
}
const withoutSignal = trimmed.replace(/^signal:/i, "").trim();
if (!withoutSignal) return {};
if (!withoutSignal) {
return {};
}
if (withoutSignal.toLowerCase().startsWith(GROUP_PREFIX)) {
const groupId = withoutSignal.slice(GROUP_PREFIX.length).trim();
return groupId ? { groupId } : {};
@@ -33,9 +41,13 @@ function resolveSignalReactionTarget(raw: string): { recipient?: string; groupId
export const signalMessageActions: ChannelMessageActionAdapter = {
listActions: ({ cfg }) => {
const accounts = listEnabledSignalAccounts(cfg);
if (accounts.length === 0) return [];
if (accounts.length === 0) {
return [];
}
const configuredAccounts = accounts.filter((account) => account.configured);
if (configuredAccounts.length === 0) return [];
if (configuredAccounts.length === 0) {
return [];
}
const actions = new Set<ChannelMessageActionName>(["send"]);
@@ -105,7 +117,9 @@ export const signalMessageActions: ChannelMessageActionAdapter = {
}
if (remove) {
if (!emoji) throw new Error("Emoji required to remove reaction.");
if (!emoji) {
throw new Error("Emoji required to remove reaction.");
}
await removeReactionSignal(target.recipient ?? "", timestamp, emoji, {
accountId: accountId ?? undefined,
groupId: target.groupId,
@@ -115,7 +129,9 @@ export const signalMessageActions: ChannelMessageActionAdapter = {
return jsonResult({ ok: true, removed: emoji });
}
if (!emoji) throw new Error("Emoji required to add reaction.");
if (!emoji) {
throw new Error("Emoji required to add reaction.");
}
await sendReactionSignal(target.recipient ?? "", timestamp, emoji, {
accountId: accountId ?? undefined,
groupId: target.groupId,

View File

@@ -42,12 +42,20 @@ export const telegramMessageActions: ChannelMessageActionAdapter = {
const accounts = listEnabledTelegramAccounts(cfg).filter(
(account) => account.tokenSource !== "none",
);
if (accounts.length === 0) return [];
if (accounts.length === 0) {
return [];
}
const gate = createActionGate(cfg.channels?.telegram?.actions);
const actions = new Set<ChannelMessageActionName>(["send"]);
if (gate("reactions")) actions.add("react");
if (gate("deleteMessage")) actions.add("delete");
if (gate("editMessage")) actions.add("edit");
if (gate("reactions")) {
actions.add("react");
}
if (gate("deleteMessage")) {
actions.add("delete");
}
if (gate("editMessage")) {
actions.add("edit");
}
if (gate("sticker", false)) {
actions.add("sticker");
actions.add("sticker-search");
@@ -58,16 +66,22 @@ export const telegramMessageActions: ChannelMessageActionAdapter = {
const accounts = listEnabledTelegramAccounts(cfg).filter(
(account) => account.tokenSource !== "none",
);
if (accounts.length === 0) return false;
if (accounts.length === 0) {
return false;
}
return accounts.some((account) =>
isTelegramInlineButtonsEnabled({ cfg, accountId: account.accountId }),
);
},
extractToolSend: ({ args }) => {
const action = typeof args.action === "string" ? args.action.trim() : "";
if (action !== "sendMessage") return null;
if (action !== "sendMessage") {
return null;
}
const to = typeof args.to === "string" ? args.to : undefined;
if (!to) return null;
if (!to) {
return null;
}
const accountId = typeof args.accountId === "string" ? args.accountId.trim() : undefined;
return { to, accountId };
},

View File

@@ -70,15 +70,21 @@ function parseCatalogEntries(raw: unknown): ExternalCatalogEntry[] {
if (Array.isArray(raw)) {
return raw.filter((entry): entry is ExternalCatalogEntry => isRecord(entry));
}
if (!isRecord(raw)) return [];
if (!isRecord(raw)) {
return [];
}
const list = raw.entries ?? raw.packages ?? raw.plugins;
if (!Array.isArray(list)) return [];
if (!Array.isArray(list)) {
return [];
}
return list.filter((entry): entry is ExternalCatalogEntry => isRecord(entry));
}
function splitEnvPaths(value: string): string[] {
const trimmed = value.trim();
if (!trimmed) return [];
if (!trimmed) {
return [];
}
return trimmed
.split(/[;,]/g)
.flatMap((chunk) => chunk.split(path.delimiter))
@@ -104,7 +110,9 @@ function loadExternalCatalogEntries(options: CatalogOptions): ExternalCatalogEnt
const entries: ExternalCatalogEntry[] = [];
for (const rawPath of paths) {
const resolved = resolveUserPath(rawPath);
if (!fs.existsSync(resolved)) continue;
if (!fs.existsSync(resolved)) {
continue;
}
try {
const payload = JSON.parse(fs.readFileSync(resolved, "utf-8")) as unknown;
entries.push(...parseCatalogEntries(payload));
@@ -120,7 +128,9 @@ function toChannelMeta(params: {
id: string;
}): ChannelMeta | null {
const label = params.channel.label?.trim();
if (!label) return null;
if (!label) {
return null;
}
const selectionLabel = params.channel.selectionLabel?.trim() || label;
const detailLabel = params.channel.detailLabel?.trim();
const docsPath = params.channel.docsPath?.trim() || `/channels/${params.id}`;
@@ -170,7 +180,9 @@ function resolveInstallInfo(params: {
workspaceDir?: string;
}): ChannelPluginCatalogEntry["install"] | null {
const npmSpec = params.manifest.install?.npmSpec?.trim() ?? params.packageName?.trim();
if (!npmSpec) return null;
if (!npmSpec) {
return null;
}
let localPath = params.manifest.install?.localPath?.trim() || undefined;
if (!localPath && params.workspaceDir && params.packageDir) {
localPath = path.relative(params.workspaceDir, params.packageDir) || undefined;
@@ -190,18 +202,26 @@ function buildCatalogEntry(candidate: {
packageManifest?: OpenClawPackageManifest;
}): ChannelPluginCatalogEntry | null {
const manifest = candidate.packageManifest;
if (!manifest?.channel) return null;
if (!manifest?.channel) {
return null;
}
const id = manifest.channel.id?.trim();
if (!id) return null;
if (!id) {
return null;
}
const meta = toChannelMeta({ channel: manifest.channel, id });
if (!meta) return null;
if (!meta) {
return null;
}
const install = resolveInstallInfo({
manifest,
packageName: candidate.packageName,
packageDir: candidate.packageDir,
workspaceDir: candidate.workspaceDir,
});
if (!install) return null;
if (!install) {
return null;
}
return { id, meta, install };
}
@@ -249,7 +269,9 @@ export function listChannelPluginCatalogEntries(
for (const candidate of discovery.candidates) {
const entry = buildCatalogEntry(candidate);
if (!entry) continue;
if (!entry) {
continue;
}
const priority = ORIGIN_PRIORITY[candidate.origin] ?? 99;
const existing = resolved.get(entry.id);
if (!existing || priority < existing.priority) {
@@ -271,7 +293,9 @@ export function listChannelPluginCatalogEntries(
.toSorted((a, b) => {
const orderA = a.meta.order ?? 999;
const orderB = b.meta.order ?? 999;
if (orderA !== orderB) return orderA - orderB;
if (orderA !== orderB) {
return orderA - orderB;
}
return a.meta.label.localeCompare(b.meta.label);
});
}
@@ -281,6 +305,8 @@ export function getChannelPluginCatalogEntry(
options: CatalogOptions = {},
): ChannelPluginCatalogEntry | undefined {
const trimmed = id.trim();
if (!trimmed) return undefined;
if (!trimmed) {
return undefined;
}
return listChannelPluginCatalogEntries(options).find((entry) => entry.id === trimmed);
}

View File

@@ -59,7 +59,9 @@ export function deleteAccountFromConfigSection(params: {
const accountKey = params.accountId || DEFAULT_ACCOUNT_ID;
const channels = params.cfg.channels as Record<string, unknown> | undefined;
const base = channels?.[params.sectionKey] as ChannelSection | undefined;
if (!base) return params.cfg;
if (!base) {
return params.cfg;
}
const baseAccounts =
base.accounts && typeof base.accounts === "object" ? { ...base.accounts } : undefined;
@@ -83,7 +85,9 @@ export function deleteAccountFromConfigSection(params: {
delete baseAccounts[accountKey];
const baseRecord = { ...(base as Record<string, unknown>) };
for (const field of params.clearBaseFields ?? []) {
if (field in baseRecord) baseRecord[field] = undefined;
if (field in baseRecord) {
baseRecord[field] = undefined;
}
}
return {
...params.cfg,

View File

@@ -8,8 +8,12 @@ type ChannelConfigWithAccounts = {
};
function resolveAccountConfig(accounts: ChannelConfigWithAccounts["accounts"], accountId: string) {
if (!accounts || typeof accounts !== "object") return undefined;
if (accountId in accounts) return accounts[accountId];
if (!accounts || typeof accounts !== "object") {
return undefined;
}
if (accountId in accounts) {
return accounts[accountId];
}
const matchKey = Object.keys(accounts).find(
(key) => key.toLowerCase() === accountId.toLowerCase(),
);
@@ -21,10 +25,14 @@ export function resolveChannelConfigWrites(params: {
channelId?: ChannelId | null;
accountId?: string | null;
}): boolean {
if (!params.channelId) return true;
if (!params.channelId) {
return true;
}
const channels = params.cfg.channels as Record<string, ChannelConfigWithAccounts> | undefined;
const channelConfig = channels?.[params.channelId];
if (!channelConfig) return true;
if (!channelConfig) {
return true;
}
const accountId = normalizeAccountId(params.accountId);
const accountConfig = resolveAccountConfig(channelConfig.accounts, accountId);
const value = accountConfig?.configWrites ?? channelConfig.configWrites;

View File

@@ -23,17 +23,23 @@ export async function listSlackDirectoryPeersFromConfig(
for (const entry of account.dm?.allowFrom ?? []) {
const raw = String(entry).trim();
if (!raw || raw === "*") continue;
if (!raw || raw === "*") {
continue;
}
ids.add(raw);
}
for (const id of Object.keys(account.config.dms ?? {})) {
const trimmed = id.trim();
if (trimmed) ids.add(trimmed);
if (trimmed) {
ids.add(trimmed);
}
}
for (const channel of Object.values(account.config.channels ?? {})) {
for (const user of channel.users ?? []) {
const raw = String(user).trim();
if (raw) ids.add(raw);
if (raw) {
ids.add(raw);
}
}
}
@@ -43,7 +49,9 @@ export async function listSlackDirectoryPeersFromConfig(
.map((raw) => {
const mention = raw.match(/^<@([A-Z0-9]+)>$/i);
const normalizedUserId = (mention?.[1] ?? raw).replace(/^(slack|user):/i, "").trim();
if (!normalizedUserId) return null;
if (!normalizedUserId) {
return null;
}
const target = `user:${normalizedUserId}`;
return normalizeSlackMessagingTarget(target) ?? target.toLowerCase();
})
@@ -78,22 +86,30 @@ export async function listDiscordDirectoryPeersFromConfig(
for (const entry of account.config.dm?.allowFrom ?? []) {
const raw = String(entry).trim();
if (!raw || raw === "*") continue;
if (!raw || raw === "*") {
continue;
}
ids.add(raw);
}
for (const id of Object.keys(account.config.dms ?? {})) {
const trimmed = id.trim();
if (trimmed) ids.add(trimmed);
if (trimmed) {
ids.add(trimmed);
}
}
for (const guild of Object.values(account.config.guilds ?? {})) {
for (const entry of guild.users ?? []) {
const raw = String(entry).trim();
if (raw) ids.add(raw);
if (raw) {
ids.add(raw);
}
}
for (const channel of Object.values(guild.channels ?? {})) {
for (const user of channel.users ?? []) {
const raw = String(user).trim();
if (raw) ids.add(raw);
if (raw) {
ids.add(raw);
}
}
}
}
@@ -104,7 +120,9 @@ export async function listDiscordDirectoryPeersFromConfig(
.map((raw) => {
const mention = raw.match(/^<@!?(\d+)>$/);
const cleaned = (mention?.[1] ?? raw).replace(/^(discord|user):/i, "").trim();
if (!/^\d+$/.test(cleaned)) return null;
if (!/^\d+$/.test(cleaned)) {
return null;
}
return `user:${cleaned}`;
})
.filter((id): id is string => Boolean(id))
@@ -122,7 +140,9 @@ export async function listDiscordDirectoryGroupsFromConfig(
for (const guild of Object.values(account.config.guilds ?? {})) {
for (const channelId of Object.keys(guild.channels ?? {})) {
const trimmed = channelId.trim();
if (trimmed) ids.add(trimmed);
if (trimmed) {
ids.add(trimmed);
}
}
}
@@ -132,7 +152,9 @@ export async function listDiscordDirectoryGroupsFromConfig(
.map((raw) => {
const mention = raw.match(/^<#(\d+)>$/);
const cleaned = (mention?.[1] ?? raw).replace(/^(discord|channel|group):/i, "").trim();
if (!/^\d+$/.test(cleaned)) return null;
if (!/^\d+$/.test(cleaned)) {
return null;
}
return `channel:${cleaned}`;
})
.filter((id): id is string => Boolean(id))
@@ -160,8 +182,12 @@ export async function listTelegramDirectoryPeersFromConfig(
)
.map((entry) => {
const trimmed = entry.trim();
if (!trimmed) return null;
if (/^-?\d+$/.test(trimmed)) return trimmed;
if (!trimmed) {
return null;
}
if (/^-?\d+$/.test(trimmed)) {
return trimmed;
}
const withAt = trimmed.startsWith("@") ? trimmed : `@${trimmed}`;
return withAt;
})

View File

@@ -24,9 +24,13 @@ type GroupMentionParams = {
};
function normalizeDiscordSlug(value?: string | null) {
if (!value) return "";
if (!value) {
return "";
}
let text = value.trim().toLowerCase();
if (!text) return "";
if (!text) {
return "";
}
text = text.replace(/^[@#]+/, "");
text = text.replace(/[\s_]+/g, "-");
text = text.replace(/[^a-z0-9-]+/g, "-");
@@ -36,7 +40,9 @@ function normalizeDiscordSlug(value?: string | null) {
function normalizeSlackSlug(raw?: string | null) {
const trimmed = raw?.trim().toLowerCase() ?? "";
if (!trimmed) return "";
if (!trimmed) {
return "";
}
const dashed = trimmed.replace(/\s+/g, "-");
const cleaned = dashed.replace(/[^a-z0-9#@._+-]+/g, "-");
return cleaned.replace(/-{2,}/g, "-").replace(/^[-.]+|[-.]+$/g, "");
@@ -44,7 +50,9 @@ function normalizeSlackSlug(raw?: string | null) {
function parseTelegramGroupId(value?: string | null) {
const raw = value?.trim() ?? "";
if (!raw) return { chatId: undefined, topicId: undefined };
if (!raw) {
return { chatId: undefined, topicId: undefined };
}
const parts = raw.split(":").filter(Boolean);
if (
parts.length >= 3 &&
@@ -66,7 +74,9 @@ function resolveTelegramRequireMention(params: {
topicId?: string;
}): boolean | undefined {
const { cfg, chatId, topicId } = params;
if (!chatId) return undefined;
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;
@@ -88,16 +98,24 @@ function resolveTelegramRequireMention(params: {
}
function resolveDiscordGuildEntry(guilds: DiscordConfig["guilds"], groupSpace?: string | null) {
if (!guilds || Object.keys(guilds).length === 0) return null;
if (!guilds || Object.keys(guilds).length === 0) {
return null;
}
const space = groupSpace?.trim() ?? "";
if (space && guilds[space]) return guilds[space];
if (space && guilds[space]) {
return guilds[space];
}
const normalized = normalizeDiscordSlug(space);
if (normalized && guilds[normalized]) return guilds[normalized];
if (normalized && guilds[normalized]) {
return guilds[normalized];
}
if (normalized) {
const match = Object.values(guilds).find(
(entry) => normalizeDiscordSlug(entry?.slug ?? undefined) === normalized,
);
if (match) return match;
if (match) {
return match;
}
}
return guilds["*"] ?? null;
}
@@ -111,7 +129,9 @@ export function resolveTelegramGroupRequireMention(
chatId,
topicId,
});
if (typeof requireMention === "boolean") return requireMention;
if (typeof requireMention === "boolean") {
return requireMention;
}
return resolveChannelGroupRequireMention({
cfg: params.cfg,
channel: "telegram",
@@ -194,7 +214,9 @@ export function resolveSlackGroupRequireMention(params: GroupMentionParams): boo
});
const channels = account.channels ?? {};
const keys = Object.keys(channels);
if (keys.length === 0) return true;
if (keys.length === 0) {
return true;
}
const channelId = params.groupId?.trim();
const groupChannel = params.groupChannel;
const channelName = groupChannel?.replace(/^#/, "");
@@ -299,8 +321,12 @@ export function resolveDiscordGroupToolPolicy(
senderUsername: params.senderUsername,
senderE164: params.senderE164,
});
if (senderPolicy) return senderPolicy;
if (entry?.tools) return entry.tools;
if (senderPolicy) {
return senderPolicy;
}
if (entry?.tools) {
return entry.tools;
}
}
const guildSenderPolicy = resolveToolsBySender({
toolsBySender: guildEntry?.toolsBySender,
@@ -309,8 +335,12 @@ export function resolveDiscordGroupToolPolicy(
senderUsername: params.senderUsername,
senderE164: params.senderE164,
});
if (guildSenderPolicy) return guildSenderPolicy;
if (guildEntry?.tools) return guildEntry.tools;
if (guildSenderPolicy) {
return guildSenderPolicy;
}
if (guildEntry?.tools) {
return guildEntry.tools;
}
return undefined;
}
@@ -323,7 +353,9 @@ export function resolveSlackGroupToolPolicy(
});
const channels = account.channels ?? {};
const keys = Object.keys(channels);
if (keys.length === 0) return undefined;
if (keys.length === 0) {
return undefined;
}
const channelId = params.groupId?.trim();
const groupChannel = params.groupChannel;
const channelName = groupChannel?.replace(/^#/, "");
@@ -351,8 +383,12 @@ export function resolveSlackGroupToolPolicy(
senderUsername: params.senderUsername,
senderE164: params.senderE164,
});
if (senderPolicy) return senderPolicy;
if (resolved?.tools) return resolved.tools;
if (senderPolicy) {
return senderPolicy;
}
if (resolved?.tools) {
return resolved.tools;
}
return undefined;
}

View File

@@ -19,7 +19,9 @@ function dedupeChannels(channels: ChannelPlugin[]): ChannelPlugin[] {
const resolved: ChannelPlugin[] = [];
for (const plugin of channels) {
const id = String(plugin.id).trim();
if (!id || seen.has(id)) continue;
if (!id || seen.has(id)) {
continue;
}
seen.add(id);
resolved.push(plugin);
}
@@ -33,14 +35,18 @@ export function listChannelPlugins(): ChannelPlugin[] {
const indexB = CHAT_CHANNEL_ORDER.indexOf(b.id as ChatChannelId);
const orderA = a.meta.order ?? (indexA === -1 ? 999 : indexA);
const orderB = b.meta.order ?? (indexB === -1 ? 999 : indexB);
if (orderA !== orderB) return orderA - orderB;
if (orderA !== orderB) {
return orderA - orderB;
}
return a.id.localeCompare(b.id);
});
}
export function getChannelPlugin(id: ChannelId): ChannelPlugin | undefined {
const resolvedId = String(id).trim();
if (!resolvedId) return undefined;
if (!resolvedId) {
return undefined;
}
return listChannelPlugins().find((plugin) => plugin.id === resolvedId);
}

View File

@@ -6,7 +6,9 @@ const cache = new Map<ChannelId, ChannelPlugin>();
let lastRegistry: PluginRegistry | null = null;
function ensureCacheForRegistry(registry: PluginRegistry | null) {
if (registry === lastRegistry) return;
if (registry === lastRegistry) {
return;
}
cache.clear();
lastRegistry = registry;
}
@@ -15,7 +17,9 @@ export async function loadChannelPlugin(id: ChannelId): Promise<ChannelPlugin |
const registry = getActivePluginRegistry();
ensureCacheForRegistry(registry);
const cached = cache.get(id);
if (cached) return cached;
if (cached) {
return cached;
}
const pluginEntry = registry?.channels.find((entry) => entry.plugin.id === id);
if (pluginEntry) {
cache.set(id, pluginEntry.plugin);

View File

@@ -15,7 +15,9 @@ export function resolveChannelMediaMaxBytes(params: {
cfg: params.cfg,
accountId,
});
if (channelLimit) return channelLimit * MB;
if (channelLimit) {
return channelLimit * MB;
}
if (params.cfg.agents?.defaults?.mediaMaxMb) {
return params.cfg.agents.defaults.mediaMaxMb * MB;
}

View File

@@ -8,22 +8,30 @@ export function listChannelMessageActions(cfg: OpenClawConfig): ChannelMessageAc
const actions = new Set<ChannelMessageActionName>(["send", "broadcast"]);
for (const plugin of listChannelPlugins()) {
const list = plugin.actions?.listActions?.({ cfg });
if (!list) continue;
for (const action of list) actions.add(action);
if (!list) {
continue;
}
for (const action of list) {
actions.add(action);
}
}
return Array.from(actions);
}
export function supportsChannelMessageButtons(cfg: OpenClawConfig): boolean {
for (const plugin of listChannelPlugins()) {
if (plugin.actions?.supportsButtons?.({ cfg })) return true;
if (plugin.actions?.supportsButtons?.({ cfg })) {
return true;
}
}
return false;
}
export function supportsChannelMessageCards(cfg: OpenClawConfig): boolean {
for (const plugin of listChannelPlugins()) {
if (plugin.actions?.supportsCards?.({ cfg })) return true;
if (plugin.actions?.supportsCards?.({ cfg })) {
return true;
}
}
return false;
}
@@ -32,7 +40,9 @@ export async function dispatchChannelMessageAction(
ctx: ChannelMessageActionContext,
): Promise<AgentToolResult<unknown> | null> {
const plugin = getChannelPlugin(ctx.channel);
if (!plugin?.actions?.handleAction) return null;
if (!plugin?.actions?.handleAction) {
return null;
}
if (plugin.actions.supportsAction && !plugin.actions.supportsAction({ action: ctx.action })) {
return null;
}

View File

@@ -8,9 +8,17 @@ export function normalizeDiscordMessagingTarget(raw: string): string | undefined
export function looksLikeDiscordTargetId(raw: string): boolean {
const trimmed = raw.trim();
if (!trimmed) return false;
if (/^<@!?\d+>$/.test(trimmed)) return true;
if (/^(user|channel|discord):/i.test(trimmed)) return true;
if (/^\d{6,}$/.test(trimmed)) return true;
if (!trimmed) {
return false;
}
if (/^<@!?\d+>$/.test(trimmed)) {
return true;
}
if (/^(user|channel|discord):/i.test(trimmed)) {
return true;
}
if (/^\d{6,}$/.test(trimmed)) {
return true;
}
return false;
}

View File

@@ -7,7 +7,9 @@ const CHAT_TARGET_PREFIX_RE =
export function normalizeIMessageMessagingTarget(raw: string): string | undefined {
const trimmed = raw.trim();
if (!trimmed) return undefined;
if (!trimmed) {
return undefined;
}
// Preserve service prefix if present (e.g., "sms:+1555" → "sms:+15551234567")
const lower = trimmed.toLowerCase();
@@ -15,8 +17,12 @@ export function normalizeIMessageMessagingTarget(raw: string): string | undefine
if (lower.startsWith(prefix)) {
const remainder = trimmed.slice(prefix.length).trim();
const normalizedHandle = normalizeIMessageHandle(remainder);
if (!normalizedHandle) return undefined;
if (CHAT_TARGET_PREFIX_RE.test(normalizedHandle)) return normalizedHandle;
if (!normalizedHandle) {
return undefined;
}
if (CHAT_TARGET_PREFIX_RE.test(normalizedHandle)) {
return normalizedHandle;
}
return `${prefix}${normalizedHandle}`;
}
}
@@ -27,9 +33,17 @@ export function normalizeIMessageMessagingTarget(raw: string): string | undefine
export function looksLikeIMessageTargetId(raw: string): boolean {
const trimmed = raw.trim();
if (!trimmed) return false;
if (/^(imessage:|sms:|auto:)/i.test(trimmed)) return true;
if (CHAT_TARGET_PREFIX_RE.test(trimmed)) return true;
if (trimmed.includes("@")) return true;
if (!trimmed) {
return false;
}
if (/^(imessage:|sms:|auto:)/i.test(trimmed)) {
return true;
}
if (CHAT_TARGET_PREFIX_RE.test(trimmed)) {
return true;
}
if (trimmed.includes("@")) {
return true;
}
return /^\+?\d{3,}$/.test(trimmed);
}

View File

@@ -1,11 +1,15 @@
export function normalizeSignalMessagingTarget(raw: string): string | undefined {
const trimmed = raw.trim();
if (!trimmed) return undefined;
if (!trimmed) {
return undefined;
}
let normalized = trimmed;
if (normalized.toLowerCase().startsWith("signal:")) {
normalized = normalized.slice("signal:".length).trim();
}
if (!normalized) return undefined;
if (!normalized) {
return undefined;
}
const lower = normalized.toLowerCase();
if (lower.startsWith("group:")) {
const id = normalized.slice("group:".length).trim();
@@ -32,17 +36,25 @@ const UUID_COMPACT_PATTERN = /^[0-9a-f]{32}$/i;
export function looksLikeSignalTargetId(raw: string): boolean {
const trimmed = raw.trim();
if (!trimmed) return false;
if (/^(signal:)?(group:|username:|u:)/i.test(trimmed)) return true;
if (!trimmed) {
return false;
}
if (/^(signal:)?(group:|username:|u:)/i.test(trimmed)) {
return true;
}
if (/^(signal:)?uuid:/i.test(trimmed)) {
const stripped = trimmed
.replace(/^signal:/i, "")
.replace(/^uuid:/i, "")
.trim();
if (!stripped) return false;
if (!stripped) {
return false;
}
return UUID_PATTERN.test(stripped) || UUID_COMPACT_PATTERN.test(stripped);
}
// Accept UUIDs (used by signal-cli for reactions)
if (UUID_PATTERN.test(trimmed) || UUID_COMPACT_PATTERN.test(trimmed)) return true;
if (UUID_PATTERN.test(trimmed) || UUID_COMPACT_PATTERN.test(trimmed)) {
return true;
}
return /^\+?\d{3,}$/.test(trimmed);
}

View File

@@ -7,10 +7,20 @@ export function normalizeSlackMessagingTarget(raw: string): string | undefined {
export function looksLikeSlackTargetId(raw: string): boolean {
const trimmed = raw.trim();
if (!trimmed) return false;
if (/^<@([A-Z0-9]+)>$/i.test(trimmed)) return true;
if (/^(user|channel):/i.test(trimmed)) return true;
if (/^slack:/i.test(trimmed)) return true;
if (/^[@#]/.test(trimmed)) return true;
if (!trimmed) {
return false;
}
if (/^<@([A-Z0-9]+)>$/i.test(trimmed)) {
return true;
}
if (/^(user|channel):/i.test(trimmed)) {
return true;
}
if (/^slack:/i.test(trimmed)) {
return true;
}
if (/^[@#]/.test(trimmed)) {
return true;
}
return /^[CUWGD][A-Z0-9]{8,}$/i.test(trimmed);
}

View File

@@ -1,25 +1,39 @@
export function normalizeTelegramMessagingTarget(raw: string): string | undefined {
const trimmed = raw.trim();
if (!trimmed) return undefined;
if (!trimmed) {
return undefined;
}
let normalized = trimmed;
if (normalized.startsWith("telegram:")) {
normalized = normalized.slice("telegram:".length).trim();
} else if (normalized.startsWith("tg:")) {
normalized = normalized.slice("tg:".length).trim();
}
if (!normalized) return undefined;
if (!normalized) {
return undefined;
}
const tmeMatch =
/^https?:\/\/t\.me\/([A-Za-z0-9_]+)$/i.exec(normalized) ??
/^t\.me\/([A-Za-z0-9_]+)$/i.exec(normalized);
if (tmeMatch?.[1]) normalized = `@${tmeMatch[1]}`;
if (!normalized) return undefined;
if (tmeMatch?.[1]) {
normalized = `@${tmeMatch[1]}`;
}
if (!normalized) {
return undefined;
}
return `telegram:${normalized}`.toLowerCase();
}
export function looksLikeTelegramTargetId(raw: string): boolean {
const trimmed = raw.trim();
if (!trimmed) return false;
if (/^(telegram|tg):/i.test(trimmed)) return true;
if (trimmed.startsWith("@")) return true;
if (!trimmed) {
return false;
}
if (/^(telegram|tg):/i.test(trimmed)) {
return true;
}
if (trimmed.startsWith("@")) {
return true;
}
return /^-?\d{6,}$/.test(trimmed);
}

View File

@@ -2,14 +2,22 @@ import { normalizeWhatsAppTarget } from "../../../whatsapp/normalize.js";
export function normalizeWhatsAppMessagingTarget(raw: string): string | undefined {
const trimmed = raw.trim();
if (!trimmed) return undefined;
if (!trimmed) {
return undefined;
}
return normalizeWhatsAppTarget(trimmed) ?? undefined;
}
export function looksLikeWhatsAppTargetId(raw: string): boolean {
const trimmed = raw.trim();
if (!trimmed) return false;
if (/^whatsapp:/i.test(trimmed)) return true;
if (trimmed.includes("@")) return true;
if (!trimmed) {
return false;
}
if (/^whatsapp:/i.test(trimmed)) {
return true;
}
if (trimmed.includes("@")) {
return true;
}
return /^\+?\d{3,}$/.test(trimmed);
}

View File

@@ -77,7 +77,9 @@ export async function promptChannelAccessConfig(params: {
: `Configure ${params.label} access?`,
initialValue: shouldPrompt,
});
if (!wants) return null;
if (!wants) {
return null;
}
const policy = await promptChannelAccessPolicy({
prompter: params.prompter,
label: params.label,
@@ -85,7 +87,9 @@ export async function promptChannelAccessConfig(params: {
allowOpen: params.allowOpen,
allowDisabled: params.allowDisabled,
});
if (policy !== "allowlist") return { policy, entries: [] };
if (policy !== "allowlist") {
return { policy, entries: [] };
}
const entries = await promptChannelAllowlist({
prompter: params.prompter,
label: params.label,

View File

@@ -201,11 +201,17 @@ async function promptDiscordAllowFrom(params: {
const parseInputs = (value: string) => parseDiscordAllowFromInput(value);
const parseId = (value: string) => {
const trimmed = value.trim();
if (!trimmed) return null;
if (!trimmed) {
return null;
}
const mention = trimmed.match(/^<@!?(\d+)>$/);
if (mention) return mention[1];
if (mention) {
return mention[1];
}
const prefixed = trimmed.replace(/^(user:|discord:)/i, "");
if (/^\d+$/.test(prefixed)) return prefixed;
if (/^\d+$/.test(prefixed)) {
return prefixed;
}
return null;
};
@@ -387,7 +393,9 @@ export const discordOnboardingAdapter: ChannelOnboardingAdapter = {
([guildKey, value]) => {
const channels = value?.channels ?? {};
const channelKeys = Object.keys(channels);
if (channelKeys.length === 0) return [guildKey];
if (channelKeys.length === 0) {
return [guildKey];
}
return channelKeys.map((channelKey) => `${guildKey}/${channelKey}`);
},
);
@@ -463,7 +471,9 @@ export const discordOnboardingAdapter: ChannelOnboardingAdapter = {
const channelKey =
entry.channelId ??
(entry.channelName ? normalizeDiscordSlug(entry.channelName) : undefined);
if (!channelKey && guildKey === "*") continue;
if (!channelKey && guildKey === "*") {
continue;
}
allowlistEntries.push({ guildKey, ...(channelKey ? { channelKey } : {}) });
}
next = setDiscordGroupPolicy(next, discordAccountId, "allowlist");

View File

@@ -16,7 +16,9 @@ export const promptAccountId: PromptAccountId = async (params: PromptAccountIdPa
initialValue: initial,
});
if (choice !== "__new__") return normalizeAccountId(choice);
if (choice !== "__new__") {
return normalizeAccountId(choice);
}
const entered = await params.prompter.text({
message: `New ${params.label} account id`,
@@ -36,6 +38,8 @@ export function addWildcardAllowFrom(
allowFrom?: Array<string | number> | null,
): Array<string | number> {
const next = (allowFrom ?? []).map((v) => String(v).trim()).filter(Boolean);
if (!next.includes("*")) next.push("*");
if (!next.includes("*")) {
next.push("*");
}
return next;
}

View File

@@ -103,24 +103,36 @@ async function promptIMessageAllowFrom(params: {
initialValue: existing[0] ? String(existing[0]) : undefined,
validate: (value) => {
const raw = String(value ?? "").trim();
if (!raw) return "Required";
if (!raw) {
return "Required";
}
const parts = parseIMessageAllowFromInput(raw);
for (const part of parts) {
if (part === "*") continue;
if (part === "*") {
continue;
}
if (part.toLowerCase().startsWith("chat_id:")) {
const id = part.slice("chat_id:".length).trim();
if (!/^\d+$/.test(id)) return `Invalid chat_id: ${part}`;
if (!/^\d+$/.test(id)) {
return `Invalid chat_id: ${part}`;
}
continue;
}
if (part.toLowerCase().startsWith("chat_guid:")) {
if (!part.slice("chat_guid:".length).trim()) return "Invalid chat_guid entry";
if (!part.slice("chat_guid:".length).trim()) {
return "Invalid chat_guid entry";
}
continue;
}
if (part.toLowerCase().startsWith("chat_identifier:")) {
if (!part.slice("chat_identifier:".length).trim()) return "Invalid chat_identifier entry";
if (!part.slice("chat_identifier:".length).trim()) {
return "Invalid chat_identifier entry";
}
continue;
}
if (!normalizeIMessageHandle(part)) return `Invalid handle: ${part}`;
if (!normalizeIMessageHandle(part)) {
return `Invalid handle: ${part}`;
}
}
return undefined;
},

View File

@@ -107,16 +107,26 @@ async function promptSignalAllowFrom(params: {
initialValue: existing[0] ? String(existing[0]) : undefined,
validate: (value) => {
const raw = String(value ?? "").trim();
if (!raw) return "Required";
if (!raw) {
return "Required";
}
const parts = parseSignalAllowFromInput(raw);
for (const part of parts) {
if (part === "*") continue;
if (part.toLowerCase().startsWith("uuid:")) {
if (!part.slice("uuid:".length).trim()) return "Invalid uuid entry";
if (part === "*") {
continue;
}
if (isUuidLike(part)) continue;
if (!normalizeE164(part)) return `Invalid entry: ${part}`;
if (part.toLowerCase().startsWith("uuid:")) {
if (!part.slice("uuid:".length).trim()) {
return "Invalid uuid entry";
}
continue;
}
if (isUuidLike(part)) {
continue;
}
if (!normalizeE164(part)) {
return `Invalid entry: ${part}`;
}
}
return undefined;
},
@@ -124,9 +134,15 @@ async function promptSignalAllowFrom(params: {
const parts = parseSignalAllowFromInput(String(entry));
const normalized = parts
.map((part) => {
if (part === "*") return "*";
if (part.toLowerCase().startsWith("uuid:")) return `uuid:${part.slice(5).trim()}`;
if (isUuidLike(part)) return `uuid:${part}`;
if (part === "*") {
return "*";
}
if (part.toLowerCase().startsWith("uuid:")) {
return `uuid:${part.slice(5).trim()}`;
}
if (isUuidLike(part)) {
return `uuid:${part}`;
}
return normalizeE164(part);
})
.filter(Boolean);
@@ -231,7 +247,9 @@ export const signalOnboardingAdapter: ChannelOnboardingAdapter = {
message: `Signal account set (${account}). Keep it?`,
initialValue: true,
});
if (!keep) account = "";
if (!keep) {
account = "";
}
}
if (!account) {

View File

@@ -251,11 +251,17 @@ async function promptSlackAllowFrom(params: {
const parseInputs = (value: string) => parseSlackAllowFromInput(value);
const parseId = (value: string) => {
const trimmed = value.trim();
if (!trimmed) return null;
if (!trimmed) {
return null;
}
const mention = trimmed.match(/^<@([A-Z0-9]+)>$/i);
if (mention) return mention[1]?.toUpperCase();
if (mention) {
return mention[1]?.toUpperCase();
}
const prefixed = trimmed.replace(/^(slack:|user:)/i, "");
if (/^[A-Z][A-Z0-9]+$/i.test(prefixed)) return prefixed.toUpperCase();
if (/^[A-Z][A-Z0-9]+$/i.test(prefixed)) {
return prefixed.toUpperCase();
}
return null;
};

View File

@@ -74,21 +74,31 @@ async function promptTelegramAllowFrom(params: {
const resolveTelegramUserId = async (raw: string): Promise<string | null> => {
const trimmed = raw.trim();
if (!trimmed) return null;
if (!trimmed) {
return null;
}
const stripped = trimmed.replace(/^(telegram|tg):/i, "").trim();
if (/^\d+$/.test(stripped)) return stripped;
if (!token) return null;
if (/^\d+$/.test(stripped)) {
return stripped;
}
if (!token) {
return null;
}
const username = stripped.startsWith("@") ? stripped : `@${stripped}`;
const url = `https://api.telegram.org/bot${token}/getChat?chat_id=${encodeURIComponent(username)}`;
try {
const res = await fetch(url);
if (!res.ok) return null;
if (!res.ok) {
return null;
}
const data = (await res.json().catch(() => null)) as {
ok?: boolean;
result?: { id?: number | string };
} | null;
const id = data?.ok ? data?.result?.id : undefined;
if (typeof id === "number" || typeof id === "string") return String(id);
if (typeof id === "number" || typeof id === "string") {
return String(id);
}
return null;
} catch {
// Network error during username lookup - return null to prompt user for numeric ID

View File

@@ -68,9 +68,13 @@ async function promptWhatsAppAllowFrom(
initialValue: existingAllowFrom[0],
validate: (value) => {
const raw = String(value ?? "").trim();
if (!raw) return "Required";
if (!raw) {
return "Required";
}
const normalized = normalizeE164(raw);
if (!normalized) return `Invalid number: ${raw}`;
if (!normalized) {
return `Invalid number: ${raw}`;
}
return undefined;
},
});
@@ -126,9 +130,13 @@ async function promptWhatsAppAllowFrom(
initialValue: existingAllowFrom[0],
validate: (value) => {
const raw = String(value ?? "").trim();
if (!raw) return "Required";
if (!raw) {
return "Required";
}
const normalized = normalizeE164(raw);
if (!normalized) return `Invalid number: ${raw}`;
if (!normalized) {
return `Invalid number: ${raw}`;
}
return undefined;
},
});
@@ -170,7 +178,9 @@ async function promptWhatsAppAllowFrom(
if (policy === "open") {
next = setWhatsAppAllowFrom(next, ["*"]);
}
if (policy === "disabled") return next;
if (policy === "disabled") {
return next;
}
const allowOptions =
existingAllowFrom.length > 0
@@ -205,16 +215,24 @@ async function promptWhatsAppAllowFrom(
placeholder: "+15555550123, +447700900123",
validate: (value) => {
const raw = String(value ?? "").trim();
if (!raw) return "Required";
if (!raw) {
return "Required";
}
const parts = raw
.split(/[\n,;]+/g)
.map((p) => p.trim())
.filter(Boolean);
if (parts.length === 0) return "Required";
if (parts.length === 0) {
return "Required";
}
for (const part of parts) {
if (part === "*") continue;
if (part === "*") {
continue;
}
const normalized = normalizeE164(part);
if (!normalized) return `Invalid number: ${part}`;
if (!normalized) {
return `Invalid number: ${part}`;
}
}
return undefined;
},

View File

@@ -11,7 +11,9 @@ const cache = new Map<ChannelId, ChannelOutboundAdapter>();
let lastRegistry: PluginRegistry | null = null;
function ensureCacheForRegistry(registry: PluginRegistry | null) {
if (registry === lastRegistry) return;
if (registry === lastRegistry) {
return;
}
cache.clear();
lastRegistry = registry;
}
@@ -22,7 +24,9 @@ export async function loadChannelOutboundAdapter(
const registry = getActivePluginRegistry();
ensureCacheForRegistry(registry);
const cached = cache.get(id);
if (cached) return cached;
if (cached) {
return cached;
}
const pluginEntry = registry?.channels.find((entry) => entry.plugin.id === id);
const outbound = pluginEntry?.plugin.outbound;
if (outbound) {

View File

@@ -3,18 +3,24 @@ import { sendMessageTelegram } from "../../../telegram/send.js";
import type { ChannelOutboundAdapter } from "../types.js";
function parseReplyToMessageId(replyToId?: string | null) {
if (!replyToId) return undefined;
if (!replyToId) {
return undefined;
}
const parsed = Number.parseInt(replyToId, 10);
return Number.isFinite(parsed) ? parsed : undefined;
}
function parseThreadId(threadId?: string | number | null) {
if (threadId == null) return undefined;
if (threadId == null) {
return undefined;
}
if (typeof threadId === "number") {
return Number.isFinite(threadId) ? Math.trunc(threadId) : undefined;
}
const trimmed = threadId.trim();
if (!trimmed) return undefined;
if (!trimmed) {
return undefined;
}
const parsed = Number.parseInt(trimmed, 10);
return Number.isFinite(parsed) ? parsed : undefined;
}

View File

@@ -58,7 +58,9 @@ export async function notifyPairingApproved(params: {
}): Promise<void> {
// Extensions may provide adapter directly to bypass ESM module isolation
const adapter = params.pairingAdapter ?? requirePairingAdapter(params.channelId);
if (!adapter.notifyApproval) return;
if (!adapter.notifyApproval) {
return;
}
await adapter.notifyApproval({
cfg: params.cfg,
id: params.id,

View File

@@ -18,8 +18,12 @@ function shouldStoreNameInAccounts(params: {
accountId: string;
alwaysUseAccounts?: boolean;
}): boolean {
if (params.alwaysUseAccounts) return true;
if (params.accountId !== DEFAULT_ACCOUNT_ID) return true;
if (params.alwaysUseAccounts) {
return true;
}
if (params.accountId !== DEFAULT_ACCOUNT_ID) {
return true;
}
return channelHasAccounts(params.cfg, params.channelKey);
}
@@ -31,7 +35,9 @@ export function applyAccountNameToChannelSection(params: {
alwaysUseAccounts?: boolean;
}): OpenClawConfig {
const trimmed = params.name?.trim();
if (!trimmed) return params.cfg;
if (!trimmed) {
return params.cfg;
}
const accountId = normalizeAccountId(params.accountId);
const channels = params.cfg.channels as Record<string, unknown> | undefined;
const baseConfig = channels?.[params.channelKey];
@@ -85,11 +91,15 @@ export function migrateBaseNameToDefaultAccount(params: {
channelKey: string;
alwaysUseAccounts?: boolean;
}): OpenClawConfig {
if (params.alwaysUseAccounts) return params.cfg;
if (params.alwaysUseAccounts) {
return params.cfg;
}
const channels = params.cfg.channels as Record<string, unknown> | undefined;
const base = channels?.[params.channelKey] as ChannelSectionBase | undefined;
const baseName = base?.name?.trim();
if (!baseName) return params.cfg;
if (!baseName) {
return params.cfg;
}
const accounts: Record<string, Record<string, unknown>> = {
...base?.accounts,
};

View File

@@ -15,7 +15,9 @@ export function createSlackActions(providerId: string): ChannelMessageActionAdap
const accounts = listEnabledSlackAccounts(cfg).filter(
(account) => account.botTokenSource !== "none",
);
if (accounts.length === 0) return [];
if (accounts.length === 0) {
return [];
}
const isActionEnabled = (key: string, defaultValue = true) => {
for (const account of accounts) {
const gate = createActionGate(
@@ -24,7 +26,9 @@ export function createSlackActions(providerId: string): ChannelMessageActionAdap
boolean | undefined
>,
);
if (gate(key, defaultValue)) return true;
if (gate(key, defaultValue)) {
return true;
}
}
return false;
};
@@ -44,15 +48,23 @@ export function createSlackActions(providerId: string): ChannelMessageActionAdap
actions.add("unpin");
actions.add("list-pins");
}
if (isActionEnabled("memberInfo")) actions.add("member-info");
if (isActionEnabled("emojiList")) actions.add("emoji-list");
if (isActionEnabled("memberInfo")) {
actions.add("member-info");
}
if (isActionEnabled("emojiList")) {
actions.add("emoji-list");
}
return Array.from(actions);
},
extractToolSend: ({ args }): ChannelToolSend | null => {
const action = typeof args.action === "string" ? args.action.trim() : "";
if (action !== "sendMessage") return null;
if (action !== "sendMessage") {
return null;
}
const to = typeof args.to === "string" ? args.to : undefined;
if (!to) return null;
if (!to) {
return null;
}
const accountId = typeof args.accountId === "string" ? args.accountId.trim() : undefined;
return { to, accountId };
},

View File

@@ -20,7 +20,9 @@ type BlueBubblesProbeResult = {
function readBlueBubblesAccountStatus(
value: ChannelAccountSnapshot,
): BlueBubblesAccountStatus | null {
if (!isRecord(value)) return null;
if (!isRecord(value)) {
return null;
}
return {
accountId: value.accountId,
enabled: value.enabled,
@@ -33,7 +35,9 @@ function readBlueBubblesAccountStatus(
}
function readBlueBubblesProbeResult(value: unknown): BlueBubblesProbeResult | null {
if (!isRecord(value)) return null;
if (!isRecord(value)) {
return null;
}
return {
ok: typeof value.ok === "boolean" ? value.ok : undefined,
status: typeof value.status === "number" ? value.status : null,
@@ -47,10 +51,14 @@ export function collectBlueBubblesStatusIssues(
const issues: ChannelStatusIssue[] = [];
for (const entry of accounts) {
const account = readBlueBubblesAccountStatus(entry);
if (!account) continue;
if (!account) {
continue;
}
const accountId = asString(account.accountId) ?? "default";
const enabled = account.enabled !== false;
if (!enabled) continue;
if (!enabled) {
continue;
}
const configured = account.configured === true;
const running = account.running === true;

View File

@@ -30,7 +30,9 @@ type DiscordPermissionsAuditSummary = {
};
function readDiscordAccountStatus(value: ChannelAccountSnapshot): DiscordAccountStatus | null {
if (!isRecord(value)) return null;
if (!isRecord(value)) {
return null;
}
return {
accountId: value.accountId,
enabled: value.enabled,
@@ -41,9 +43,13 @@ function readDiscordAccountStatus(value: ChannelAccountSnapshot): DiscordAccount
}
function readDiscordApplicationSummary(value: unknown): DiscordApplicationSummary {
if (!isRecord(value)) return {};
if (!isRecord(value)) {
return {};
}
const intentsRaw = value.intents;
if (!isRecord(intentsRaw)) return {};
if (!isRecord(intentsRaw)) {
return {};
}
return {
intents: {
messageContent:
@@ -57,7 +63,9 @@ function readDiscordApplicationSummary(value: unknown): DiscordApplicationSummar
}
function readDiscordPermissionsAuditSummary(value: unknown): DiscordPermissionsAuditSummary {
if (!isRecord(value)) return {};
if (!isRecord(value)) {
return {};
}
const unresolvedChannels =
typeof value.unresolvedChannels === "number" && Number.isFinite(value.unresolvedChannels)
? value.unresolvedChannels
@@ -66,9 +74,13 @@ function readDiscordPermissionsAuditSummary(value: unknown): DiscordPermissionsA
const channels = Array.isArray(channelsRaw)
? (channelsRaw
.map((entry) => {
if (!isRecord(entry)) return null;
if (!isRecord(entry)) {
return null;
}
const channelId = asString(entry.channelId);
if (!channelId) return null;
if (!channelId) {
return null;
}
const ok = typeof entry.ok === "boolean" ? entry.ok : undefined;
const missing = Array.isArray(entry.missing)
? entry.missing.map((v) => asString(v)).filter(Boolean)
@@ -96,11 +108,15 @@ export function collectDiscordStatusIssues(
const issues: ChannelStatusIssue[] = [];
for (const entry of accounts) {
const account = readDiscordAccountStatus(entry);
if (!account) continue;
if (!account) {
continue;
}
const accountId = asString(account.accountId) ?? "default";
const enabled = account.enabled !== false;
const configured = account.configured === true;
if (!enabled || !configured) continue;
if (!enabled || !configured) {
continue;
}
const app = readDiscordApplicationSummary(account.application);
const messageContent = app.intents?.messageContent;
@@ -125,7 +141,9 @@ export function collectDiscordStatusIssues(
});
}
for (const channel of audit.channels ?? []) {
if (channel.ok === true) continue;
if (channel.ok === true) {
continue;
}
const missing = channel.missing?.length ? ` missing ${channel.missing.join(", ")}` : "";
const error = channel.error ? `: ${channel.error}` : "";
const baseMessage = `Channel ${channel.channelId} permission check failed.${missing}${error}`;

View File

@@ -23,7 +23,9 @@ type TelegramGroupMembershipAuditSummary = {
};
function readTelegramAccountStatus(value: ChannelAccountSnapshot): TelegramAccountStatus | null {
if (!isRecord(value)) return null;
if (!isRecord(value)) {
return null;
}
return {
accountId: value.accountId,
enabled: value.enabled,
@@ -36,7 +38,9 @@ function readTelegramAccountStatus(value: ChannelAccountSnapshot): TelegramAccou
function readTelegramGroupMembershipAuditSummary(
value: unknown,
): TelegramGroupMembershipAuditSummary {
if (!isRecord(value)) return {};
if (!isRecord(value)) {
return {};
}
const unresolvedGroups =
typeof value.unresolvedGroups === "number" && Number.isFinite(value.unresolvedGroups)
? value.unresolvedGroups
@@ -49,9 +53,13 @@ function readTelegramGroupMembershipAuditSummary(
const groups = Array.isArray(groupsRaw)
? (groupsRaw
.map((entry) => {
if (!isRecord(entry)) return null;
if (!isRecord(entry)) {
return null;
}
const chatId = asString(entry.chatId);
if (!chatId) return null;
if (!chatId) {
return null;
}
const ok = typeof entry.ok === "boolean" ? entry.ok : undefined;
const status = asString(entry.status) ?? null;
const error = asString(entry.error) ?? null;
@@ -70,11 +78,15 @@ export function collectTelegramStatusIssues(
const issues: ChannelStatusIssue[] = [];
for (const entry of accounts) {
const account = readTelegramAccountStatus(entry);
if (!account) continue;
if (!account) {
continue;
}
const accountId = asString(account.accountId) ?? "default";
const enabled = account.enabled !== false;
const configured = account.configured === true;
if (!enabled || !configured) continue;
if (!enabled || !configured) {
continue;
}
if (account.allowUnmentionedGroups === true) {
issues.push({
@@ -108,7 +120,9 @@ export function collectTelegramStatusIssues(
});
}
for (const group of audit.groups ?? []) {
if (group.ok === true) continue;
if (group.ok === true) {
continue;
}
const status = group.status ? ` status=${group.status}` : "";
const err = group.error ? `: ${group.error}` : "";
const baseMessage = `Group ${group.chatId} not reachable by bot.${status}${err}`;

View File

@@ -13,7 +13,9 @@ type WhatsAppAccountStatus = {
};
function readWhatsAppAccountStatus(value: ChannelAccountSnapshot): WhatsAppAccountStatus | null {
if (!isRecord(value)) return null;
if (!isRecord(value)) {
return null;
}
return {
accountId: value.accountId,
enabled: value.enabled,
@@ -31,10 +33,14 @@ export function collectWhatsAppStatusIssues(
const issues: ChannelStatusIssue[] = [];
for (const entry of accounts) {
const account = readWhatsAppAccountStatus(entry);
if (!account) continue;
if (!account) {
continue;
}
const accountId = asString(account.accountId) ?? "default";
const enabled = account.enabled !== false;
if (!enabled) continue;
if (!enabled) {
continue;
}
const linked = account.linked === true;
const running = account.running === true;
const connected = account.connected === true;

View File

@@ -9,7 +9,9 @@ type HeartbeatRecipientsOpts = { to?: string; all?: boolean };
function getSessionRecipients(cfg: OpenClawConfig) {
const sessionCfg = cfg.session;
const scope = sessionCfg?.scope ?? "per-sender";
if (scope === "global") return [];
if (scope === "global") {
return [];
}
const storePath = resolveStorePath(cfg.session?.store);
const store = loadSessionStore(storePath);
const isGroupKey = (key: string) =>
@@ -32,7 +34,9 @@ function getSessionRecipients(cfg: OpenClawConfig) {
// Dedupe while preserving recency ordering.
const seen = new Set<string>();
return recipients.filter((r) => {
if (seen.has(r.to)) return false;
if (seen.has(r.to)) {
return false;
}
seen.add(r.to);
return true;
});

View File

@@ -27,7 +27,9 @@ describe("channel registry", () => {
it("formats selection lines with docs labels", () => {
const channels = listChatChannels();
const first = channels[0];
if (!first) throw new Error("Missing channel metadata.");
if (!first) {
throw new Error("Missing channel metadata.");
}
const line = formatChannelSelectionLine(first, (path, label) =>
[label, path].filter(Boolean).join(":"),
);

View File

@@ -125,7 +125,9 @@ export function getChatChannelMeta(id: ChatChannelId): ChatChannelMeta {
export function normalizeChatChannelId(raw?: string | null): ChatChannelId | null {
const normalized = normalizeChannelKey(raw);
if (!normalized) return null;
if (!normalized) {
return null;
}
const resolved = CHAT_CHANNEL_ALIASES[normalized] ?? normalized;
return CHAT_CHANNEL_ORDER.includes(resolved) ? resolved : null;
}
@@ -142,14 +144,18 @@ export function normalizeChannelId(raw?: string | null): ChatChannelId | null {
// monitors, web login, etc). The plugin registry must be initialized first.
export function normalizeAnyChannelId(raw?: string | null): ChannelId | null {
const key = normalizeChannelKey(raw);
if (!key) return null;
if (!key) {
return null;
}
const registry = requireActivePluginRegistry();
const hit = registry.channels.find((entry) => {
const id = String(entry.plugin.id ?? "")
.trim()
.toLowerCase();
if (id && id === key) return true;
if (id && id === key) {
return true;
}
return (entry.plugin.meta.aliases ?? []).some((alias) => alias.trim().toLowerCase() === key);
});
return hit?.plugin.id ?? null;

View File

@@ -25,10 +25,12 @@ export function validateSenderIdentity(ctx: MsgContext): string[] {
}
if (senderUsername) {
if (senderUsername.includes("@"))
if (senderUsername.includes("@")) {
issues.push(`SenderUsername should not include "@": ${senderUsername}`);
if (/\s/.test(senderUsername))
}
if (/\s/.test(senderUsername)) {
issues.push(`SenderUsername should not include whitespace: ${senderUsername}`);
}
}
if (ctx.SenderId != null && !senderId) {

View File

@@ -20,7 +20,9 @@ export function resolveSenderLabel(params: SenderLabelParams): string | null {
const display = name ?? username ?? tag ?? "";
const idPart = e164 ?? id ?? "";
if (display && idPart && display !== idPart) return `${display} (${idPart})`;
if (display && idPart && display !== idPart) {
return `${display} (${idPart})`;
}
return display || idPart || null;
}
@@ -32,12 +34,24 @@ export function listSenderLabelCandidates(params: SenderLabelParams): string[] {
const e164 = normalize(params.e164);
const id = normalize(params.id);
if (name) candidates.add(name);
if (username) candidates.add(username);
if (tag) candidates.add(tag);
if (e164) candidates.add(e164);
if (id) candidates.add(id);
if (name) {
candidates.add(name);
}
if (username) {
candidates.add(username);
}
if (tag) {
candidates.add(tag);
}
if (e164) {
candidates.add(e164);
}
if (id) {
candidates.add(id);
}
const resolved = resolveSenderLabel(params);
if (resolved) candidates.add(resolved);
if (resolved) {
candidates.add(resolved);
}
return Array.from(candidates);
}

View File

@@ -33,7 +33,9 @@ export async function recordInboundSession(params: {
}).catch(params.onRecordError);
const update = params.updateLastRoute;
if (!update) return;
if (!update) {
return;
}
await updateLastRoute({
storePath,
sessionKey: update.sessionKey,