openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
1,519 lines • 60.5 kB
JavaScript
import { i as formatErrorMessage } from "./errors-BXgSefBE.js";
import { y as truncateUtf16Safe } from "./utils-CCC-BEJH.js";
import { m as resolveThreadSessionKeys } from "./session-key-B_NoIfpX.js";
import { a as shouldLogVerbose, r as logVerbose, t as danger } from "./globals-GTrXU4s9.js";
import { a as getReplyPayloadTtsSupplement, n as buildTtsSupplementMediaPayload, o as isReplyPayloadNonTerminalToolErrorWarning } from "./reply-payload-CZJ2cgZc.js";
import { u as resolveStorePath } from "./paths-NEwU8m3X.js";
import { V as resolveSessionStoreEntry } from "./store-load-C4uq6Lrq.js";
import { o as readSessionUpdatedAt } from "./store-Qsgtu-0y.js";
import { d as resolveAndPersistSessionFile, r as readLatestAssistantTextFromSessionTranscript } from "./transcript-Cw-EfeYD.js";
import { d as stripReasoningTagsFromText } from "./assistant-visible-text-QdHceFYu.js";
import { r as stripInlineDirectiveTagsForDelivery } from "./directive-tags-Dg-nkHHo.js";
import { m as resolveSendableOutboundReplyParts } from "./reply-payload-D-VMfpYI.js";
import { t as buildAgentSessionKey } from "./resolve-route-xcIS8NFL.js";
import { l as resolvePinnedMainDmOwnerFromAllowlist } from "./dm-policy-shared-BjnswXJt.js";
import { r as getAgentScopedMediaLocalRoots } from "./local-roots-BqdtViGS.js";
import { i as resolveHumanDelayConfig, t as resolveAckReaction } from "./identity-Qcvvo6SY.js";
import { M as resolveTranscriptBackedChannelFinalText, S as resolveChannelStreamingBlockEnabled, i as buildChannelProgressDraftLineForEntry, j as resolveChannelStreamingSuppressDefaultToolProgressMessages, k as resolveChannelStreamingPreviewToolProgress, r as buildChannelProgressDraftLine } from "./streaming-_TVeMLGC.js";
import { s as resolveChunkMode } from "./chunk-Cpb8JO1x.js";
import { a as deliverWithFinalizableLivePreviewAdapter, r as defineFinalizableLivePreviewAdapter } from "./live-DjttXqqq.js";
import "./error-runtime-C8vbtAJt.js";
import "./media-runtime-fgpmX6eL.js";
import "./runtime-env-D_2q8-VK.js";
import "./security-runtime-CQm7DD1u.js";
import { t as evaluateSupplementalContextVisibility } from "./context-visibility-C5CaKMWO.js";
import { t as convertMarkdownTables } from "./tables-BgtXxld3.js";
import "./text-chunking-D45TeeEs.js";
import "./text-utility-runtime-BZ3jGr-e.js";
import { t as EmbeddedBlockChunker } from "./embedded-agent-block-chunker-DH4NVUQq.js";
import "./routing-CQ63ICPX.js";
import { a as resolveEnvelopeFormatOptions, r as formatInboundEnvelope } from "./envelope-IMcsY0tl.js";
import { i as shouldAckReaction } from "./ack-reactions-D4-6N7fi.js";
import { E as toInboundMediaFacts, T as toHistoryMediaEntries, h as hasFinalChannelTurnDispatch, v as recordChannelBotPairLoopAndCheckSuppression, y as buildChannelInboundEventContext } from "./kernel-BdraY_qZ.js";
import { t as recordInboundSession } from "./session-Do0yFgU8.js";
import { n as resolveChannelSourceReplyDeliveryMode, t as createChannelReplyPipeline } from "./reply-pipeline-eJPstCAz.js";
import { t as createChannelHistoryWindow } from "./history-window-B0hyPVKC.js";
import { t as resolveMarkdownTableMode } from "./markdown-tables-LfSv7QSx.js";
import { n as isDangerousNameMatchingEnabled } from "./dangerous-name-matching-D4QRC91G.js";
import "./reply-history-Y9HXucO9.js";
import { t as resolveChannelContextVisibilityMode } from "./context-visibility-D4NM8ac1.js";
import "./reply-chunking-C9pgDFVG.js";
import { r as dispatchChannelInboundReply } from "./inbound-reply-dispatch-BpzI7GIp.js";
import { a as resolveChannelDraftStreamingChunking, i as createChannelProgressDraftCompositor } from "./channel-outbound-B3_Zy-kG.js";
import { n as createFinalizableDraftLifecycle } from "./draft-stream-controls-C8Kxicze.js";
import { t as logAckFailure } from "./logging-gUWPKC5g.js";
import "./conversation-runtime-VjM1-D7a.js";
import "./agent-runtime-CvpQRNVf.js";
import "./markdown-table-runtime-j_C4iTF_.js";
import "./dangerous-name-runtime-zP2lSdMQ.js";
import "./channel-inbound-bl7VmTdr.js";
import { a as DEFAULT_TIMING, c as createStatusReactionController } from "./channel-feedback-Bi1IYmE_.js";
import { t as loadSessionStore } from "./session-store-runtime-rvUx2Fil.js";
import { f as resolveDiscordMaxLinesPerMessage, s as resolveDiscordAccount } from "./accounts-BI_6uJlY.js";
import { n as resolveDiscordChannelId } from "./target-parsing-CxulEx_f.js";
import { $ as createChannelMessage, Ht as MessageFlags, It as ChannelType, it as editChannelMessage, nt as deleteChannelMessage } from "./discord-DdqXEGEm.js";
import { I as createDiscordRestClient, L as createDiscordRuntimeAccountContext, _ as resolveDiscordMessageFlags, d as resolveDiscordTargetChannelId } from "./send.shared-SdWkx4sF.js";
import { t as chunkDiscordTextWithMode } from "./chunk-D_QII2-W.js";
import { i as resolveTimestampMs } from "./format-DZW075F7.js";
import { a as normalizeDiscordSlug, r as normalizeDiscordAllowList } from "./allow-list-CBhA2l4a.js";
import { d as editMessageDiscord, i as removeReactionDiscord, n as reactMessageDiscord } from "./send-DU6EOsCH.js";
import { i as rewriteDiscordKnownMentions, n as discordTextHasTargetedMention, t as discordTextHasBroadcastMention } from "./mentions-Da7srbtk.js";
import "./targets-B5qkz8Y1.js";
import { t as beginDiscordInboundEventDeliveryCorrelation } from "./inbound-event-delivery-BI9WnEey.js";
import { t as resolveDiscordConversationIdentity } from "./conversation-identity-LwFFXgXY.js";
import { t as DISCORD_TEXT_CHUNK_LIMIT } from "./outbound-adapter-AcNudkf5.js";
import { t as resolveDiscordPreviewStreamMode } from "./preview-streaming-B4v7xLda.js";
import { n as DISCORD_ATTACHMENT_TOTAL_TIMEOUT_MS, t as DISCORD_ATTACHMENT_IDLE_TIMEOUT_MS } from "./timeouts-BG9tJyKJ.js";
import { a as resolveForwardedMediaList, o as resolveMediaList, r as resolveDiscordMessageText, s as resolveReferencedReplyMediaList } from "./message-utils-BjIDHgFC.js";
import { c as resolveDiscordThreadStarter, r as resolveDiscordAutoThreadReplyPlan } from "./threading-D6pXQbX7.js";
import { t as createDiscordReplyTypingFeedback } from "./reply-typing-feedback-DYF0iQs7.js";
import { n as buildDiscordInboundAccessContext, r as createDiscordSupplementalContextAccessChecker } from "./inbound-context-DaAiPgjT.js";
import { a as resolveReplyContext, i as buildGuildLabel, n as sanitizeDiscordFrontChannelReplyPayloads, r as buildDirectLabel, t as deliverDiscordReply } from "./reply-delivery-CRZ075t2.js";
import path from "node:path";
//#region extensions/discord/src/monitor/ack-reactions.ts
function createDiscordAckReactionContext(params) {
return {
rest: params.rest,
...createDiscordRuntimeAccountContext({
cfg: params.cfg,
accountId: params.accountId
})
};
}
function createDiscordAckReactionAdapter(params) {
return {
setReaction: async (emoji) => {
await reactMessageDiscord(params.channelId, params.messageId, emoji, params.reactionContext);
},
removeReaction: async (emoji) => {
await removeReactionDiscord(params.channelId, params.messageId, emoji, params.reactionContext);
}
};
}
function queueInitialDiscordAckReaction(params) {
if (params.enabled) {
params.statusReactions.setQueued();
return;
}
if (!params.shouldSendAckReaction || !params.ackReaction) return;
params.reactionAdapter.setReaction(params.ackReaction).catch((err) => {
logAckFailure({
log: logVerbose,
channel: "discord",
target: params.target,
error: err
});
});
}
//#endregion
//#region extensions/discord/src/monitor/message-handler.context.ts
function normalizeDiscordDmOwnerEntry(entry) {
const candidate = normalizeDiscordAllowList([entry], [
"discord:",
"user:",
"pk:"
])?.ids.values().next().value;
return typeof candidate === "string" && /^\d+$/.test(candidate) ? candidate : void 0;
}
function isContextAborted(abortSignal) {
return Boolean(abortSignal?.aborted);
}
async function buildDiscordMessageProcessContext(params) {
const { ctx, text, mediaList } = params;
const { cfg, discordConfig, accountId, runtime, botUserId, mediaMaxBytes, discordRestFetch, abortSignal, guildHistories, historyLimit, replyToMode, message, author, sender, canonicalMessageId, data, client, channelInfo, channelName, messageChannelId, isGuildMessage, isDirectMessage, baseText, preflightAudioTranscript, threadChannel, threadParentId, threadParentName, threadParentType, threadName, displayChannelSlug, guildInfo, guildSlug, memberRoleIds, channelConfig, baseSessionKey, boundSessionKey, route, commandAuthorized } = ctx;
const fromLabel = isDirectMessage ? buildDirectLabel(author) : buildGuildLabel({
guild: data.guild ?? void 0,
channelName: channelName ?? messageChannelId,
channelId: messageChannelId
});
const senderLabel = sender.label;
const isForumParent = threadParentType === ChannelType.GuildForum || threadParentType === ChannelType.GuildMedia;
const forumParentSlug = isForumParent && threadParentName ? normalizeDiscordSlug(threadParentName) : "";
const threadChannelId = threadChannel?.id;
const threadParentInheritanceEnabled = discordConfig?.thread?.inheritParent ?? false;
const forumContextLine = Boolean(threadChannelId && isForumParent && forumParentSlug) && message.id === threadChannelId ? `[Forum parent: #${forumParentSlug}]` : null;
const groupChannel = isGuildMessage && displayChannelSlug ? `#${displayChannelSlug}` : void 0;
const groupSubject = isDirectMessage ? void 0 : groupChannel;
const senderName = sender.isPluralKit ? sender.name ?? author.username : data.member?.nickname ?? author.globalName ?? author.username;
const senderUsername = sender.isPluralKit ? sender.tag ?? sender.name ?? author.username : author.username;
const { groupSystemPrompt, ownerAllowFrom, untrustedContext } = buildDiscordInboundAccessContext({
channelConfig,
guildInfo,
sender: {
id: sender.id,
name: sender.name,
tag: sender.tag
},
allowNameMatching: isDangerousNameMatchingEnabled(discordConfig),
isGuild: isGuildMessage,
channelTopic: channelInfo?.topic
});
const pinnedMainDmOwner = isDirectMessage ? resolvePinnedMainDmOwnerFromAllowlist({
dmScope: cfg.session?.dmScope,
allowFrom: channelConfig?.users ?? guildInfo?.users,
normalizeEntry: normalizeDiscordDmOwnerEntry
}) : null;
const contextVisibilityMode = resolveChannelContextVisibilityMode({
cfg,
channel: "discord",
accountId
});
const isSupplementalContextSenderAllowed = createDiscordSupplementalContextAccessChecker({
channelConfig,
guildInfo,
allowNameMatching: isDangerousNameMatchingEnabled(discordConfig),
isGuild: isGuildMessage
});
const storePath = resolveStorePath(cfg.session?.store, { agentId: route.agentId });
const envelopeOptions = resolveEnvelopeFormatOptions(cfg);
const previousTimestamp = readSessionUpdatedAt({
storePath,
sessionKey: route.sessionKey
});
const channelHistory = createChannelHistoryWindow({ historyMap: guildHistories });
const isRoomEvent = ctx.inboundEventKind === "room_event";
let combinedBody = formatInboundEnvelope({
channel: "Discord",
from: fromLabel,
timestamp: resolveTimestampMs(message.timestamp),
body: text,
chatType: isDirectMessage ? "direct" : "channel",
senderLabel,
previousTimestamp,
envelope: envelopeOptions
});
const shouldIncludeChannelHistory = !isDirectMessage && (isRoomEvent || !(isGuildMessage && channelConfig?.autoThread && !threadChannel));
if (shouldIncludeChannelHistory) combinedBody = channelHistory.buildPendingContext({
historyKey: messageChannelId,
limit: historyLimit,
currentMessage: combinedBody,
formatEntry: (entry) => formatInboundEnvelope({
channel: "Discord",
from: fromLabel,
timestamp: entry.timestamp,
body: `${entry.body} [id:${entry.messageId ?? "unknown"} channel:${messageChannelId}]`,
chatType: "channel",
senderLabel: entry.sender,
envelope: envelopeOptions
})
});
const replyContext = resolveReplyContext(message, resolveDiscordMessageText);
const replySenderAllowed = replyContext ? isSupplementalContextSenderAllowed({
id: replyContext.senderId,
name: replyContext.senderName,
tag: replyContext.senderTag,
memberRoleIds: replyContext.memberRoleIds
}) : true;
const replyVisible = evaluateSupplementalContextVisibility({
mode: contextVisibilityMode,
kind: "quote",
senderAllowed: replySenderAllowed
}).include;
if (replyContext && !replyVisible && isGuildMessage) logVerbose(`discord: drop reply context (mode=${contextVisibilityMode})`);
if (forumContextLine) combinedBody = `${combinedBody}\n${forumContextLine}`;
let threadStarterBody;
let threadLabel;
let parentSessionKey;
let modelParentSessionKey;
if (threadChannel) {
if (channelConfig?.includeThreadStarter !== false) {
const starter = await resolveDiscordThreadStarter({
channel: threadChannel,
client,
parentId: threadParentId,
parentType: threadParentType,
resolveTimestampMs
});
if (starter?.text) if (evaluateSupplementalContextVisibility({
mode: contextVisibilityMode,
kind: "thread",
senderAllowed: isSupplementalContextSenderAllowed({
id: starter.authorId,
name: starter.authorName ?? starter.author,
tag: starter.authorTag,
memberRoleIds: starter.memberRoleIds
})
}).include) threadStarterBody = starter.text;
else logVerbose(`discord: drop thread starter context (mode=${contextVisibilityMode})`);
}
const parentName = threadParentName ?? "parent";
threadLabel = threadName ? `Discord thread #${normalizeDiscordSlug(parentName)} › ${threadName}` : `Discord thread #${normalizeDiscordSlug(parentName)}`;
if (threadParentId) {
parentSessionKey = buildAgentSessionKey({
agentId: route.agentId,
channel: route.channel,
peer: {
kind: "channel",
id: threadParentId
}
});
modelParentSessionKey = parentSessionKey;
}
if (!threadParentInheritanceEnabled) parentSessionKey = void 0;
}
const preflightAudioIndex = preflightAudioTranscript === void 0 ? -1 : mediaList.findIndex((media) => media.contentType?.startsWith("audio/"));
const threadKeys = resolveThreadSessionKeys({
baseSessionKey,
threadId: threadChannel ? messageChannelId : void 0,
parentSessionKey,
useSuffix: false
});
const replyPlan = await resolveDiscordAutoThreadReplyPlan({
client,
message,
messageChannelId,
isGuildMessage,
channelConfig: isRoomEvent ? null : channelConfig,
threadChannel,
channelType: channelInfo?.type,
channelName: channelInfo?.name,
channelDescription: channelInfo?.topic,
baseText: baseText ?? "",
combinedBody,
replyToMode,
agentId: route.agentId,
channel: route.channel,
cfg,
threadParentInheritanceEnabled
});
const deliverTarget = replyPlan.deliverTarget;
const replyTarget = replyPlan.replyTarget;
const replyReference = replyPlan.replyReference;
const autoThreadContext = replyPlan.autoThreadContext;
const effectiveFrom = isDirectMessage ? `discord:${author.id}` : autoThreadContext?.From ?? `discord:channel:${messageChannelId}`;
const dmConversationTarget = isDirectMessage ? resolveDiscordConversationIdentity({
isDirectMessage,
userId: author.id
}) : void 0;
const effectiveTo = autoThreadContext?.To ?? dmConversationTarget ?? replyTarget;
if (!effectiveTo) {
runtime.error(danger("discord: missing reply target"));
return null;
}
const lastRouteTo = dmConversationTarget ?? effectiveTo;
const inboundHistory = shouldIncludeChannelHistory ? channelHistory.buildInboundHistory({
historyKey: messageChannelId,
limit: historyLimit
}) : void 0;
const originatingTo = autoThreadContext?.OriginatingTo ?? dmConversationTarget ?? replyTarget;
const effectiveSessionKey = boundSessionKey ?? autoThreadContext?.SessionKey ?? threadKeys.sessionKey;
const effectivePreviousTimestamp = effectiveSessionKey === route.sessionKey ? previousTimestamp : readSessionUpdatedAt({
storePath,
sessionKey: effectiveSessionKey
});
const ctxPayload = await buildChannelInboundEventContext({
channel: "discord",
resolveSupplementalMedia: true,
contextVisibility: contextVisibilityMode,
accountId: route.accountId,
messageId: canonicalMessageId ?? message.id,
messageIdFull: canonicalMessageId && canonicalMessageId !== message.id ? message.id : void 0,
timestamp: resolveTimestampMs(message.timestamp),
from: effectiveFrom,
sender: {
id: sender.id,
name: senderName,
username: senderUsername,
tag: sender.tag,
roles: memberRoleIds,
displayLabel: senderLabel
},
conversation: {
kind: isDirectMessage ? "direct" : "channel",
id: messageChannelId,
label: fromLabel,
spaceId: isGuildMessage ? (guildInfo?.id ?? guildSlug) || void 0 : void 0,
parentId: threadChannel ? threadParentId : void 0,
threadId: threadChannel?.id ?? autoThreadContext?.createdThreadId ?? void 0
},
route: {
agentId: route.agentId,
accountId: route.accountId,
routeSessionKey: route.sessionKey,
dispatchSessionKey: effectiveSessionKey,
parentSessionKey: autoThreadContext?.ParentSessionKey ?? threadKeys.parentSessionKey,
modelParentSessionKey: autoThreadContext?.ModelParentSessionKey ?? modelParentSessionKey ?? void 0
},
reply: {
to: effectiveTo,
...originatingTo !== effectiveTo ? { originatingTo } : {}
},
message: {
inboundEventKind: ctx.inboundEventKind,
body: combinedBody,
rawBody: preflightAudioTranscript ?? baseText,
bodyForAgent: preflightAudioTranscript ?? baseText ?? text,
commandBody: preflightAudioTranscript ?? baseText,
inboundHistory
},
access: {
mentions: {
canDetectMention: ctx.canDetectMention,
wasMentioned: ctx.effectiveWasMentioned,
hasAnyMention: ctx.hasAnyMention,
requireMention: ctx.shouldRequireMention,
effectiveWasMentioned: ctx.effectiveWasMentioned
},
commands: { authorized: commandAuthorized }
},
commandTurn: {
kind: "text-slash",
source: "text",
authorized: commandAuthorized,
body: preflightAudioTranscript ?? baseText
},
media: toInboundMediaFacts(mediaList, { transcribed: (_media, index) => index === preflightAudioIndex }),
supplemental: {
quote: replyContext && replyVisible ? {
id: replyContext.id,
body: replyContext.body,
sender: replyContext.sender,
senderAllowed: replySenderAllowed,
isSelf: Boolean(botUserId && replyContext.senderId === botUserId),
media: async () => {
const referencedReplyMediaList = await resolveReferencedReplyMediaList(message, mediaMaxBytes, {
fetchImpl: discordRestFetch,
ssrfPolicy: cfg.browser?.ssrfPolicy,
readIdleTimeoutMs: DISCORD_ATTACHMENT_IDLE_TIMEOUT_MS,
totalTimeoutMs: DISCORD_ATTACHMENT_TOTAL_TIMEOUT_MS,
abortSignal
});
return isContextAborted(abortSignal) ? [] : toInboundMediaFacts(referencedReplyMediaList);
}
} : void 0,
thread: {
starterBody: !effectivePreviousTimestamp ? threadStarterBody : void 0,
label: threadLabel,
senderAllowed: true
},
groupSystemPrompt: isGuildMessage ? groupSystemPrompt : void 0
},
extra: {
...preflightAudioTranscript !== void 0 ? { Transcript: preflightAudioTranscript } : {},
GroupSubject: groupSubject,
GroupChannel: groupChannel,
UntrustedStructuredContext: untrustedContext,
OwnerAllowFrom: ownerAllowFrom
}
});
const persistedSessionKey = ctxPayload.SessionKey ?? route.sessionKey;
if (isRoomEvent && shouldIncludeChannelHistory) await channelHistory.recordWithMedia({
historyKey: messageChannelId,
limit: historyLimit,
entry: {
sender: senderName,
body: text,
timestamp: resolveTimestampMs(message.timestamp),
messageId: message.id
},
media: toHistoryMediaEntries(mediaList, { messageId: message.id }),
messageId: message.id
});
if (shouldLogVerbose()) {
const preview = truncateUtf16Safe(combinedBody, 200).replace(/\n/g, "\\n");
logVerbose(`discord inbound: channel=${messageChannelId} deliver=${deliverTarget} from=${ctxPayload.From} preview="${preview}"`);
}
return {
ctxPayload,
persistedSessionKey,
turn: {
storePath,
record: {
updateLastRoute: {
sessionKey: persistedSessionKey,
channel: "discord",
to: lastRouteTo,
accountId: route.accountId,
mainDmOwnerPin: isDirectMessage && persistedSessionKey === route.mainSessionKey && pinnedMainDmOwner ? {
ownerRecipient: pinnedMainDmOwner,
senderRecipient: author.id,
onSkip: ({ ownerRecipient, senderRecipient }) => {
logVerbose(`discord: skip main-session last route for ${senderRecipient} (pinned owner ${ownerRecipient})`);
}
} : void 0
},
onRecordError: (err) => {
logVerbose(`discord: failed updating session meta: ${String(err)}`);
}
}
},
replyPlan,
deliverTarget,
replyTarget,
replyReference
};
}
//#endregion
//#region extensions/discord/src/draft-chunking.ts
function resolveDiscordDraftStreamingChunking(cfg, accountId) {
return resolveChannelDraftStreamingChunking(cfg, "discord", accountId, { fallbackLimit: DISCORD_TEXT_CHUNK_LIMIT });
}
//#endregion
//#region extensions/discord/src/draft-stream.ts
/** Discord messages cap at 2000 characters. */
const DISCORD_STREAM_MAX_CHARS = 2e3;
const DEFAULT_THROTTLE_MS = 1200;
const DISCORD_PREVIEW_ALLOWED_MENTIONS = { parse: [] };
function createDiscordDraftStream(params) {
const maxChars = Math.min(params.maxChars ?? DISCORD_STREAM_MAX_CHARS, DISCORD_STREAM_MAX_CHARS);
const throttleMs = Math.max(250, params.throttleMs ?? DEFAULT_THROTTLE_MS);
const minInitialChars = params.minInitialChars;
const channelId = params.channelId;
const rest = params.rest;
const flags = resolveDiscordMessageFlags({ suppressEmbeds: params.suppressEmbeds });
const resolveReplyToMessageId = () => typeof params.replyToMessageId === "function" ? params.replyToMessageId() : params.replyToMessageId;
const streamState = {
stopped: false,
final: false
};
let streamMessageId;
let lastSentText = "";
const sendOrEditStreamMessage = async (text) => {
if (streamState.stopped && !streamState.final) return false;
const trimmed = text.trimEnd();
if (!trimmed) return false;
if (trimmed.length > maxChars) {
streamState.stopped = true;
params.warn?.(`discord stream preview stopped (text length ${trimmed.length} > ${maxChars})`);
return false;
}
if (trimmed === lastSentText) return true;
if (streamMessageId === void 0 && minInitialChars != null && !streamState.final) {
if (trimmed.length < minInitialChars) return false;
}
lastSentText = trimmed;
try {
if (streamMessageId !== void 0) {
await editChannelMessage(rest, channelId, streamMessageId, { body: {
content: trimmed,
allowed_mentions: DISCORD_PREVIEW_ALLOWED_MENTIONS,
...flags ? { flags } : {}
} });
return true;
}
const replyToMessageId = resolveReplyToMessageId()?.trim();
const messageReference = replyToMessageId ? {
message_id: replyToMessageId,
fail_if_not_exists: false
} : void 0;
const sentMessageId = (await createChannelMessage(rest, channelId, { body: {
content: trimmed,
allowed_mentions: DISCORD_PREVIEW_ALLOWED_MENTIONS,
...flags ? { flags } : {},
...messageReference ? { message_reference: messageReference } : {}
} }))?.id;
if (typeof sentMessageId !== "string" || !sentMessageId) {
streamState.stopped = true;
params.warn?.("discord stream preview stopped (missing message id from send)");
return false;
}
streamMessageId = sentMessageId;
return true;
} catch (err) {
streamState.stopped = true;
params.warn?.(`discord stream preview failed: ${formatErrorMessage(err)}`);
return false;
}
};
const readMessageId = () => streamMessageId;
const clearMessageId = () => {
streamMessageId = void 0;
};
const isValidStreamMessageId = (value) => typeof value === "string";
const deleteStreamMessage = async (messageId) => {
await deleteChannelMessage(rest, channelId, messageId);
};
const { loop, update, stop, clear, discardPending, seal } = createFinalizableDraftLifecycle({
throttleMs,
state: streamState,
sendOrEditStreamMessage,
readMessageId,
clearMessageId,
isValidMessageId: isValidStreamMessageId,
deleteMessage: deleteStreamMessage,
warn: params.warn,
warnPrefix: "discord stream preview cleanup failed"
});
const forceNewMessage = () => {
streamMessageId = void 0;
lastSentText = "";
loop.resetPending();
};
const deleteCurrentMessage = async () => {
loop.resetPending();
await loop.waitForInFlight();
const messageId = streamMessageId;
streamMessageId = void 0;
lastSentText = "";
loop.resetThrottleWindow();
if (!isValidStreamMessageId(messageId)) return;
try {
await deleteStreamMessage(messageId);
} catch (err) {
params.warn?.(`discord stream preview cleanup failed: ${formatErrorMessage(err)}`);
}
};
params.log?.(`discord stream preview ready (maxChars=${maxChars}, throttleMs=${throttleMs})`);
return {
update,
flush: loop.flush,
messageId: () => streamMessageId,
clear,
deleteCurrentMessage,
discardPending,
seal,
stop,
forceNewMessage
};
}
//#endregion
//#region extensions/discord/src/monitor/message-handler.draft-preview.ts
function createDiscordDraftPreviewController(params) {
const discordStreamMode = resolveDiscordPreviewStreamMode(params.discordConfig);
const draftMaxChars = Math.min(params.textLimit, 2e3);
const accountBlockStreamingEnabled = resolveChannelStreamingBlockEnabled(params.discordConfig) ?? params.cfg.agents?.defaults?.blockStreamingDefault === "on";
const canStreamProgressDraftForToolOnlySource = params.sourceRepliesAreToolOnly && discordStreamMode === "progress";
const draftStream = (!params.sourceRepliesAreToolOnly || canStreamProgressDraftForToolOnlySource) && discordStreamMode !== "off" && !accountBlockStreamingEnabled ? createDiscordDraftStream({
rest: params.deliveryRest,
channelId: params.deliverChannelId,
maxChars: draftMaxChars,
replyToMessageId: () => params.replyReference.peek(),
minInitialChars: discordStreamMode === "progress" ? 0 : 30,
suppressEmbeds: params.discordConfig?.suppressEmbeds ?? true,
throttleMs: 1200,
log: params.log,
warn: params.log
}) : void 0;
const draftChunking = draftStream && discordStreamMode === "block" ? resolveDiscordDraftStreamingChunking(params.cfg, params.accountId) : void 0;
const shouldSplitPreviewMessages = discordStreamMode === "block";
const draftChunker = draftChunking ? new EmbeddedBlockChunker(draftChunking) : void 0;
let lastPartialText = "";
let draftText = "";
let hasStreamedMessage = false;
let finalizedViaPreviewMessage = false;
let finalReplyDelivered = false;
const previewToolProgressEnabled = Boolean(draftStream) && resolveChannelStreamingPreviewToolProgress(params.discordConfig);
const suppressDefaultToolProgressMessages = Boolean(draftStream) && resolveChannelStreamingSuppressDefaultToolProgressMessages(params.discordConfig, {
draftStreamActive: true,
previewToolProgressEnabled
});
const progressSeed = `${params.accountId}:${params.deliverChannelId}`;
const progressDraft = createChannelProgressDraftCompositor({
entry: params.discordConfig,
mode: discordStreamMode,
active: Boolean(draftStream),
seed: progressSeed,
update: async (previewText, options) => {
lastPartialText = previewText;
draftText = previewText;
hasStreamedMessage = true;
draftChunker?.reset();
draftStream?.update(previewText);
if (options?.flush) await draftStream?.flush();
},
deleteCurrent: async () => {
lastPartialText = "";
draftText = "";
hasStreamedMessage = false;
if (draftStream?.messageId()) await draftStream.deleteCurrentMessage();
},
isEmptyLine: isEmptyDiscordProgressLine,
shouldStartNow: shouldStartDiscordProgressDraftNow
});
const resetProgressState = () => {
lastPartialText = "";
draftText = "";
draftChunker?.reset();
progressDraft.reset();
};
const forceNewMessageIfNeeded = () => {
if (shouldSplitPreviewMessages && hasStreamedMessage) {
params.log("discord: calling forceNewMessage() for draft stream");
draftStream?.forceNewMessage();
}
resetProgressState();
};
return {
draftStream,
previewToolProgressEnabled,
commentaryProgressEnabled: progressDraft.commentaryProgressEnabled,
suppressDefaultToolProgressMessages,
get isProgressMode() {
return discordStreamMode === "progress";
},
get hasProgressDraftStarted() {
return progressDraft.hasStarted;
},
get finalizedViaPreviewMessage() {
return finalizedViaPreviewMessage;
},
markFinalReplyStarted() {
progressDraft.markFinalReplyStarted();
},
markFinalReplyDelivered() {
finalReplyDelivered = true;
progressDraft.markFinalReplyDelivered();
},
markPreviewFinalized() {
finalizedViaPreviewMessage = true;
},
disableBlockStreamingForDraft: draftStream ? true : void 0,
async startProgressDraft() {
if (!draftStream || discordStreamMode !== "progress") return;
await progressDraft.start();
},
async pushToolProgress(line, options) {
await progressDraft.pushToolProgress(line, options);
},
async pushReasoningProgress(text, options) {
await progressDraft.pushReasoningProgress(text, options);
},
async pushCommentaryProgress(text, options) {
await progressDraft.pushCommentaryProgress(text, options);
},
resolvePreviewFinalText(text) {
if (typeof text !== "string") return;
const formatted = convertMarkdownTables(stripInlineDirectiveTagsForDelivery(text).text, params.tableMode);
const chunks = chunkDiscordTextWithMode(formatted, {
maxChars: draftMaxChars,
maxLines: params.maxLinesPerMessage,
chunkMode: params.chunkMode
});
if (!chunks.length && formatted) chunks.push(formatted);
if (chunks.length !== 1) return;
const trimmed = chunks[0].trim();
if (!trimmed) return;
const currentPreviewText = discordStreamMode === "block" ? draftText : lastPartialText;
if (currentPreviewText && currentPreviewText.startsWith(trimmed) && trimmed.length < currentPreviewText.length) return;
return trimmed;
},
updateFromPartial(text) {
if (!draftStream || !text) return;
const cleaned = stripInlineDirectiveTagsForDelivery(stripReasoningTagsFromText(text, {
mode: "strict",
trim: "both"
})).text;
if (!cleaned || cleaned.startsWith("Reasoning:\n")) return;
if (cleaned === lastPartialText) return;
if (discordStreamMode === "progress") return;
progressDraft.suppress();
hasStreamedMessage = true;
if (discordStreamMode === "partial") {
if (lastPartialText && lastPartialText.startsWith(cleaned) && cleaned.length < lastPartialText.length) return;
lastPartialText = cleaned;
draftStream.update(cleaned);
return;
}
let delta = cleaned;
if (cleaned.startsWith(lastPartialText)) delta = cleaned.slice(lastPartialText.length);
else {
draftChunker?.reset();
draftText = "";
}
lastPartialText = cleaned;
if (!delta) return;
if (!draftChunker) {
draftText = cleaned;
draftStream.update(draftText);
return;
}
draftChunker.append(delta);
draftChunker.drain({
force: false,
emit: (chunk) => {
draftText += chunk;
draftStream.update(draftText);
}
});
},
handleAssistantMessageBoundary() {
if (discordStreamMode === "progress") return;
forceNewMessageIfNeeded();
},
async flush() {
if (!draftStream) return;
if (draftChunker?.hasBuffered()) {
draftChunker.drain({
force: true,
emit: (chunk) => {
draftText += chunk;
}
});
draftChunker.reset();
if (draftText) draftStream.update(draftText);
}
await draftStream.flush();
},
async cleanup() {
try {
progressDraft.cancel();
if (!finalReplyDelivered) await draftStream?.discardPending();
if (!finalReplyDelivered && !finalizedViaPreviewMessage && draftStream?.messageId()) await draftStream.clear();
} catch (err) {
params.log(`discord: draft cleanup failed: ${String(err)}`);
}
}
};
}
function isEmptyDiscordProgressLine(line) {
if (!line || typeof line === "string") return false;
return line.toolName === "apply_patch" && !line.detail && !line.status;
}
function shouldStartDiscordProgressDraftNow(line) {
return typeof line === "object" && line?.kind === "patch" && Boolean(line.detail);
}
//#endregion
//#region extensions/discord/src/monitor/message-handler.process.ts
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
let replyRuntimePromise;
async function loadReplyRuntime() {
replyRuntimePromise ??= import("./plugin-sdk/reply-runtime.js");
return await replyRuntimePromise;
}
function isProcessAborted(abortSignal) {
return Boolean(abortSignal?.aborted);
}
function formatDiscordReplyDeliveryFailure(params) {
const context = [`target=${params.target}`, params.sessionKey ? `session=${params.sessionKey}` : void 0].filter(Boolean).join(" ");
return `discord ${params.kind} reply failed (${context}): ${String(params.err)}`;
}
function isFallbackOnlyToolWarningFinal(payload) {
if (payload.isError !== true || !isReplyPayloadNonTerminalToolErrorWarning(payload)) return false;
return !resolveSendableOutboundReplyParts(payload).hasMedia;
}
function formatDiscordReplySkip(params) {
const context = [`target=${params.target}`, params.sessionKey ? `session=${params.sessionKey}` : void 0].filter(Boolean).join(" ");
return `discord ${params.kind} reply skipped (${params.reason}): ${context}`;
}
function readToolStringArg(args, key) {
const value = args[key];
return typeof value === "string" && value.trim() ? value.trim() : void 0;
}
function readToolBooleanArg(args, key) {
return args[key] === true;
}
async function processDiscordMessage(ctx, observer) {
try {
await processDiscordMessageInner(ctx, observer);
} finally {
ctx.replyTypingFeedback?.onCleanup?.();
}
}
async function processDiscordMessageInner(ctx, observer) {
const dispatchStartedAt = Date.now();
const { cfg, discordConfig, accountId, token, runtime, guildHistories, historyLimit, mediaMaxBytes, textLimit, replyToMode, ackReactionScope, message, messageChannelId, isGuildMessage, isDirectMessage, isGroupDm, messageText, shouldRequireMention, canDetectMention, effectiveWasMentioned, shouldBypassMention, channelConfig, threadBindings, route, discordRestFetch, abortSignal, botLoopProtection, replyTypingFeedback } = ctx;
if (isProcessAborted(abortSignal)) return;
if (botLoopProtection) {
const botLoopResult = recordChannelBotPairLoopAndCheckSuppression(botLoopProtection);
if (botLoopResult.suppressed) {
logVerbose(`discord: bot-to-bot loop detected before dispatch setup, suppressing for ${Math.max(0, Math.ceil((botLoopResult.cooldownUntilMs - Date.now()) / 1e3))}s`);
return;
}
}
const mediaResolveOptions = {
fetchImpl: discordRestFetch,
ssrfPolicy: cfg.browser?.ssrfPolicy,
readIdleTimeoutMs: DISCORD_ATTACHMENT_IDLE_TIMEOUT_MS,
totalTimeoutMs: DISCORD_ATTACHMENT_TOTAL_TIMEOUT_MS,
abortSignal
};
const mediaList = await resolveMediaList(message, mediaMaxBytes, mediaResolveOptions);
if (isProcessAborted(abortSignal)) return;
const forwardedMediaList = await resolveForwardedMediaList(message, mediaMaxBytes, mediaResolveOptions);
if (isProcessAborted(abortSignal)) return;
mediaList.push(...forwardedMediaList);
const text = messageText;
if (!text) {
logVerbose("discord: drop message " + message.id + " (empty content)");
return;
}
const boundThreadId = ctx.threadBinding?.conversation?.conversationId?.trim();
if (boundThreadId && typeof threadBindings.touchThread === "function") threadBindings.touchThread({ threadId: boundThreadId });
const { dispatchReplyWithBufferedBlockDispatcher } = await loadReplyRuntime();
const sourceReplyDeliveryMode = resolveChannelSourceReplyDeliveryMode({
cfg,
ctx: {
ChatType: isDirectMessage ? "direct" : isGroupDm ? "group" : isGuildMessage ? "channel" : void 0,
InboundEventKind: ctx.inboundEventKind
}
});
const sourceRepliesAreToolOnly = sourceReplyDeliveryMode === "message_tool_only";
const ackReaction = resolveAckReaction(cfg, route.agentId, {
channel: "discord",
accountId
});
const removeAckAfterReply = cfg.messages?.removeAckAfterReply ?? false;
const mediaLocalRoots = getAgentScopedMediaLocalRoots(cfg, route.agentId);
const isRoomEvent = ctx.inboundEventKind === "room_event";
const shouldAckReaction$1 = () => Boolean(!isRoomEvent && ackReaction && shouldAckReaction({
scope: ackReactionScope,
isDirect: isDirectMessage,
isGroup: isGuildMessage || isGroupDm,
isMentionableGroup: isGuildMessage,
requireMention: shouldRequireMention,
canDetectMention,
effectiveWasMentioned,
shouldBypassMention
}));
const shouldSendAckReaction = shouldAckReaction$1();
const statusReactionsExplicitlyEnabled = cfg.messages?.statusReactions?.enabled === true;
const statusReactionsEnabled = !isRoomEvent && shouldSendAckReaction && cfg.messages?.statusReactions?.enabled !== false && (!sourceRepliesAreToolOnly || statusReactionsExplicitlyEnabled);
const feedbackRest = createDiscordRestClient({
cfg,
token,
accountId
}).rest;
const deliveryRest = createDiscordRestClient({
cfg,
token,
accountId
}).rest;
const ackReactionContext = createDiscordAckReactionContext({
rest: feedbackRest,
cfg,
accountId
});
const discordAdapter = createDiscordAckReactionAdapter({
channelId: messageChannelId,
messageId: message.id,
reactionContext: ackReactionContext
});
let statusReactionTarget = `${messageChannelId}/${message.id}`;
let statusReactionsActive = statusReactionsEnabled;
let statusReactions = createStatusReactionController({
enabled: statusReactionsEnabled,
adapter: discordAdapter,
initialEmoji: ackReaction,
emojis: cfg.messages?.statusReactions?.emojis,
timing: cfg.messages?.statusReactions?.timing,
onError: (err) => {
logAckFailure({
log: logVerbose,
channel: "discord",
target: statusReactionTarget,
error: err
});
}
});
const resolveTrackedReactionChannelId = async (args) => {
const target = readToolStringArg(args, "channelId") ?? readToolStringArg(args, "channel_id") ?? readToolStringArg(args, "to");
if (!target) return messageChannelId;
try {
return resolveDiscordChannelId(target);
} catch {
return (await resolveDiscordTargetChannelId(target, {
cfg,
token,
accountId
})).channelId;
}
};
const maybeBindStatusReactionsToToolReaction = async (payload) => {
if (sourceRepliesAreToolOnly || cfg.messages?.statusReactions?.enabled === false || payload.phase !== "start" || payload.name !== "message" || !payload.args) return;
const args = payload.args;
if (readToolStringArg(args, "action")?.toLowerCase() !== "react") return;
if (!(readToolBooleanArg(args, "trackToolCalls") || readToolBooleanArg(args, "track_tool_calls"))) return;
const emoji = readToolStringArg(args, "emoji");
const remove = readToolBooleanArg(args, "remove");
if (!emoji || remove) return;
const trackedMessageId = readToolStringArg(args, "messageId") ?? readToolStringArg(args, "message_id") ?? message.id;
let trackedChannelId;
try {
trackedChannelId = await resolveTrackedReactionChannelId(args);
} catch (err) {
logAckFailure({
log: logVerbose,
channel: "discord",
target: `${readToolStringArg(args, "to") ?? readToolStringArg(args, "channelId") ?? messageChannelId}/${trackedMessageId}`,
error: err
});
return;
}
statusReactionTarget = `${trackedChannelId}/${trackedMessageId}`;
if (statusReactionsActive) statusReactions.clear();
statusReactions = createStatusReactionController({
enabled: true,
adapter: createDiscordAckReactionAdapter({
channelId: trackedChannelId,
messageId: trackedMessageId,
reactionContext: ackReactionContext
}),
initialEmoji: emoji,
emojis: cfg.messages?.statusReactions?.emojis,
timing: cfg.messages?.statusReactions?.timing,
onError: (err) => {
logAckFailure({
log: logVerbose,
channel: "discord",
target: statusReactionTarget,
error: err
});
}
});
statusReactionsActive = true;
statusReactions.setQueued();
};
queueInitialDiscordAckReaction({
enabled: statusReactionsEnabled,
shouldSendAckReaction,
ackReaction,
statusReactions,
reactionAdapter: discordAdapter,
target: `${messageChannelId}/${message.id}`
});
const processContext = await buildDiscordMessageProcessContext({
ctx,
text,
mediaList
});
if (!processContext) return;
const { ctxPayload, persistedSessionKey, turn, replyPlan, deliverTarget, replyTarget, replyReference } = processContext;
observer?.onReplyPlanResolved?.({
createdThreadId: replyPlan.createdThreadId,
sessionKey: persistedSessionKey
});
const typingChannelId = deliverTarget.startsWith("channel:") ? deliverTarget.slice(8) : messageChannelId;
const typingFeedback = replyTypingFeedback ?? createDiscordReplyTypingFeedback({
cfg,
token,
accountId,
channelId: typingChannelId,
rest: feedbackRest,
log: logVerbose
});
if (replyTypingFeedback) replyTypingFeedback.restartForDispatch(typingChannelId);
else typingFeedback.updateChannelId(typingChannelId);
const { onModelSelected, ...replyPipeline } = createChannelReplyPipeline({
cfg,
agentId: route.agentId,
channel: "discord",
accountId: route.accountId,
typingCallbacks: typingFeedback
});
const tableMode = resolveMarkdownTableMode({
cfg,
channel: "discord",
accountId
});
const maxLinesPerMessage = resolveDiscordMaxLinesPerMessage({
cfg,
discordConfig,
accountId
});
const chunkMode = resolveChunkMode(cfg, "discord", accountId);
const clearGroupHistory = () => {
if (isDirectMessage) return;
createChannelHistoryWindow({ historyMap: guildHistories }).clear({
historyKey: messageChannelId,
limit: historyLimit
});
};
const beginDeliveryCorrelation = () => isRoomEvent ? beginDiscordInboundEventDeliveryCorrelation(ctxPayload.SessionKey, {
outboundTo: messageChannelId,
outboundAccountId: route.accountId,
markInboundEventDelivered: clearGroupHistory
}, { inboundEventKind: ctxPayload.InboundEventKind }) : () => {};
const endDiscordInboundEventDeliveryCorrelation = beginDeliveryCorrelation();
const resolveCurrentTurnTranscriptFinalText = async () => {
const sessionKey = ctxPayload.SessionKey;
if (!sessionKey) return;
try {
const storePath = resolveStorePath(cfg.session?.store, { agentId: route.agentId });
const store = loadSessionStore(storePath, { clone: false });
const sessionEntry = resolveSessionStoreEntry({
store,
sessionKey
}).existing;
if (!sessionEntry?.sessionId) return;
const { sessionFile } = await resolveAndPersistSessionFile({
sessionId: sessionEntry.sessionId,
sessionKey,
sessionStore: store,
storePath,
sessionEntry,
agentId: route.agentId,
sessionsDir: path.dirname(storePath)
});
const latest = await readLatestAssistantTextFromSessionTranscript(sessionFile);
if (!latest?.timestamp || latest.timestamp < dispatchStartedAt) return;
return latest.text;
} catch (err) {
logVerbose(`discord transcript final candidate lookup failed: ${String(err)}`);
return;
}
};
const deliverChannelId = deliverTarget.startsWith("channel:") ? deliverTarget.slice(8) : messageChannelId;
const draftPreview = createDiscordDraftPreviewController({
cfg,
discordConfig,
accountId,
sourceRepliesAreToolOnly,
textLimit,
deliveryRest,
deliverChannelId,
replyReference,
tableMode,
maxLinesPerMessage,
chunkMode,
log: logVerbose
});
let verboseProgressActive = () => false;
const finalPreviewFlags = discordConfig?.suppressEmbeds ?? true ? MessageFlags.SuppressEmbeds : void 0;
let finalReplyStartNotified = false;
const notifyFinalReplyStart = () => {
if (finalReplyStartNotified) return;
finalReplyStartNotified = true;
draftPreview.markFinalReplyStarted();
observer?.onFinalReplyStart?.();
};
let userFacingFinalDelivered = false;
let userFacingFinalDeliveryFailed = false;
let pendingToolWarningFinal;
const markUserFacingFinalDelivered = () => {
userFacingFinalDelivered = true;
userFacingFinalDeliveryFailed = false;
pendingToolWarningFinal = void 0;
draftPreview.markFinalReplyDelivered();
observer?.onFinalReplyDelivered?.();
};
const beforeDiscordPayloadDelivery = (payload, info) => {
if (isProcessAborted(abortSignal)) {
logVerbose(formatDiscordReplySkip({
kind: info.kind,
reason: "aborted before delivery",
target: deliverTarget,
sessionKey: ctxPayload.SessionKey
}));
return null;
}
if (payload.isReasoning) {
logVerbose(formatDiscordReplySkip({
kind: info.kind,
reason: "reasoning payload",
target: deliverTarget,
sessionKey: ctxPayload.SessionKey
}));
return null;
}
if (draftPreview.draftStream && draftPreview.isProgressMode && info.kind === "block") {
if (!resolveSendableOutboundReplyParts(payload).hasMedia && !payload.isError) return null;
}
if (info.kind === "final" && !isFallbackOnlyToolWarningFinal(payload)) draftPreview.markFinalReplyStarted();
return payload;
};
const deliverDiscordPayload = async (payload, info, options) => {
if (isProcessAborted(abortSignal)) {
logVerbose(formatDiscordReplySkip({
kind: info.kind,
reason: "aborted before delivery",
target: deliverTarget,
sessionKey: ctxPayload.SessionKey
}));
return { visibleReplySent: false };
}
const isFinal = info.kind === "final";
if (payload.isReasoning) {
logVerbose(formatDiscordReplySkip({
kind: info.kind,
reason: "reasoning payload",
target: deliverTarget,
sessionKey: ctxPayload.SessionKey
}));
return { visibleReplySent: false };
}
if (isFinal && !options?.allowFallbackOnlyToolWarning && isFallbackOnlyToolWarningFinal(payload)) {
if (!userFacingFinalDelivered && (!finalReplyStartNotified || userFacingFinalDeliveryFailed)) pendingToolWarningFinal = {
payload,
info
};
return { visibleReplySent: false };
}
if (isFinal) draftPreview.markFinalReplyStarted();
const finalText = isFinal && typeof payload.text === "string" ? await resolveTranscriptBackedChannelFinalText({
finalText: payload.text,
resolveCandidateText: resolveCurrentTurnTranscriptFinalText
}) : payload.text;
const [deliverablePayload] = sanitizeDiscordFrontChannelReplyPayloads([finalText !== payload.text ? {
...payload,
text: finalText
} : payload], { kind: info.kind });
if (!deliverablePayload) {
logVerbose(formatDiscordReplySkip({
kind: info.kind,
reason: "internal-only payload",
target: deliverTarget,
sessionKey: ctxPayload.SessionKey
}));
return { visibleReplySent: false };
}
const draftStream = draftPreview.draftStream;
if (draftStream && draftPreview.isProgressMode && info.kind === "block") {
if (!resolveSendableOutboundReplyParts(deliverablePayload).hasMedia && !deliverablePayload.isError) return { visibleReplySent: false };
}
if (draftStream && isFinal && (!draftPreview.isProgressMode || draftPreview.hasProgressDraftStarted) && !deliverablePayload.isError) {
const hasMedia = resolveSendableOutboundReplyParts(deliverablePayload).hasMedia;
const ttsSupplement = getReplyPayloadTtsSupplement(deliverablePayload);
const previewSourceText = deliverablePayload.text ?? ttsSupplement?.spokenText;
const previewFinalText = draftPreview.resolvePreviewFinalText(previewSourceText);
const previewReplyToId = replyReference.peek();
const hasExplicitReplyDirective = Boolean(deliverablePayload.replyToTag || deliverablePayload.replyToCurrent) || typeof previewSourceText === "string" && /\[\[\s*reply_to(?:_current|\s*:)/i.test(previewSourceText);
if ((await deliverWithFinalizableLivePreviewAdapter({
kind: info.kind,
payload: deliverablePayload,
adapter: defineFinalizableLivePreviewAdapter({
draft: {
flush: () => draftPreview.flush(),
clear: () => draftStream.clear(),
discardPending: () => draftStream.discardPending(),
seal: () => draftStream.seal(),
id: draftStream.messageId
},
buildFinalEdit: () => {
if (draftPreview.finalizedViaPreviewMessage || hasMedia && !ttsSupplement || typeof previewFinalText !== "string" || hasExplicitReplyDirective || deliverablePayload.isError) return;
const rewrittenFinal = rewriteDiscordKnownMentions(previewFinalText, {
accountId,
mentionAliases: resolveDiscordAccount({
cfg,
accountId
}).config.mentionAliases
});
if (discordTextHasTargetedMention(rewrittenFinal) && !discordTextHasBroadcastMention(rewrittenFinal)) return;
return {
content: previewFinalText,
...finalPreviewFlags ? { flags: finalPreviewFlags } : {}
};
},
editFinal: async (previewMessageId, edit) => {
if (isProcessAborted(abortSignal)) throw new Error("process aborted");
notifyFinalReplyStart();
await editMessageDiscord(deliverChannelId, previewMessageId, edit, {
cfg,
accountId,
rest: deliveryRest
});
},
onPreviewFinalized: () => {
markUserFacingFinalDelivered();
draftPreview.markPreviewFinalized();
replyReference.markSent();
},
buildSupplementalPayload: () => ttsSupplement ? buildTtsSupplementMediaPayload(deliverablePayload) : void 0,
deliverSupplemental: async (supplementalPayload) => {
if (isProcessAborted(abortSignal)) return false;
const supplementalReplyToId = previewReplyToId ?? replyReference.peek() ?? (replyToMode === "all" ? typeof message.id === "string" && message.id ? message.id : ctxPayload.MessageSid : void 0);
await deliverDiscordReply({
cfg,
replies: [supplementalPayload],
target: deliverTarget,
token,
accountId,
rest: deliveryRest,
runtime,
replyToId: supplementalReplyToId,
replyToMode,
textLimit,
maxLinesPerMessage,
tableMode,
chunkMode,
sessionKey: ctxPayload.SessionKey,
threadBindings,
mediaLocalRoots,
kind: info.kind
});
return true;
},
logPreviewEditFailure: (err) => {
logVerbose(`discord: preview final edit failed; falling back to standard send (${String(err)})`);
}
}),
deliverNormally: async () => {
if (isProcessAborted(abortSignal)) return false;
const fallbackPayload = ttsSupplement && ttsSupplement.visibleTextAlreadyDelivered !== true && !deliverablePayload.text?.trim() ? {
...deliverablePayload,
text: ttsSupplement.spokenText
} : deliverablePayload;
const replyToId = replyReference.use();
notifyFinalReplyStart();
await deliverDiscordReply({
cfg,
replies: [fallbackPayload],
target: deliverTarget,
token,
accountId,
rest: deliveryRest,
runtime,
replyToId,
replyToMode,
textLimit,
maxLinesPerMessage,
tableMode,
chunkMode,
sessionKey: ctxPayload.SessionKey,
threadBindings,
mediaLocalRoots,
kind: info.kind
});
return true;
},
onNormalDelivered: () => {
markUserFacingFinalDelivered();
replyReference.markSent();
}
})).kind !== "normal-skipped") return { visibleReplySent: true };
}
if (isProcessAborted(abortSignal)) {
logVerbose(formatDiscordReplySkip({
kind: info.kind,
reason: "aborted before delivery",
target: deliverTarget,
sessionKey: ctxPayload.SessionKey
}));
return { visibleReplySent: false };
}
const replyToId = replyReference.use();
if (isFinal) notifyFinalReplyStart();
await deliverDiscordReply({
cfg,
replies: [deliverablePayload],
target: deliverTarget,
token,
accountId,
rest: deliveryRest,
runtime,
replyToId,
replyToMode,
textLimit,
maxLinesPerMessage,
tableMode,
chunkMode,
sessionKey: ctxPayload.SessionKey,
threadBindings,
mediaLocalRoots,
kind: info.kind
});
replyReference.markSent();
if (isFinal && deliverablePayload.isError !== true) markUserFacingFinalDelivered();
return { visibleReplySent: true };
};
const onDiscordDeliveryError = (err, info) => {
if (info.kind === "final" && finalReplyStartNotified && !userFacingFinalDelivered) userFacingFinalDeliveryFailed = true;
runtime.error(danger(formatDiscordReplyDeliveryFailure({
kind: info.kind,
err,
target: deliverTarget,
sessionKey: ctxPayload.SessionKey
})));
};
const onDiscordReplyStart = async () => {
if (isProcessAborted(abortSignal)) return;
await replyPipeline.typingCallbacks?.onReplyStart();
await statusReactions.setThinking();
};
const resolvedBlockStreamingEnabled = resolveChannelStreamingBlockEnabled(discordConfig);
let dispatchResult = null;
let dispatchError = false;
let dispatchAborted = false;
const deliverPendingToolWarningFinalIfNeeded = async () => {
if (!pendingToolWarningFinal || userFacingFinalDelivered || isProcessAborted(abortSignal)) return;
const pending = pendingToolWarningFinal;
pendingToolWarningFinal = void 0;
try {
return await deliverDiscordPayload(pending.payload, pending.info, { allowFallbackOnlyToolWarning: true });
} catch (err) {
dispatchError = true;
onDiscordDeliveryError(err, pending.info);
return { visibleReplySent: false };
}
};
try {
if (isProcessAborted(abortSignal)) {
dispatchAborted = true;
return;
}
const preparedResult = await dispatchChannelInboundReply({
cfg,
channel: "discord",
accountId: route.accountId,
agentId: route.agentId,
routeSessionKey: persistedSessionKey,
storePath: turn.storePath,
ctxPayload,
recordInboundSession,
dispatchReplyWithBufferedBlockDispatcher,
dispatcherOptions: {
...replyPipeline,
humanDelay: resolveHumanDelayConfig(cfg, route.agentId),
beforeDeliver: beforeDiscordPayloadDelivery,
onReplyStart: onDiscordReplyStart,
onFreshSettledDelivery: deliverPendingToolWarningFinalIfNeeded
},
delivery: {
deliver: deliverDiscordPayload,
onError: onDiscordDeliveryError
},
record: turn.record,
history: isRoomEvent ? void 0 : {
isGroup: isGuildMessage,
historyKey: messageChannelId,
historyMap: guildHistories,
limit: historyLimit
},
replyOptions: {
abortSignal,
skillFilter: channelConfig?.skills,
sourceReplyDeliveryMode,
queuedDeliveryCorrelations: isRoomEvent ? [{ begin: beginDeliveryCorrelation }] : void 0,
suppressTyping: isRoomEvent ? true : void 0,
allowProgressCallbacksWhenSourceDeliverySuppressed: sourceRepliesAreToolOnly && draftPreview.draftStream && draftPreview.isProgressMode ? true : void 0,
disableBlockStreaming: sourceRepliesAreToolOnly ? true : draftPreview.disableBlockStreamingForDraft ?? (typeof resolvedBlockStreamingEnabled === "boolean" ? !resolvedBlockStreamingEnabled : void 0),
onPartialReply: draftPreview.draftStream && !draftPreview.isProgressMode ? (payload) => draftPreview.updateFromPartial(payload.text) : void 0,
onAssistantMessageStart: draftPreview.draftStream ? () => draftPreview.handleAssistantMessageBoundary() : void 0,
onReasoningEnd: draftPreview.draftStream ? () => draftPreview.handleAssistantMessageBoundary() : void 0,
onModelSelected,
suppressDefaultToolProgressMessages: draftPreview.suppressDefaultToolProgressMessages ? true : void 0,
commentaryProgressEnabled: draftPreview.isProgressMode ? draftPreview.commentaryProgressEnabled : void 0,
onVerboseProgressVisibility: (isActive) => {
verboseProgressActive = isActive;
},
onReasoningStream: async (payload) => {
await statusReactions.setThinking();
await draftPreview.pushReasoningProgress(payload?.text, { snapshot: payload?.isReasoningSnapshot === true });
},
onToolStart: async (payload) => {
if (isProcessAborted(abortSignal)) return;
await maybeBindStatusReactionsToToolReaction(payload);
await statusReactions.setTool(payload.name);
await draftPreview.pushToolProgress(buildChannelProgressDraftLineForEntry(discordConfig, {
event: "tool",
name: payload.name,
phase: payload.phase,
args: payload.args
}, payload.detailMode ? { detailMode: payload.detailMode } : void 0), { toolName: payload.name });
},
onItemEvent: async (payload) => {
if (payload.kind === "preamble") {
if (verboseProgressActive()) return;
if (draftPreview.commentaryProgressEnabled && payload.progressText) await draftPreview.pushCommentaryProgress(payload.progressText, { itemId: payload.itemId });
return;
}
await draftPreview.pushToolProgress(buildChannelProgressDraftLineForEntry(discordConfig, {
event: "item",
itemId: payload.itemId,
itemKind: payload.kind,
title: payload.title,
name: payload.name,
phase: payload.phase,
status: payload.status,
summary: payload.summary,
progressText: payload.progressText,
meta: payload.meta
}));
},
onPlanUpdate: async (payload) => {
if (payload.phase !== "update") return;
await draftPreview.pushToolProgress(buildChannelProgressDraftLine({
event: "plan",
phase: payload.phase,
title: payload.title,
explanation: payload.explanation,
steps: payload.steps
}));
},
onApprovalEvent: async (payload) => {
if (payload.phase !== "requested") return;
await draftPreview.pushToolProgress(buildChannelProgressDraftLine({
event: "approval",
phase: payload.phase,
title: payload.title,
command: payload.command,
reason: payload.reason,
message: payload.message
}));
},
onCommandOutput: async (payload) => {
if (payload.phase !== "end") return;
await draftPreview.pushToolProgress(buildChannelProgressDraftLine({
event: "command-output",
phase: payload.phase,
title: payload.title,
name: payload.name,
status: payload.status,
exitCode: payload.exitCode
}));
},
onPatchSummary: async (payload) => {
if (payload.phase !== "end") return;
await draftPreview.pushToolProgress(buildChannelProgressDraftLine({
event: "patch",
phase: payload.phase,
title: payload.title,
name: payload.name,
added: payload.added,
modified: payload.modified,
deleted: payload.deleted,
summary: payload.summary
}));
},
onCompactionStart: async () => {
if (isProcessAborted(abortSignal)) return;
await statusReactions.setCompacting();
},
onCompactionEnd: async () => {
if (isProcessAborted(abortSignal)) return;
statusReactions.cancelPending();
await statusReactions.setThinking();
}
}
});
if (!preparedResult.dispatched) return;
dispatchResult = preparedResult.dispatchResult;
if (isProcessAborted(abortSignal)) {
dispatchAborted = true;
return;
}
} catch (err) {
if (isProcessAborted(abortSignal)) {
dispatchAborted = true;
return;
}
dispatchError = true;
throw err;
} finally {
endDiscordInboundEventDeliveryCorrelation();
await draftPreview.cleanup();
const finalDeliveryFailed = (dispatchResult?.failedCounts?.final ?? 0) > 0;
if (statusReactionsActive) if (dispatchAborted) if (removeAckAfterReply) statusReactions.clear();
else statusReactions.restoreInitial();
else {
if (dispatchError || finalDeliveryFailed) await statusReactions.setError();
else await statusReactions.setDone();
if (removeAckAfterReply) (async () => {
await sleep(dispatchError || finalDeliveryFailed ? DEFAULT_TIMING.errorHoldMs : DEFAULT_TIMING.doneHoldMs);
await statusReactions.clear();
})();
else statusReactions.restoreInitial();
}
else if (shouldSendAckReaction && ackReaction && removeAckAfterReply) removeReactionDiscord(messageChannelId, message.id, ackReaction, ackReactionContext).catch((err) => {
logAckFailure({
log: logVerbose,
channel: "discord",
target: `${messageChannelId}/${message.id}`,
error: err
});
});
}
if (dispatchAborted) return;
const finalDispatchResult = dispatchResult;
if (!finalDispatchResult || !hasFinalChannelTurnDispatch(finalDispatchResult)) return;
if (shouldLogVerbose()) {
const finalCount = finalDispatchResult.counts.final;
logVerbose(`discord: delivered ${finalCount} reply${finalCount === 1 ? "" : "ies"} to ${replyTarget}`);
}
}
//#endregion
export { formatDiscordReplySkip, processDiscordMessage };