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

@@ -52,7 +52,9 @@ export function resolveAgentDeliveryPlan(params: {
});
const resolvedChannel = (() => {
if (requestedChannel === INTERNAL_MESSAGE_CHANNEL) return INTERNAL_MESSAGE_CHANNEL;
if (requestedChannel === INTERNAL_MESSAGE_CHANNEL) {
return INTERNAL_MESSAGE_CHANNEL;
}
if (requestedChannel === "last") {
if (baseDelivery.channel && baseDelivery.channel !== INTERNAL_MESSAGE_CHANNEL) {
return baseDelivery.channel;
@@ -60,7 +62,9 @@ export function resolveAgentDeliveryPlan(params: {
return params.wantsDelivery ? DEFAULT_CHAT_CHANNEL : INTERNAL_MESSAGE_CHANNEL;
}
if (isGatewayMessageChannel(requestedChannel)) return requestedChannel;
if (isGatewayMessageChannel(requestedChannel)) {
return requestedChannel;
}
if (baseDelivery.channel && baseDelivery.channel !== INTERNAL_MESSAGE_CHANNEL) {
return baseDelivery.channel;

View File

@@ -19,6 +19,8 @@ const DISCORD_ADAPTER: ChannelMessageAdapter = {
};
export function getChannelMessageAdapter(channel: ChannelId): ChannelMessageAdapter {
if (channel === "discord") return DISCORD_ADAPTER;
if (channel === "discord") {
return DISCORD_ADAPTER;
}
return DEFAULT_ADAPTER;
}

View File

@@ -16,24 +16,34 @@ function isKnownChannel(value: string): boolean {
}
function isAccountEnabled(account: unknown): boolean {
if (!account || typeof account !== "object") return true;
if (!account || typeof account !== "object") {
return true;
}
const enabled = (account as { enabled?: boolean }).enabled;
return enabled !== false;
}
async function isPluginConfigured(plugin: ChannelPlugin, cfg: OpenClawConfig): Promise<boolean> {
const accountIds = plugin.config.listAccountIds(cfg);
if (accountIds.length === 0) return false;
if (accountIds.length === 0) {
return false;
}
for (const accountId of accountIds) {
const account = plugin.config.resolveAccount(cfg, accountId);
const enabled = plugin.config.isEnabled
? plugin.config.isEnabled(account, cfg)
: isAccountEnabled(account);
if (!enabled) continue;
if (!plugin.config.isConfigured) return true;
if (!enabled) {
continue;
}
if (!plugin.config.isConfigured) {
return true;
}
const configured = await plugin.config.isConfigured(account, cfg);
if (configured) return true;
if (configured) {
return true;
}
}
return false;
@@ -44,7 +54,9 @@ export async function listConfiguredMessageChannels(
): Promise<MessageChannelId[]> {
const channels: MessageChannelId[] = [];
for (const plugin of listChannelPlugins()) {
if (!isKnownChannel(plugin.id)) continue;
if (!isKnownChannel(plugin.id)) {
continue;
}
if (await isPluginConfigured(plugin, cfg)) {
channels.push(plugin.id);
}

View File

@@ -24,7 +24,9 @@ export function applyTargetToParams(params: {
throw new Error("Use `target` for actions that accept a destination.");
}
if (!target) return;
if (!target) {
return;
}
if (mode === "channelId") {
params.args.channelId = target;
return;

View File

@@ -124,7 +124,9 @@ function createPluginHandler(params: {
gifPlayback?: boolean;
}): ChannelHandler | null {
const outbound = params.outbound;
if (!outbound?.sendText || !outbound?.sendMedia) return null;
if (!outbound?.sendText || !outbound?.sendMedia) {
return null;
}
const sendText = outbound.sendText;
const sendMedia = outbound.sendMedia;
const chunker = outbound.chunker ?? null;
@@ -244,10 +246,14 @@ export async function deliverOutboundPayloads(params: {
? chunkMarkdownTextWithMode(text, textLimit, "newline")
: chunkByParagraph(text, textLimit);
if (!blockChunks.length && text) blockChunks.push(text);
if (!blockChunks.length && text) {
blockChunks.push(text);
}
for (const blockChunk of blockChunks) {
const chunks = handler.chunker(blockChunk, textLimit);
if (!chunks.length && blockChunk) chunks.push(blockChunk);
if (!chunks.length && blockChunk) {
chunks.push(blockChunk);
}
for (const chunk of chunks) {
throwIfAborted(abortSignal);
results.push(await handler.sendText(chunk));
@@ -346,7 +352,9 @@ export async function deliverOutboundPayloads(params: {
}
}
} catch (err) {
if (!params.bestEffort) throw err;
if (!params.bestEffort) {
throw err;
}
params.onError?.(err, payloadSummary);
}
}

View File

@@ -28,7 +28,9 @@ export class DirectoryCache<T> {
get(key: string, cfg: OpenClawConfig): T | undefined {
this.resetIfConfigChanged(cfg);
const entry = this.cache.get(key);
if (!entry) return undefined;
if (!entry) {
return undefined;
}
if (Date.now() - entry.fetchedAt > this.ttlMs) {
this.cache.delete(key);
return undefined;
@@ -43,13 +45,17 @@ export class DirectoryCache<T> {
clearMatching(match: (key: string) => boolean): void {
for (const key of this.cache.keys()) {
if (match(key)) this.cache.delete(key);
if (match(key)) {
this.cache.delete(key);
}
}
}
clear(cfg?: OpenClawConfig): void {
this.cache.clear();
if (cfg) this.lastConfigRef = cfg;
if (cfg) {
this.lastConfigRef = cfg;
}
}
private resetIfConfigChanged(cfg: OpenClawConfig): void {

View File

@@ -31,9 +31,13 @@ type OutboundDeliveryMeta = {
const resolveChannelLabel = (channel: string) => {
const pluginLabel = getChannelPlugin(channel as ChannelId)?.meta.label;
if (pluginLabel) return pluginLabel;
if (pluginLabel) {
return pluginLabel;
}
const normalized = normalizeChatChannelId(channel);
if (normalized) return getChatChannelMeta(normalized).label;
if (normalized) {
return getChatChannelMeta(normalized).label;
}
return channel;
};
@@ -48,10 +52,18 @@ export function formatOutboundDeliverySummary(
const label = resolveChannelLabel(result.channel);
const base = `✅ Sent via ${label}. Message ID: ${result.messageId}`;
if ("chatId" in result) return `${base} (chat ${result.chatId})`;
if ("channelId" in result) return `${base} (channel ${result.channelId})`;
if ("roomId" in result) return `${base} (room ${result.roomId})`;
if ("conversationId" in result) return `${base} (conversation ${result.conversationId})`;
if ("chatId" in result) {
return `${base} (chat ${result.chatId})`;
}
if ("channelId" in result) {
return `${base} (channel ${result.channelId})`;
}
if ("roomId" in result) {
return `${base} (room ${result.roomId})`;
}
if ("conversationId" in result) {
return `${base} (conversation ${result.conversationId})`;
}
return base;
}

View File

@@ -123,7 +123,9 @@ export function getToolResult(
}
function extractToolPayload(result: AgentToolResult<unknown>): unknown {
if (result.details !== undefined) return result.details;
if (result.details !== undefined) {
return result.details;
}
const textBlock = Array.isArray(result.content)
? result.content.find(
(block) =>
@@ -188,7 +190,9 @@ async function maybeApplyCrossContextMarker(params: {
toolContext: params.toolContext,
accountId: params.accountId ?? undefined,
});
if (!decoration) return params.message;
if (!decoration) {
return params.message;
}
return applyCrossContextMessageDecoration({
params: params.args,
message: params.message,
@@ -199,11 +203,17 @@ async function maybeApplyCrossContextMarker(params: {
function readBooleanParam(params: Record<string, unknown>, key: string): boolean | undefined {
const raw = params[key];
if (typeof raw === "boolean") return raw;
if (typeof raw === "boolean") {
return raw;
}
if (typeof raw === "string") {
const trimmed = raw.trim().toLowerCase();
if (trimmed === "true") return true;
if (trimmed === "false") return false;
if (trimmed === "true") {
return true;
}
if (trimmed === "false") {
return false;
}
}
return undefined;
}
@@ -213,13 +223,23 @@ function resolveSlackAutoThreadId(params: {
toolContext?: ChannelThreadingToolContext;
}): string | undefined {
const context = params.toolContext;
if (!context?.currentThreadTs || !context.currentChannelId) return undefined;
if (!context?.currentThreadTs || !context.currentChannelId) {
return undefined;
}
// Only mirror auto-threading when Slack would reply in the active thread for this channel.
if (context.replyToMode !== "all" && context.replyToMode !== "first") return undefined;
if (context.replyToMode !== "all" && context.replyToMode !== "first") {
return undefined;
}
const parsedTarget = parseSlackTarget(params.to, { defaultKind: "channel" });
if (!parsedTarget || parsedTarget.kind !== "channel") return undefined;
if (parsedTarget.id.toLowerCase() !== context.currentChannelId.toLowerCase()) return undefined;
if (context.replyToMode === "first" && context.hasRepliedRef?.value) return undefined;
if (!parsedTarget || parsedTarget.kind !== "channel") {
return undefined;
}
if (parsedTarget.id.toLowerCase() !== context.currentChannelId.toLowerCase()) {
return undefined;
}
if (context.replyToMode === "first" && context.hasRepliedRef?.value) {
return undefined;
}
return context.currentThreadTs;
}
@@ -266,14 +286,20 @@ function inferAttachmentFilename(params: {
if (mediaHint.startsWith("file://")) {
const filePath = fileURLToPath(mediaHint);
const base = path.basename(filePath);
if (base) return base;
if (base) {
return base;
}
} else if (/^https?:\/\//i.test(mediaHint)) {
const url = new URL(mediaHint);
const base = path.basename(url.pathname);
if (base) return base;
if (base) {
return base;
}
} else {
const base = path.basename(mediaHint);
if (base) return base;
if (base) {
return base;
}
}
} catch {
// fall through to content-type based default
@@ -287,9 +313,13 @@ function normalizeBase64Payload(params: { base64?: string; contentType?: string
base64?: string;
contentType?: string;
} {
if (!params.base64) return { base64: params.base64, contentType: params.contentType };
if (!params.base64) {
return { base64: params.base64, contentType: params.contentType };
}
const match = /^data:([^;]+);base64,(.*)$/i.exec(params.base64.trim());
if (!match) return { base64: params.base64, contentType: params.contentType };
if (!match) {
return { base64: params.base64, contentType: params.contentType };
}
const [, mime, payload] = match;
return {
base64: payload,
@@ -305,7 +335,9 @@ async function hydrateSetGroupIconParams(params: {
action: ChannelMessageActionName;
dryRun?: boolean;
}): Promise<void> {
if (params.action !== "setGroupIcon") return;
if (params.action !== "setGroupIcon") {
return;
}
const mediaHint = readStringParam(params.args, "media", { trim: false });
const fileHint =
@@ -362,7 +394,9 @@ async function hydrateSendAttachmentParams(params: {
action: ChannelMessageActionName;
dryRun?: boolean;
}): Promise<void> {
if (params.action !== "sendAttachment") return;
if (params.action !== "sendAttachment") {
return;
}
const mediaHint = readStringParam(params.args, "media", { trim: false });
const fileHint =
@@ -372,7 +406,9 @@ async function hydrateSendAttachmentParams(params: {
readStringParam(params.args, "contentType") ?? readStringParam(params.args, "mimeType");
const caption = readStringParam(params.args, "caption", { allowEmpty: true })?.trim();
const message = readStringParam(params.args, "message", { allowEmpty: true })?.trim();
if (!caption && message) params.args.caption = message;
if (!caption && message) {
params.args.caption = message;
}
const rawBuffer = readStringParam(params.args, "buffer", { trim: false });
const normalized = normalizeBase64Payload({
@@ -416,7 +452,9 @@ async function hydrateSendAttachmentParams(params: {
function parseButtonsParam(params: Record<string, unknown>): void {
const raw = params.buttons;
if (typeof raw !== "string") return;
if (typeof raw !== "string") {
return;
}
const trimmed = raw.trim();
if (!trimmed) {
delete params.buttons;
@@ -431,7 +469,9 @@ function parseButtonsParam(params: Record<string, unknown>): void {
function parseCardParam(params: Record<string, unknown>): void {
const raw = params.card;
if (typeof raw !== "string") return;
if (typeof raw !== "string") {
return;
}
const trimmed = raw.trim();
if (!trimmed) {
delete params.card;
@@ -511,7 +551,9 @@ type ResolvedActionContext = {
abortSignal?: AbortSignal;
};
function resolveGateway(input: RunMessageActionParams): MessageActionRunnerGateway | undefined {
if (!input.gateway) return undefined;
if (!input.gateway) {
return undefined;
}
return {
url: input.gateway.url,
token: input.gateway.token,
@@ -562,7 +604,9 @@ async function handleBroadcastAction(
channel: targetChannel,
input: target,
});
if (!resolved.ok) throw resolved.error;
if (!resolved.ok) {
throw resolved.error;
}
const sendResult = await runMessageAction({
...input,
action: "send",
@@ -579,7 +623,9 @@ async function handleBroadcastAction(
result: sendResult.kind === "send" ? sendResult.sendResult : undefined,
});
} catch (err) {
if (isAbortError(err)) throw err;
if (isAbortError(err)) {
throw err;
}
results.push({
channel: targetChannel,
to: target,
@@ -640,17 +686,25 @@ async function handleSendAction(ctx: ResolvedActionContext): Promise<MessageActi
const seenMedia = new Set<string>();
const pushMedia = (value?: string | null) => {
const trimmed = value?.trim();
if (!trimmed) return;
if (seenMedia.has(trimmed)) return;
if (!trimmed) {
return;
}
if (seenMedia.has(trimmed)) {
return;
}
seenMedia.add(trimmed);
mergedMediaUrls.push(trimmed);
};
pushMedia(mediaHint);
for (const url of parsed.mediaUrls ?? []) pushMedia(url);
for (const url of parsed.mediaUrls ?? []) {
pushMedia(url);
}
pushMedia(parsed.mediaUrl);
message = parsed.text;
params.message = message;
if (!params.replyTo && parsed.replyToId) params.replyTo = parsed.replyToId;
if (!params.replyTo && parsed.replyToId) {
params.replyTo = parsed.replyToId;
}
if (!params.media) {
// Use path/filePath if media not set, then fall back to parsed directives
params.media = mergedMediaUrls[0] || undefined;

View File

@@ -75,15 +75,25 @@ export function actionHasTarget(
params: Record<string, unknown>,
): boolean {
const to = typeof params.to === "string" ? params.to.trim() : "";
if (to) return true;
if (to) {
return true;
}
const channelId = typeof params.channelId === "string" ? params.channelId.trim() : "";
if (channelId) return true;
if (channelId) {
return true;
}
const aliases = ACTION_TARGET_ALIASES[action];
if (!aliases) return false;
if (!aliases) {
return false;
}
return aliases.some((alias) => {
const value = params[alias];
if (typeof value === "string") return value.trim().length > 0;
if (typeof value === "number") return Number.isFinite(value);
if (typeof value === "string") {
return value.trim().length > 0;
}
if (typeof value === "number") {
return Number.isFinite(value);
}
return false;
});
}

View File

@@ -155,7 +155,9 @@ export async function sendMessage(params: MessageSendParams): Promise<MessageSen
accountId: params.accountId,
mode: "explicit",
});
if (!resolvedTarget.ok) throw resolvedTarget.error;
if (!resolvedTarget.ok) {
throw resolvedTarget.error;
}
const results = await deliverOutboundPayloads({
cfg,

View File

@@ -39,16 +39,26 @@ function resolveContextGuardTarget(
action: ChannelMessageActionName,
params: Record<string, unknown>,
): string | undefined {
if (!CONTEXT_GUARDED_ACTIONS.has(action)) return undefined;
if (action === "thread-reply" || action === "thread-create") {
if (typeof params.channelId === "string") return params.channelId;
if (typeof params.to === "string") return params.to;
if (!CONTEXT_GUARDED_ACTIONS.has(action)) {
return undefined;
}
if (typeof params.to === "string") return params.to;
if (typeof params.channelId === "string") return params.channelId;
if (action === "thread-reply" || action === "thread-create") {
if (typeof params.channelId === "string") {
return params.channelId;
}
if (typeof params.to === "string") {
return params.to;
}
return undefined;
}
if (typeof params.to === "string") {
return params.to;
}
if (typeof params.channelId === "string") {
return params.channelId;
}
return undefined;
}
@@ -62,10 +72,14 @@ function isCrossContextTarget(params: {
toolContext?: ChannelThreadingToolContext;
}): boolean {
const currentTarget = params.toolContext?.currentChannelId?.trim();
if (!currentTarget) return false;
if (!currentTarget) {
return false;
}
const normalizedTarget = normalizeTarget(params.channel, params.target);
const normalizedCurrent = normalizeTarget(params.channel, currentTarget);
if (!normalizedTarget || !normalizedCurrent) return false;
if (!normalizedTarget || !normalizedCurrent) {
return false;
}
return normalizedTarget !== normalizedCurrent;
}
@@ -77,10 +91,16 @@ export function enforceCrossContextPolicy(params: {
cfg: OpenClawConfig;
}): void {
const currentTarget = params.toolContext?.currentChannelId?.trim();
if (!currentTarget) return;
if (!CONTEXT_GUARDED_ACTIONS.has(params.action)) return;
if (!currentTarget) {
return;
}
if (!CONTEXT_GUARDED_ACTIONS.has(params.action)) {
return;
}
if (params.cfg.tools?.message?.allowCrossContextSend) return;
if (params.cfg.tools?.message?.allowCrossContextSend) {
return;
}
const currentProvider = params.toolContext?.currentChannelProvider;
const allowWithinProvider =
@@ -97,10 +117,14 @@ export function enforceCrossContextPolicy(params: {
return;
}
if (allowWithinProvider) return;
if (allowWithinProvider) {
return;
}
const target = resolveContextGuardTarget(params.action, params.args);
if (!target) return;
if (!target) {
return;
}
if (!isCrossContextTarget({ channel: params.channel, target, toolContext: params.toolContext })) {
return;
@@ -118,13 +142,21 @@ export async function buildCrossContextDecoration(params: {
toolContext?: ChannelThreadingToolContext;
accountId?: string | null;
}): Promise<CrossContextDecoration | null> {
if (!params.toolContext?.currentChannelId) return null;
if (!params.toolContext?.currentChannelId) {
return null;
}
// Skip decoration for direct tool sends (agent composing, not forwarding)
if (params.toolContext.skipCrossContextDecoration) return null;
if (!isCrossContextTarget(params)) return null;
if (params.toolContext.skipCrossContextDecoration) {
return null;
}
if (!isCrossContextTarget(params)) {
return null;
}
const markerConfig = params.cfg.tools?.message?.crossContext?.marker;
if (markerConfig?.enabled === false) return null;
if (markerConfig?.enabled === false) {
return null;
}
const currentName =
(await lookupDirectoryDisplay({

View File

@@ -36,7 +36,9 @@ export type OutboundSendContext = {
};
function extractToolPayload(result: AgentToolResult<unknown>): unknown {
if (result.details !== undefined) return result.details;
if (result.details !== undefined) {
return result.details;
}
const textBlock = Array.isArray(result.content)
? result.content.find(
(block) =>

View File

@@ -53,16 +53,24 @@ const UUID_COMPACT_RE = /^[0-9a-f]{32}$/i;
const SLACK_CHANNEL_TYPE_CACHE = new Map<string, "channel" | "group" | "dm" | "unknown">();
function looksLikeUuid(value: string): boolean {
if (UUID_RE.test(value) || UUID_COMPACT_RE.test(value)) return true;
if (UUID_RE.test(value) || UUID_COMPACT_RE.test(value)) {
return true;
}
const compact = value.replace(/-/g, "");
if (!/^[0-9a-f]+$/i.test(compact)) return false;
if (!/^[0-9a-f]+$/i.test(compact)) {
return false;
}
return /[a-f]/i.test(compact);
}
function normalizeThreadId(value?: string | number | null): string | undefined {
if (value == null) return undefined;
if (value == null) {
return undefined;
}
if (typeof value === "number") {
if (!Number.isFinite(value)) return undefined;
if (!Number.isFinite(value)) {
return undefined;
}
return String(Math.trunc(value));
}
const trimmed = value.trim();
@@ -73,7 +81,9 @@ function stripProviderPrefix(raw: string, channel: string): string {
const trimmed = raw.trim();
const lower = trimmed.toLowerCase();
const prefix = `${channel.toLowerCase()}:`;
if (lower.startsWith(prefix)) return trimmed.slice(prefix.length).trim();
if (lower.startsWith(prefix)) {
return trimmed.slice(prefix.length).trim();
}
return trimmed;
}
@@ -86,14 +96,20 @@ function inferPeerKind(params: {
resolvedTarget?: ResolvedMessagingTarget;
}): RoutePeerKind {
const resolvedKind = params.resolvedTarget?.kind;
if (resolvedKind === "user") return "dm";
if (resolvedKind === "channel") return "channel";
if (resolvedKind === "user") {
return "dm";
}
if (resolvedKind === "channel") {
return "channel";
}
if (resolvedKind === "group") {
const plugin = getChannelPlugin(params.channel);
const chatTypes = plugin?.capabilities?.chatTypes ?? [];
const supportsChannel = chatTypes.includes("channel");
const supportsGroup = chatTypes.includes("group");
if (supportsChannel && !supportsGroup) return "channel";
if (supportsChannel && !supportsGroup) {
return "channel";
}
return "group";
}
return "dm";
@@ -123,9 +139,13 @@ async function resolveSlackChannelType(params: {
channelId: string;
}): Promise<"channel" | "group" | "dm" | "unknown"> {
const channelId = params.channelId.trim();
if (!channelId) return "unknown";
if (!channelId) {
return "unknown";
}
const cached = SLACK_CHANNEL_TYPE_CACHE.get(`${params.accountId ?? "default"}:${channelId}`);
if (cached) return cached;
if (cached) {
return cached;
}
const account = resolveSlackAccount({ cfg: params.cfg, accountId: params.accountId });
const groupChannels = normalizeAllowListLower(account.dm?.groupChannels);
@@ -181,7 +201,9 @@ async function resolveSlackSession(
params: ResolveOutboundSessionRouteParams,
): Promise<OutboundSessionRoute | null> {
const parsed = parseSlackTarget(params.target, { defaultKind: "channel" });
if (!parsed) return null;
if (!parsed) {
return null;
}
const isDm = parsed.kind === "user";
let peerKind: RoutePeerKind = isDm ? "dm" : "channel";
if (!isDm && /^G/i.test(parsed.id)) {
@@ -191,8 +213,12 @@ async function resolveSlackSession(
accountId: params.accountId,
channelId: parsed.id,
});
if (channelType === "group") peerKind = "group";
if (channelType === "dm") peerKind = "dm";
if (channelType === "group") {
peerKind = "group";
}
if (channelType === "dm") {
peerKind = "dm";
}
}
const peer: RoutePeer = {
kind: peerKind,
@@ -230,7 +256,9 @@ function resolveDiscordSession(
params: ResolveOutboundSessionRouteParams,
): OutboundSessionRoute | null {
const parsed = parseDiscordTarget(params.target, { defaultKind: "channel" });
if (!parsed) return null;
if (!parsed) {
return null;
}
const isDm = parsed.kind === "user";
const peer: RoutePeer = {
kind: isDm ? "dm" : "channel",
@@ -267,7 +295,9 @@ function resolveTelegramSession(
): OutboundSessionRoute | null {
const parsed = parseTelegramTarget(params.target);
const chatId = parsed.chatId.trim();
if (!chatId) return null;
if (!chatId) {
return null;
}
const parsedThreadId = parsed.messageThreadId;
const fallbackThreadId = normalizeThreadId(params.threadId);
const resolvedThreadId =
@@ -307,7 +337,9 @@ function resolveWhatsAppSession(
params: ResolveOutboundSessionRouteParams,
): OutboundSessionRoute | null {
const normalized = normalizeWhatsAppTarget(params.target);
if (!normalized) return null;
if (!normalized) {
return null;
}
const isGroup = isWhatsAppGroupJid(normalized);
const peer: RoutePeer = {
kind: isGroup ? "group" : "dm",
@@ -337,7 +369,9 @@ function resolveSignalSession(
const lowered = stripped.toLowerCase();
if (lowered.startsWith("group:")) {
const groupId = stripped.slice("group:".length).trim();
if (!groupId) return null;
if (!groupId) {
return null;
}
const peer: RoutePeer = { kind: "group", id: groupId };
const baseSessionKey = buildBaseSessionKey({
cfg: params.cfg,
@@ -362,7 +396,9 @@ function resolveSignalSession(
} else if (lowered.startsWith("u:")) {
recipient = stripped.slice("u:".length).trim();
}
if (!recipient) return null;
if (!recipient) {
return null;
}
const uuidCandidate = recipient.toLowerCase().startsWith("uuid:")
? recipient.slice("uuid:".length)
@@ -397,7 +433,9 @@ function resolveIMessageSession(
const parsed = parseIMessageTarget(params.target);
if (parsed.kind === "handle") {
const handle = normalizeIMessageHandle(parsed.to);
if (!handle) return null;
if (!handle) {
return null;
}
const peer: RoutePeer = { kind: "dm", id: handle };
const baseSessionKey = buildBaseSessionKey({
cfg: params.cfg,
@@ -422,7 +460,9 @@ function resolveIMessageSession(
: parsed.kind === "chat_guid"
? parsed.chatGuid
: parsed.chatIdentifier;
if (!peerId) return null;
if (!peerId) {
return null;
}
const peer: RoutePeer = { kind: "group", id: peerId };
const baseSessionKey = buildBaseSessionKey({
cfg: params.cfg,
@@ -454,7 +494,9 @@ function resolveMatrixSession(
const isUser =
params.resolvedTarget?.kind === "user" || stripped.startsWith("@") || /^user:/i.test(stripped);
const rawId = stripKindPrefix(stripped);
if (!rawId) return null;
if (!rawId) {
return null;
}
const peer: RoutePeer = { kind: isUser ? "dm" : "channel", id: rawId };
const baseSessionKey = buildBaseSessionKey({
cfg: params.cfg,
@@ -477,13 +519,17 @@ function resolveMSTeamsSession(
params: ResolveOutboundSessionRouteParams,
): OutboundSessionRoute | null {
let trimmed = params.target.trim();
if (!trimmed) return null;
if (!trimmed) {
return null;
}
trimmed = trimmed.replace(/^(msteams|teams):/i, "").trim();
const lower = trimmed.toLowerCase();
const isUser = lower.startsWith("user:");
const rawId = stripKindPrefix(trimmed);
if (!rawId) return null;
if (!rawId) {
return null;
}
const conversationId = rawId.split(";")[0] ?? rawId;
const isChannel = !isUser && /@thread\.tacv2/i.test(conversationId);
const peer: RoutePeer = {
@@ -515,7 +561,9 @@ function resolveMattermostSession(
params: ResolveOutboundSessionRouteParams,
): OutboundSessionRoute | null {
let trimmed = params.target.trim();
if (!trimmed) return null;
if (!trimmed) {
return null;
}
trimmed = trimmed.replace(/^mattermost:/i, "").trim();
const lower = trimmed.toLowerCase();
const isUser = lower.startsWith("user:") || trimmed.startsWith("@");
@@ -523,7 +571,9 @@ function resolveMattermostSession(
trimmed = trimmed.slice(1).trim();
}
const rawId = stripKindPrefix(trimmed);
if (!rawId) return null;
if (!rawId) {
return null;
}
const peer: RoutePeer = { kind: isUser ? "dm" : "channel", id: rawId };
const baseSessionKey = buildBaseSessionKey({
cfg: params.cfg,
@@ -565,7 +615,9 @@ function resolveBlueBubblesSession(
const peerId = isGroup
? rawPeerId.replace(/^(chat_id|chat_guid|chat_identifier):/i, "")
: rawPeerId;
if (!peerId) return null;
if (!peerId) {
return null;
}
const peer: RoutePeer = {
kind: isGroup ? "group" : "dm",
id: peerId,
@@ -591,10 +643,14 @@ function resolveNextcloudTalkSession(
params: ResolveOutboundSessionRouteParams,
): OutboundSessionRoute | null {
let trimmed = params.target.trim();
if (!trimmed) return null;
if (!trimmed) {
return null;
}
trimmed = trimmed.replace(/^(nextcloud-talk|nc-talk|nc):/i, "").trim();
trimmed = trimmed.replace(/^room:/i, "").trim();
if (!trimmed) return null;
if (!trimmed) {
return null;
}
const peer: RoutePeer = { kind: "group", id: trimmed };
const baseSessionKey = buildBaseSessionKey({
cfg: params.cfg,
@@ -619,7 +675,9 @@ function resolveZaloSession(
const trimmed = stripProviderPrefix(params.target, "zalo")
.replace(/^(zl):/i, "")
.trim();
if (!trimmed) return null;
if (!trimmed) {
return null;
}
const isGroup = trimmed.toLowerCase().startsWith("group:");
const peerId = stripKindPrefix(trimmed);
const peer: RoutePeer = { kind: isGroup ? "group" : "dm", id: peerId };
@@ -646,7 +704,9 @@ function resolveZalouserSession(
const trimmed = stripProviderPrefix(params.target, "zalouser")
.replace(/^(zlu):/i, "")
.trim();
if (!trimmed) return null;
if (!trimmed) {
return null;
}
const isGroup = trimmed.toLowerCase().startsWith("group:");
const peerId = stripKindPrefix(trimmed);
// Keep DM vs group aligned with inbound sessions for Zalo Personal.
@@ -672,7 +732,9 @@ function resolveNostrSession(
params: ResolveOutboundSessionRouteParams,
): OutboundSessionRoute | null {
const trimmed = stripProviderPrefix(params.target, "nostr").trim();
if (!trimmed) return null;
if (!trimmed) {
return null;
}
const peer: RoutePeer = { kind: "dm", id: trimmed };
const baseSessionKey = buildBaseSessionKey({
cfg: params.cfg,
@@ -693,7 +755,9 @@ function resolveNostrSession(
function normalizeTlonShip(raw: string): string {
const trimmed = raw.trim();
if (!trimmed) return trimmed;
if (!trimmed) {
return trimmed;
}
return trimmed.startsWith("~") ? trimmed : `~${trimmed}`;
}
@@ -702,7 +766,9 @@ function resolveTlonSession(
): OutboundSessionRoute | null {
let trimmed = stripProviderPrefix(params.target, "tlon");
trimmed = trimmed.trim();
if (!trimmed) return null;
if (!trimmed) {
return null;
}
const lower = trimmed.toLowerCase();
let isGroup =
lower.startsWith("group:") || lower.startsWith("room:") || lower.startsWith("chat/");
@@ -754,13 +820,17 @@ function resolveFallbackSession(
params: ResolveOutboundSessionRouteParams,
): OutboundSessionRoute | null {
const trimmed = stripProviderPrefix(params.target, params.channel).trim();
if (!trimmed) return null;
if (!trimmed) {
return null;
}
const peerKind = inferPeerKind({
channel: params.channel,
resolvedTarget: params.resolvedTarget,
});
const peerId = stripKindPrefix(trimmed);
if (!peerId) return null;
if (!peerId) {
return null;
}
const peer: RoutePeer = { kind: peerKind, id: peerId };
const baseSessionKey = buildBaseSessionKey({
cfg: params.cfg,
@@ -786,7 +856,9 @@ export async function resolveOutboundSessionRoute(
params: ResolveOutboundSessionRouteParams,
): Promise<OutboundSessionRoute | null> {
const target = params.target.trim();
if (!target) return null;
if (!target) {
return null;
}
switch (params.channel) {
case "slack":
return await resolveSlackSession({ ...params, target });

View File

@@ -19,11 +19,17 @@ function mergeMediaUrls(...lists: Array<Array<string | undefined> | undefined>):
const seen = new Set<string>();
const merged: string[] = [];
for (const list of lists) {
if (!list) continue;
if (!list) {
continue;
}
for (const entry of list) {
const trimmed = entry?.trim();
if (!trimmed) continue;
if (seen.has(trimmed)) continue;
if (!trimmed) {
continue;
}
if (seen.has(trimmed)) {
continue;
}
seen.add(trimmed);
merged.push(trimmed);
}
@@ -52,8 +58,12 @@ export function normalizeReplyPayloadsForDelivery(payloads: ReplyPayload[]): Rep
replyToCurrent: payload.replyToCurrent || parsed.replyToCurrent,
audioAsVoice: Boolean(payload.audioAsVoice || parsed.audioAsVoice),
};
if (parsed.isSilent && mergedMedia.length === 0) return [];
if (!isRenderablePayload(next)) return [];
if (parsed.isSilent && mergedMedia.length === 0) {
return [];
}
if (!isRenderablePayload(next)) {
return [];
}
return [next];
});
}
@@ -90,7 +100,11 @@ export function normalizeOutboundPayloadsForJson(payloads: ReplyPayload[]): Outb
export function formatOutboundPayloadLog(payload: NormalizedOutboundPayload): string {
const lines: string[] = [];
if (payload.text) lines.push(payload.text.trimEnd());
for (const url of payload.mediaUrls) lines.push(`MEDIA:${url}`);
if (payload.text) {
lines.push(payload.text.trimEnd());
}
for (const url of payload.mediaUrls) {
lines.push(`MEDIA:${url}`);
}
return lines.join("\n");
}

View File

@@ -23,6 +23,8 @@ export function unknownTargetError(provider: string, raw: string, hint?: string)
}
function formatTargetHint(hint?: string, withLabel = false): string {
if (!hint) return "";
if (!hint) {
return "";
}
return withLabel ? ` Hint: ${hint}` : ` ${hint}`;
}

View File

@@ -6,7 +6,9 @@ export function normalizeChannelTargetInput(raw: string): string {
}
export function normalizeTargetForProvider(provider: string, raw?: string): string | undefined {
if (!raw) return undefined;
if (!raw) {
return undefined;
}
const providerId = normalizeChannelId(provider);
const plugin = providerId ? getChannelPlugin(providerId) : undefined;
const normalized =

View File

@@ -51,8 +51,12 @@ export function resetDirectoryCache(params?: { channel?: ChannelId; accountId?:
const channelKey = params.channel;
const accountKey = params.accountId ?? "default";
directoryCache.clearMatching((key) => {
if (!key.startsWith(`${channelKey}:`)) return false;
if (!params.accountId) return true;
if (!key.startsWith(`${channelKey}:`)) {
return false;
}
if (!params.accountId) {
return true;
}
return key.startsWith(`${channelKey}:${accountKey}:`);
});
}
@@ -91,14 +95,24 @@ export function formatTargetDisplay(params: {
(lowered.startsWith("user:") ? "user" : lowered.startsWith("channel:") ? "group" : undefined);
if (display) {
if (display.startsWith("#") || display.startsWith("@")) return display;
if (kind === "user") return `@${display}`;
if (kind === "group" || kind === "channel") return `#${display}`;
if (display.startsWith("#") || display.startsWith("@")) {
return display;
}
if (kind === "user") {
return `@${display}`;
}
if (kind === "group" || kind === "channel") {
return `#${display}`;
}
return display;
}
if (!trimmedTarget) return trimmedTarget;
if (trimmedTarget.startsWith("#") || trimmedTarget.startsWith("@")) return trimmedTarget;
if (!trimmedTarget) {
return trimmedTarget;
}
if (trimmedTarget.startsWith("#") || trimmedTarget.startsWith("@")) {
return trimmedTarget;
}
const channelPrefix = `${params.channel}:`;
const withoutProvider = trimmedTarget.toLowerCase().startsWith(channelPrefix)
@@ -116,11 +130,19 @@ export function formatTargetDisplay(params: {
}
function preserveTargetCase(channel: ChannelId, raw: string, normalized: string): string {
if (channel !== "slack") return normalized;
if (channel !== "slack") {
return normalized;
}
const trimmed = raw.trim();
if (/^channel:/i.test(trimmed) || /^user:/i.test(trimmed)) return trimmed;
if (trimmed.startsWith("#")) return `channel:${trimmed.slice(1).trim()}`;
if (trimmed.startsWith("@")) return `user:${trimmed.slice(1).trim()}`;
if (/^channel:/i.test(trimmed) || /^user:/i.test(trimmed)) {
return trimmed;
}
if (trimmed.startsWith("#")) {
return `channel:${trimmed.slice(1).trim()}`;
}
if (trimmed.startsWith("@")) {
return `user:${trimmed.slice(1).trim()}`;
}
return trimmed;
}
@@ -129,12 +151,20 @@ function detectTargetKind(
raw: string,
preferred?: TargetResolveKind,
): TargetResolveKind {
if (preferred) return preferred;
if (preferred) {
return preferred;
}
const trimmed = raw.trim();
if (!trimmed) return "group";
if (!trimmed) {
return "group";
}
if (trimmed.startsWith("@") || /^<@!?/.test(trimmed) || /^user:/i.test(trimmed)) return "user";
if (trimmed.startsWith("#") || /^channel:/i.test(trimmed)) return "group";
if (trimmed.startsWith("@") || /^<@!?/.test(trimmed) || /^user:/i.test(trimmed)) {
return "user";
}
if (trimmed.startsWith("#") || /^channel:/i.test(trimmed)) {
return "group";
}
// For some channels (e.g., BlueBubbles/iMessage), bare phone numbers are almost always DM targets.
if ((channel === "bluebubbles" || channel === "imessage") && /^\+?\d{6,}$/.test(trimmed)) {
@@ -155,7 +185,9 @@ function matchesDirectoryEntry(params: {
query: string;
}): boolean {
const query = normalizeQuery(params.query);
if (!query) return false;
if (!query) {
return false;
}
const id = stripTargetPrefixes(normalizeDirectoryEntryId(params.channel, params.entry));
const name = params.entry.name ? stripTargetPrefixes(params.entry.name) : "";
const handle = params.entry.handle ? stripTargetPrefixes(params.entry.handle) : "";
@@ -171,8 +203,12 @@ function resolveMatch(params: {
const matches = params.entries.filter((entry) =>
matchesDirectoryEntry({ channel: params.channel, entry, query: params.query }),
);
if (matches.length === 0) return { kind: "none" as const };
if (matches.length === 1) return { kind: "single" as const, entry: matches[0] };
if (matches.length === 0) {
return { kind: "none" as const };
}
if (matches.length === 1) {
return { kind: "single" as const, entry: matches[0] };
}
return { kind: "ambiguous" as const, entries: matches };
}
@@ -187,12 +223,16 @@ async function listDirectoryEntries(params: {
}): Promise<ChannelDirectoryEntry[]> {
const plugin = getChannelPlugin(params.channel);
const directory = plugin?.directory;
if (!directory) return [];
if (!directory) {
return [];
}
const runtime = params.runtime ?? defaultRuntime;
const useLive = params.source === "live";
if (params.kind === "user") {
const fn = useLive ? (directory.listPeersLive ?? directory.listPeers) : directory.listPeers;
if (!fn) return [];
if (!fn) {
return [];
}
return await fn({
cfg: params.cfg,
accountId: params.accountId ?? undefined,
@@ -202,7 +242,9 @@ async function listDirectoryEntries(params: {
});
}
const fn = useLive ? (directory.listGroupsLive ?? directory.listGroups) : directory.listGroups;
if (!fn) return [];
if (!fn) {
return [];
}
return await fn({
cfg: params.cfg,
accountId: params.accountId ?? undefined,
@@ -230,7 +272,9 @@ async function getDirectoryEntries(params: {
signature,
});
const cached = directoryCache.get(cacheKey, params.cfg);
if (cached) return cached;
if (cached) {
return cached;
}
const entries = await listDirectoryEntries({
cfg: params.cfg,
channel: params.channel,
@@ -269,8 +313,12 @@ function pickAmbiguousMatch(
entries: ChannelDirectoryEntry[],
mode: ResolveAmbiguousMode,
): ChannelDirectoryEntry | null {
if (entries.length === 0) return null;
if (mode === "first") return entries[0] ?? null;
if (entries.length === 0) {
return null;
}
if (mode === "first") {
return entries[0] ?? null;
}
const ranked = entries.map((entry) => ({
entry,
rank: typeof entry.rank === "number" ? entry.rank : 0,
@@ -300,19 +348,33 @@ export async function resolveMessagingTarget(params: {
const normalized = normalizeTargetForProvider(params.channel, raw) ?? raw;
const looksLikeTargetId = (): boolean => {
const trimmed = raw.trim();
if (!trimmed) return false;
if (!trimmed) {
return false;
}
const lookup = plugin?.messaging?.targetResolver?.looksLikeId;
if (lookup) return lookup(trimmed, normalized);
if (/^(channel|group|user):/i.test(trimmed)) return true;
if (/^[@#]/.test(trimmed)) return true;
if (lookup) {
return lookup(trimmed, normalized);
}
if (/^(channel|group|user):/i.test(trimmed)) {
return true;
}
if (/^[@#]/.test(trimmed)) {
return true;
}
if (/^\+?\d{6,}$/.test(trimmed)) {
// BlueBubbles/iMessage phone numbers should usually resolve via the directory to a DM chat,
// otherwise the provider may pick an existing group containing that handle.
if (params.channel === "bluebubbles" || params.channel === "imessage") return false;
if (params.channel === "bluebubbles" || params.channel === "imessage") {
return false;
}
return true;
}
if (trimmed.includes("@thread")) {
return true;
}
if (/^(conversation|user):/i.test(trimmed)) {
return true;
}
if (trimmed.includes("@thread")) return true;
if (/^(conversation|user):/i.test(trimmed)) return true;
return false;
};
if (looksLikeTargetId()) {

View File

@@ -184,7 +184,9 @@ export function resolveHeartbeatDeliveryTarget(params: {
target = rawTarget;
} else if (typeof rawTarget === "string") {
const normalized = normalizeChannelId(rawTarget);
if (normalized) target = normalized;
if (normalized) {
target = normalized;
}
}
if (target === "none") {
@@ -279,12 +281,16 @@ function resolveHeartbeatSenderId(params: {
}
if (candidates.length > 0 && allowList.length > 0) {
const matched = candidates.find((candidate) => allowList.includes(candidate));
if (matched) return matched;
if (matched) {
return matched;
}
}
if (candidates.length > 0 && allowList.length === 0) {
return candidates[0];
}
if (allowList.length > 0) return allowList[0];
if (allowList.length > 0) {
return allowList[0];
}
return candidates[0] ?? "heartbeat";
}