mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 01:13:29 +00:00
refactor(outbound): centralize outbound identity
This commit is contained in:
@@ -45,16 +45,17 @@ describe("slack outbound hook wiring", () => {
|
|||||||
text: "hello",
|
text: "hello",
|
||||||
accountId: "default",
|
accountId: "default",
|
||||||
replyToId: "1111.2222",
|
replyToId: "1111.2222",
|
||||||
username: "My Agent",
|
identity: {
|
||||||
icon_url: "https://example.com/avatar.png",
|
name: "My Agent",
|
||||||
icon_emoji: ":should_not_send:",
|
avatarUrl: "https://example.com/avatar.png",
|
||||||
|
emoji: ":should_not_send:",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(sendMessageSlack).toHaveBeenCalledWith("C123", "hello", {
|
expect(sendMessageSlack).toHaveBeenCalledWith("C123", "hello", {
|
||||||
threadTs: "1111.2222",
|
threadTs: "1111.2222",
|
||||||
accountId: "default",
|
accountId: "default",
|
||||||
username: "My Agent",
|
identity: { username: "My Agent", iconUrl: "https://example.com/avatar.png" },
|
||||||
icon_url: "https://example.com/avatar.png",
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -66,13 +67,13 @@ describe("slack outbound hook wiring", () => {
|
|||||||
text: "hello",
|
text: "hello",
|
||||||
accountId: "default",
|
accountId: "default",
|
||||||
replyToId: "1111.2222",
|
replyToId: "1111.2222",
|
||||||
icon_emoji: ":lobster:",
|
identity: { emoji: ":lobster:" },
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(sendMessageSlack).toHaveBeenCalledWith("C123", "hello", {
|
expect(sendMessageSlack).toHaveBeenCalledWith("C123", "hello", {
|
||||||
threadTs: "1111.2222",
|
threadTs: "1111.2222",
|
||||||
accountId: "default",
|
accountId: "default",
|
||||||
icon_emoji: ":lobster:",
|
identity: { iconEmoji: ":lobster:" },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,27 @@
|
|||||||
|
import type { OutboundIdentity } from "../../../infra/outbound/identity.js";
|
||||||
import type { ChannelOutboundAdapter } from "../types.js";
|
import type { ChannelOutboundAdapter } from "../types.js";
|
||||||
import { getGlobalHookRunner } from "../../../plugins/hook-runner-global.js";
|
import { getGlobalHookRunner } from "../../../plugins/hook-runner-global.js";
|
||||||
import { sendMessageSlack } from "../../../slack/send.js";
|
import { sendMessageSlack, type SlackSendIdentity } from "../../../slack/send.js";
|
||||||
|
|
||||||
|
function resolveSlackSendIdentity(identity?: OutboundIdentity): SlackSendIdentity | undefined {
|
||||||
|
if (!identity) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const username = identity.name?.trim() || undefined;
|
||||||
|
const iconUrl = identity.avatarUrl?.trim() || undefined;
|
||||||
|
const rawEmoji = identity.emoji?.trim();
|
||||||
|
const iconEmoji = !iconUrl && rawEmoji && /^:[^:\s]+:$/.test(rawEmoji) ? rawEmoji : undefined;
|
||||||
|
if (!username && !iconUrl && !iconEmoji) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return { username, iconUrl, iconEmoji };
|
||||||
|
}
|
||||||
|
|
||||||
export const slackOutbound: ChannelOutboundAdapter = {
|
export const slackOutbound: ChannelOutboundAdapter = {
|
||||||
deliveryMode: "direct",
|
deliveryMode: "direct",
|
||||||
chunker: null,
|
chunker: null,
|
||||||
textChunkLimit: 4000,
|
textChunkLimit: 4000,
|
||||||
sendText: async ({
|
sendText: async ({ to, text, accountId, deps, replyToId, threadId, identity }) => {
|
||||||
to,
|
|
||||||
text,
|
|
||||||
accountId,
|
|
||||||
deps,
|
|
||||||
replyToId,
|
|
||||||
threadId,
|
|
||||||
username,
|
|
||||||
icon_url,
|
|
||||||
icon_emoji,
|
|
||||||
}) => {
|
|
||||||
const send = deps?.sendSlack ?? sendMessageSlack;
|
const send = deps?.sendSlack ?? sendMessageSlack;
|
||||||
// Use threadId fallback so routed tool notifications stay in the Slack thread.
|
// Use threadId fallback so routed tool notifications stay in the Slack thread.
|
||||||
const threadTs = replyToId ?? (threadId != null ? String(threadId) : undefined);
|
const threadTs = replyToId ?? (threadId != null ? String(threadId) : undefined);
|
||||||
@@ -42,27 +47,15 @@ export const slackOutbound: ChannelOutboundAdapter = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const slackIdentity = resolveSlackSendIdentity(identity);
|
||||||
const result = await send(to, finalText, {
|
const result = await send(to, finalText, {
|
||||||
threadTs,
|
threadTs,
|
||||||
accountId: accountId ?? undefined,
|
accountId: accountId ?? undefined,
|
||||||
...(username ? { username } : {}),
|
...(slackIdentity ? { identity: slackIdentity } : {}),
|
||||||
...(icon_url ? { icon_url } : {}),
|
|
||||||
...(icon_emoji && !icon_url ? { icon_emoji } : {}),
|
|
||||||
});
|
});
|
||||||
return { channel: "slack", ...result };
|
return { channel: "slack", ...result };
|
||||||
},
|
},
|
||||||
sendMedia: async ({
|
sendMedia: async ({ to, text, mediaUrl, accountId, deps, replyToId, threadId, identity }) => {
|
||||||
to,
|
|
||||||
text,
|
|
||||||
mediaUrl,
|
|
||||||
accountId,
|
|
||||||
deps,
|
|
||||||
replyToId,
|
|
||||||
threadId,
|
|
||||||
username,
|
|
||||||
icon_url,
|
|
||||||
icon_emoji,
|
|
||||||
}) => {
|
|
||||||
const send = deps?.sendSlack ?? sendMessageSlack;
|
const send = deps?.sendSlack ?? sendMessageSlack;
|
||||||
// Use threadId fallback so routed tool notifications stay in the Slack thread.
|
// Use threadId fallback so routed tool notifications stay in the Slack thread.
|
||||||
const threadTs = replyToId ?? (threadId != null ? String(threadId) : undefined);
|
const threadTs = replyToId ?? (threadId != null ? String(threadId) : undefined);
|
||||||
@@ -88,13 +81,12 @@ export const slackOutbound: ChannelOutboundAdapter = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const slackIdentity = resolveSlackSendIdentity(identity);
|
||||||
const result = await send(to, finalText, {
|
const result = await send(to, finalText, {
|
||||||
mediaUrl,
|
mediaUrl,
|
||||||
threadTs,
|
threadTs,
|
||||||
accountId: accountId ?? undefined,
|
accountId: accountId ?? undefined,
|
||||||
...(username ? { username } : {}),
|
...(slackIdentity ? { identity: slackIdentity } : {}),
|
||||||
...(icon_url ? { icon_url } : {}),
|
|
||||||
...(icon_emoji && !icon_url ? { icon_emoji } : {}),
|
|
||||||
});
|
});
|
||||||
return { channel: "slack", ...result };
|
return { channel: "slack", ...result };
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import type { ReplyPayload } from "../../auto-reply/types.js";
|
|||||||
import type { OpenClawConfig } from "../../config/config.js";
|
import type { OpenClawConfig } from "../../config/config.js";
|
||||||
import type { GroupToolPolicyConfig } from "../../config/types.tools.js";
|
import type { GroupToolPolicyConfig } from "../../config/types.tools.js";
|
||||||
import type { OutboundDeliveryResult, OutboundSendDeps } from "../../infra/outbound/deliver.js";
|
import type { OutboundDeliveryResult, OutboundSendDeps } from "../../infra/outbound/deliver.js";
|
||||||
|
import type { OutboundIdentity } from "../../infra/outbound/identity.js";
|
||||||
import type { RuntimeEnv } from "../../runtime.js";
|
import type { RuntimeEnv } from "../../runtime.js";
|
||||||
import type {
|
import type {
|
||||||
ChannelAccountSnapshot,
|
ChannelAccountSnapshot,
|
||||||
@@ -79,9 +80,7 @@ export type ChannelOutboundContext = {
|
|||||||
replyToId?: string | null;
|
replyToId?: string | null;
|
||||||
threadId?: string | number | null;
|
threadId?: string | number | null;
|
||||||
accountId?: string | null;
|
accountId?: string | null;
|
||||||
username?: string;
|
identity?: OutboundIdentity;
|
||||||
icon_url?: string;
|
|
||||||
icon_emoji?: string;
|
|
||||||
deps?: OutboundSendDeps;
|
deps?: OutboundSendDeps;
|
||||||
silent?: boolean;
|
silent?: boolean;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ import { getCliSessionId, setCliSessionId } from "../../agents/cli-session.js";
|
|||||||
import { lookupContextTokens } from "../../agents/context.js";
|
import { lookupContextTokens } from "../../agents/context.js";
|
||||||
import { resolveCronStyleNow } from "../../agents/current-time.js";
|
import { resolveCronStyleNow } from "../../agents/current-time.js";
|
||||||
import { DEFAULT_CONTEXT_TOKENS, DEFAULT_MODEL, DEFAULT_PROVIDER } from "../../agents/defaults.js";
|
import { DEFAULT_CONTEXT_TOKENS, DEFAULT_MODEL, DEFAULT_PROVIDER } from "../../agents/defaults.js";
|
||||||
import { resolveAgentAvatar } from "../../agents/identity-avatar.js";
|
|
||||||
import { resolveAgentIdentity } from "../../agents/identity.js";
|
|
||||||
import { loadModelCatalog } from "../../agents/model-catalog.js";
|
import { loadModelCatalog } from "../../agents/model-catalog.js";
|
||||||
import { runWithModelFallback } from "../../agents/model-fallback.js";
|
import { runWithModelFallback } from "../../agents/model-fallback.js";
|
||||||
import {
|
import {
|
||||||
@@ -46,6 +44,7 @@ import {
|
|||||||
} from "../../config/sessions.js";
|
} from "../../config/sessions.js";
|
||||||
import { registerAgentRunContext } from "../../infra/agent-events.js";
|
import { registerAgentRunContext } from "../../infra/agent-events.js";
|
||||||
import { deliverOutboundPayloads } from "../../infra/outbound/deliver.js";
|
import { deliverOutboundPayloads } from "../../infra/outbound/deliver.js";
|
||||||
|
import { resolveAgentOutboundIdentity } from "../../infra/outbound/identity.js";
|
||||||
import { getRemoteSkillEligibility } from "../../infra/skills-remote.js";
|
import { getRemoteSkillEligibility } from "../../infra/skills-remote.js";
|
||||||
import { logWarn } from "../../logger.js";
|
import { logWarn } from "../../logger.js";
|
||||||
import { buildAgentMainSessionKey, normalizeAgentId } from "../../routing/session-key.js";
|
import { buildAgentMainSessionKey, normalizeAgentId } from "../../routing/session-key.js";
|
||||||
@@ -557,18 +556,11 @@ export async function runCronIsolatedAgentTurn(params: {
|
|||||||
logWarn(`[cron:${params.job.id}] ${message}`);
|
logWarn(`[cron:${params.job.id}] ${message}`);
|
||||||
return withRunSession({ status: "ok", summary, outputText });
|
return withRunSession({ status: "ok", summary, outputText });
|
||||||
}
|
}
|
||||||
const agentIdentity = resolveAgentIdentity(cfgWithAgentDefaults, agentId);
|
const identity = resolveAgentOutboundIdentity(cfgWithAgentDefaults, agentId);
|
||||||
const avatar = resolveAgentAvatar(cfgWithAgentDefaults, agentId);
|
|
||||||
const icon_url = avatar.kind === "remote" ? avatar.url : undefined;
|
|
||||||
const username = agentIdentity?.name?.trim() || undefined;
|
|
||||||
const rawEmoji = agentIdentity?.emoji?.trim();
|
|
||||||
// Slack `icon_emoji` requires :emoji_name: (not a Unicode emoji).
|
|
||||||
const icon_emoji =
|
|
||||||
!icon_url && rawEmoji && /^:[^:\\s]+:$/.test(rawEmoji) ? rawEmoji : undefined;
|
|
||||||
|
|
||||||
// Shared subagent announce flow is text-based. When we have an explicit sender
|
// Shared subagent announce flow is text-based. When we have an explicit sender
|
||||||
// identity to preserve, prefer direct outbound delivery even for plain-text payloads.
|
// identity to preserve, prefer direct outbound delivery even for plain-text payloads.
|
||||||
if (deliveryPayloadHasStructuredContent || username || icon_url || icon_emoji) {
|
if (deliveryPayloadHasStructuredContent || identity) {
|
||||||
try {
|
try {
|
||||||
const payloadsForDelivery =
|
const payloadsForDelivery =
|
||||||
deliveryPayloadHasStructuredContent && deliveryPayloads.length > 0
|
deliveryPayloadHasStructuredContent && deliveryPayloads.length > 0
|
||||||
@@ -584,9 +576,7 @@ export async function runCronIsolatedAgentTurn(params: {
|
|||||||
accountId: resolvedDelivery.accountId,
|
accountId: resolvedDelivery.accountId,
|
||||||
threadId: resolvedDelivery.threadId,
|
threadId: resolvedDelivery.threadId,
|
||||||
payloads: payloadsForDelivery,
|
payloads: payloadsForDelivery,
|
||||||
username,
|
identity,
|
||||||
icon_url,
|
|
||||||
icon_emoji,
|
|
||||||
bestEffort: deliveryBestEffort,
|
bestEffort: deliveryBestEffort,
|
||||||
deps: createOutboundSendDeps(params.deps),
|
deps: createOutboundSendDeps(params.deps),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import type { sendMessageIMessage } from "../../imessage/send.js";
|
|||||||
import type { sendMessageSlack } from "../../slack/send.js";
|
import type { sendMessageSlack } from "../../slack/send.js";
|
||||||
import type { sendMessageTelegram } from "../../telegram/send.js";
|
import type { sendMessageTelegram } from "../../telegram/send.js";
|
||||||
import type { sendMessageWhatsApp } from "../../web/outbound.js";
|
import type { sendMessageWhatsApp } from "../../web/outbound.js";
|
||||||
|
import type { OutboundIdentity } from "./identity.js";
|
||||||
import type { NormalizedOutboundPayload } from "./payloads.js";
|
import type { NormalizedOutboundPayload } from "./payloads.js";
|
||||||
import type { OutboundChannel } from "./targets.js";
|
import type { OutboundChannel } from "./targets.js";
|
||||||
import {
|
import {
|
||||||
@@ -85,9 +86,7 @@ async function createChannelHandler(params: {
|
|||||||
accountId?: string;
|
accountId?: string;
|
||||||
replyToId?: string | null;
|
replyToId?: string | null;
|
||||||
threadId?: string | number | null;
|
threadId?: string | number | null;
|
||||||
username?: string;
|
identity?: OutboundIdentity;
|
||||||
icon_url?: string;
|
|
||||||
icon_emoji?: string;
|
|
||||||
deps?: OutboundSendDeps;
|
deps?: OutboundSendDeps;
|
||||||
gifPlayback?: boolean;
|
gifPlayback?: boolean;
|
||||||
silent?: boolean;
|
silent?: boolean;
|
||||||
@@ -104,9 +103,7 @@ async function createChannelHandler(params: {
|
|||||||
accountId: params.accountId,
|
accountId: params.accountId,
|
||||||
replyToId: params.replyToId,
|
replyToId: params.replyToId,
|
||||||
threadId: params.threadId,
|
threadId: params.threadId,
|
||||||
username: params.username,
|
identity: params.identity,
|
||||||
icon_url: params.icon_url,
|
|
||||||
icon_emoji: params.icon_emoji,
|
|
||||||
deps: params.deps,
|
deps: params.deps,
|
||||||
gifPlayback: params.gifPlayback,
|
gifPlayback: params.gifPlayback,
|
||||||
silent: params.silent,
|
silent: params.silent,
|
||||||
@@ -125,9 +122,7 @@ function createPluginHandler(params: {
|
|||||||
accountId?: string;
|
accountId?: string;
|
||||||
replyToId?: string | null;
|
replyToId?: string | null;
|
||||||
threadId?: string | number | null;
|
threadId?: string | number | null;
|
||||||
username?: string;
|
identity?: OutboundIdentity;
|
||||||
icon_url?: string;
|
|
||||||
icon_emoji?: string;
|
|
||||||
deps?: OutboundSendDeps;
|
deps?: OutboundSendDeps;
|
||||||
gifPlayback?: boolean;
|
gifPlayback?: boolean;
|
||||||
silent?: boolean;
|
silent?: boolean;
|
||||||
@@ -154,9 +149,7 @@ function createPluginHandler(params: {
|
|||||||
accountId: params.accountId,
|
accountId: params.accountId,
|
||||||
replyToId: params.replyToId,
|
replyToId: params.replyToId,
|
||||||
threadId: params.threadId,
|
threadId: params.threadId,
|
||||||
username: params.username,
|
identity: params.identity,
|
||||||
icon_url: params.icon_url,
|
|
||||||
icon_emoji: params.icon_emoji,
|
|
||||||
gifPlayback: params.gifPlayback,
|
gifPlayback: params.gifPlayback,
|
||||||
deps: params.deps,
|
deps: params.deps,
|
||||||
silent: params.silent,
|
silent: params.silent,
|
||||||
@@ -171,9 +164,7 @@ function createPluginHandler(params: {
|
|||||||
accountId: params.accountId,
|
accountId: params.accountId,
|
||||||
replyToId: params.replyToId,
|
replyToId: params.replyToId,
|
||||||
threadId: params.threadId,
|
threadId: params.threadId,
|
||||||
username: params.username,
|
identity: params.identity,
|
||||||
icon_url: params.icon_url,
|
|
||||||
icon_emoji: params.icon_emoji,
|
|
||||||
gifPlayback: params.gifPlayback,
|
gifPlayback: params.gifPlayback,
|
||||||
deps: params.deps,
|
deps: params.deps,
|
||||||
silent: params.silent,
|
silent: params.silent,
|
||||||
@@ -187,9 +178,7 @@ function createPluginHandler(params: {
|
|||||||
accountId: params.accountId,
|
accountId: params.accountId,
|
||||||
replyToId: params.replyToId,
|
replyToId: params.replyToId,
|
||||||
threadId: params.threadId,
|
threadId: params.threadId,
|
||||||
username: params.username,
|
identity: params.identity,
|
||||||
icon_url: params.icon_url,
|
|
||||||
icon_emoji: params.icon_emoji,
|
|
||||||
gifPlayback: params.gifPlayback,
|
gifPlayback: params.gifPlayback,
|
||||||
deps: params.deps,
|
deps: params.deps,
|
||||||
silent: params.silent,
|
silent: params.silent,
|
||||||
@@ -207,9 +196,7 @@ export async function deliverOutboundPayloads(params: {
|
|||||||
payloads: ReplyPayload[];
|
payloads: ReplyPayload[];
|
||||||
replyToId?: string | null;
|
replyToId?: string | null;
|
||||||
threadId?: string | number | null;
|
threadId?: string | number | null;
|
||||||
username?: string;
|
identity?: OutboundIdentity;
|
||||||
icon_url?: string;
|
|
||||||
icon_emoji?: string;
|
|
||||||
deps?: OutboundSendDeps;
|
deps?: OutboundSendDeps;
|
||||||
gifPlayback?: boolean;
|
gifPlayback?: boolean;
|
||||||
abortSignal?: AbortSignal;
|
abortSignal?: AbortSignal;
|
||||||
@@ -292,9 +279,7 @@ async function deliverOutboundPayloadsCore(params: {
|
|||||||
payloads: ReplyPayload[];
|
payloads: ReplyPayload[];
|
||||||
replyToId?: string | null;
|
replyToId?: string | null;
|
||||||
threadId?: string | number | null;
|
threadId?: string | number | null;
|
||||||
username?: string;
|
identity?: OutboundIdentity;
|
||||||
icon_url?: string;
|
|
||||||
icon_emoji?: string;
|
|
||||||
deps?: OutboundSendDeps;
|
deps?: OutboundSendDeps;
|
||||||
gifPlayback?: boolean;
|
gifPlayback?: boolean;
|
||||||
abortSignal?: AbortSignal;
|
abortSignal?: AbortSignal;
|
||||||
@@ -323,9 +308,7 @@ async function deliverOutboundPayloadsCore(params: {
|
|||||||
accountId,
|
accountId,
|
||||||
replyToId: params.replyToId,
|
replyToId: params.replyToId,
|
||||||
threadId: params.threadId,
|
threadId: params.threadId,
|
||||||
username: params.username,
|
identity: params.identity,
|
||||||
icon_url: params.icon_url,
|
|
||||||
icon_emoji: params.icon_emoji,
|
|
||||||
gifPlayback: params.gifPlayback,
|
gifPlayback: params.gifPlayback,
|
||||||
silent: params.silent,
|
silent: params.silent,
|
||||||
});
|
});
|
||||||
|
|||||||
37
src/infra/outbound/identity.ts
Normal file
37
src/infra/outbound/identity.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import type { OpenClawConfig } from "../../config/config.js";
|
||||||
|
import { resolveAgentAvatar } from "../../agents/identity-avatar.js";
|
||||||
|
import { resolveAgentIdentity } from "../../agents/identity.js";
|
||||||
|
|
||||||
|
export type OutboundIdentity = {
|
||||||
|
name?: string;
|
||||||
|
avatarUrl?: string;
|
||||||
|
emoji?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function normalizeOutboundIdentity(
|
||||||
|
identity?: OutboundIdentity | null,
|
||||||
|
): OutboundIdentity | undefined {
|
||||||
|
if (!identity) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const name = identity.name?.trim() || undefined;
|
||||||
|
const avatarUrl = identity.avatarUrl?.trim() || undefined;
|
||||||
|
const emoji = identity.emoji?.trim() || undefined;
|
||||||
|
if (!name && !avatarUrl && !emoji) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return { name, avatarUrl, emoji };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveAgentOutboundIdentity(
|
||||||
|
cfg: OpenClawConfig,
|
||||||
|
agentId: string,
|
||||||
|
): OutboundIdentity | undefined {
|
||||||
|
const agentIdentity = resolveAgentIdentity(cfg, agentId);
|
||||||
|
const avatar = resolveAgentAvatar(cfg, agentId);
|
||||||
|
return normalizeOutboundIdentity({
|
||||||
|
name: agentIdentity?.name,
|
||||||
|
emoji: agentIdentity?.emoji,
|
||||||
|
avatarUrl: avatar.kind === "remote" ? avatar.url : undefined,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -27,19 +27,23 @@ type SlackRecipient =
|
|||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type SlackSendIdentity = {
|
||||||
|
username?: string;
|
||||||
|
iconUrl?: string;
|
||||||
|
iconEmoji?: string;
|
||||||
|
};
|
||||||
|
|
||||||
type SlackSendOpts = {
|
type SlackSendOpts = {
|
||||||
token?: string;
|
token?: string;
|
||||||
accountId?: string;
|
accountId?: string;
|
||||||
mediaUrl?: string;
|
mediaUrl?: string;
|
||||||
client?: WebClient;
|
client?: WebClient;
|
||||||
threadTs?: string;
|
threadTs?: string;
|
||||||
username?: string;
|
identity?: SlackSendIdentity;
|
||||||
icon_url?: string;
|
|
||||||
icon_emoji?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function hasCustomIdentity(opts: SlackSendOpts): boolean {
|
function hasCustomIdentity(identity?: SlackSendIdentity): boolean {
|
||||||
return Boolean(opts.username || opts.icon_url || opts.icon_emoji);
|
return Boolean(identity?.username || identity?.iconUrl || identity?.iconEmoji);
|
||||||
}
|
}
|
||||||
|
|
||||||
function isSlackCustomizeScopeError(err: unknown): boolean {
|
function isSlackCustomizeScopeError(err: unknown): boolean {
|
||||||
@@ -73,7 +77,7 @@ async function postSlackMessageBestEffort(params: {
|
|||||||
channelId: string;
|
channelId: string;
|
||||||
text: string;
|
text: string;
|
||||||
threadTs?: string;
|
threadTs?: string;
|
||||||
opts: SlackSendOpts;
|
identity?: SlackSendIdentity;
|
||||||
}) {
|
}) {
|
||||||
const basePayload = {
|
const basePayload = {
|
||||||
channel: params.channelId,
|
channel: params.channelId,
|
||||||
@@ -83,26 +87,26 @@ async function postSlackMessageBestEffort(params: {
|
|||||||
try {
|
try {
|
||||||
// Slack Web API types model icon_url and icon_emoji as mutually exclusive.
|
// Slack Web API types model icon_url and icon_emoji as mutually exclusive.
|
||||||
// Build payloads in explicit branches so TS and runtime stay aligned.
|
// Build payloads in explicit branches so TS and runtime stay aligned.
|
||||||
if (params.opts.icon_url) {
|
if (params.identity?.iconUrl) {
|
||||||
return await params.client.chat.postMessage({
|
return await params.client.chat.postMessage({
|
||||||
...basePayload,
|
...basePayload,
|
||||||
...(params.opts.username ? { username: params.opts.username } : {}),
|
...(params.identity.username ? { username: params.identity.username } : {}),
|
||||||
icon_url: params.opts.icon_url,
|
icon_url: params.identity.iconUrl,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (params.opts.icon_emoji) {
|
if (params.identity?.iconEmoji) {
|
||||||
return await params.client.chat.postMessage({
|
return await params.client.chat.postMessage({
|
||||||
...basePayload,
|
...basePayload,
|
||||||
...(params.opts.username ? { username: params.opts.username } : {}),
|
...(params.identity.username ? { username: params.identity.username } : {}),
|
||||||
icon_emoji: params.opts.icon_emoji,
|
icon_emoji: params.identity.iconEmoji,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return await params.client.chat.postMessage({
|
return await params.client.chat.postMessage({
|
||||||
...basePayload,
|
...basePayload,
|
||||||
...(params.opts.username ? { username: params.opts.username } : {}),
|
...(params.identity?.username ? { username: params.identity.username } : {}),
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (!hasCustomIdentity(params.opts) || !isSlackCustomizeScopeError(err)) {
|
if (!hasCustomIdentity(params.identity) || !isSlackCustomizeScopeError(err)) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
logVerbose("slack send: missing chat:write.customize, retrying without custom identity");
|
logVerbose("slack send: missing chat:write.customize, retrying without custom identity");
|
||||||
@@ -262,7 +266,7 @@ export async function sendMessageSlack(
|
|||||||
channelId,
|
channelId,
|
||||||
text: chunk,
|
text: chunk,
|
||||||
threadTs: opts.threadTs,
|
threadTs: opts.threadTs,
|
||||||
opts,
|
identity: opts.identity,
|
||||||
});
|
});
|
||||||
lastMessageId = response.ts ?? lastMessageId;
|
lastMessageId = response.ts ?? lastMessageId;
|
||||||
}
|
}
|
||||||
@@ -273,7 +277,7 @@ export async function sendMessageSlack(
|
|||||||
channelId,
|
channelId,
|
||||||
text: chunk,
|
text: chunk,
|
||||||
threadTs: opts.threadTs,
|
threadTs: opts.threadTs,
|
||||||
opts,
|
identity: opts.identity,
|
||||||
});
|
});
|
||||||
lastMessageId = response.ts ?? lastMessageId;
|
lastMessageId = response.ts ?? lastMessageId;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user