@kodelyth/discord 2026.5.39 → 2026.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/account-inspect-Dqw-enky.js +81 -0
- package/dist/account-inspect-api.js +10 -0
- package/dist/accounts-B7OBFePq.js +224 -0
- package/dist/action-runtime-api.js +2 -0
- package/dist/agent-components.runtime-DVY_1VB4.js +4 -0
- package/dist/allow-list-B0s7evD7.js +354 -0
- package/dist/api-CXAcv9nZ.js +130 -0
- package/dist/api.js +23 -0
- package/dist/approval-handler.runtime-B9xUAF3n.js +426 -0
- package/dist/audit-DoiK49WO.js +24 -0
- package/dist/audit-core-BGrq3G7r.js +105 -0
- package/dist/channel-U_aeoFwW.js +795 -0
- package/dist/channel-actions-BxEBnEuv.js +173 -0
- package/dist/channel-actions.runtime-CPtpH-yl.js +263 -0
- package/dist/channel-api-BfjklLby.js +21 -0
- package/dist/channel-config-api.js +2 -0
- package/dist/channel-plugin-api.js +2 -0
- package/dist/channel.setup-BUSC0apv.js +337 -0
- package/dist/components-luonoe13.js +909 -0
- package/dist/config-api-DSYGqaLQ.js +2 -0
- package/dist/config-schema-DIqJBGwC.js +357 -0
- package/dist/configured-state.js +6 -0
- package/dist/contract-api.js +8 -0
- package/dist/conversation-identity-DXAm0_Mk.js +270 -0
- package/dist/directory-config-CYbuMmPS.js +49 -0
- package/dist/directory-contract-api.js +2 -0
- package/dist/directory-live-DX4dLRpJ.js +159 -0
- package/dist/doctor-bbKSvGVD.js +244 -0
- package/dist/doctor-contract-Btjt6NJD.js +383 -0
- package/dist/doctor-contract-api.js +2 -0
- package/dist/gateway-registry-BKSpa4GB.js +74 -0
- package/dist/handle-action.guild-admin-B5BArS2n.js +286 -0
- package/dist/inbound-context-WAOqhGlT.js +48 -0
- package/dist/inbound-event-delivery-C-1Ji3WP.js +65 -0
- package/dist/index.js +26 -0
- package/dist/manager.runtime-DXHynKE4.js +2356 -0
- package/dist/message-handler-mXzc3tA_.js +381 -0
- package/dist/message-handler.preflight-BPD1a347.js +1113 -0
- package/dist/message-handler.process-GUa3aV8z.js +1438 -0
- package/dist/message-utils-dUbem16p.js +549 -0
- package/dist/outbound-adapter-C18OAc1y.js +536 -0
- package/dist/pluralkit-D1Q2x0w5.js +22 -0
- package/dist/preflight-audio-CZtpWcIm.js +72 -0
- package/dist/preflight-audio.runtime-Brx_0_xW.js +7 -0
- package/dist/preview-streaming-D_slNIiO.js +8 -0
- package/dist/probe-D--Ca4JF.js +139 -0
- package/dist/probe.runtime-DQBchZzv.js +2 -0
- package/dist/provider-B2-31CIT.js +9565 -0
- package/dist/provider-session.runtime-BwzzSsrH.js +6 -0
- package/dist/provider.runtime-CP3oHLls.js +2 -0
- package/dist/resolve-allowlist-common-CqxPLcJO.js +34 -0
- package/dist/resolve-channels-0LX4pUbB.js +265 -0
- package/dist/resolve-users-CztOv0Qs.js +120 -0
- package/dist/runtime-DUaw66V_.js +1073 -0
- package/dist/runtime-api.actions.js +3 -0
- package/dist/runtime-api.js +30 -0
- package/dist/runtime-api.lookup.js +7 -0
- package/dist/runtime-api.monitor-CvVKvEXW.js +5 -0
- package/dist/runtime-api.monitor.js +8 -0
- package/dist/runtime-api.send.js +6 -0
- package/dist/runtime-api.threads.js +6 -0
- package/dist/runtime-fC6f4UF2.js +8 -0
- package/dist/runtime-setter-api.js +2 -0
- package/dist/secret-config-contract-B6WW5V88.js +115 -0
- package/dist/secret-contract-api.js +2 -0
- package/dist/security-audit-CnyIQKz6.js +120 -0
- package/dist/security-audit-contract-api.js +2 -0
- package/dist/security-audit.runtime-CQSkjNLu.js +2 -0
- package/dist/security-contract-DLvYOgLM.js +26 -0
- package/dist/security-contract-api.js +2 -0
- package/dist/security-doctor-DepqtNCI.js +18 -0
- package/dist/send-DCtPCHGk.js +881 -0
- package/dist/send.components-Bcgxvm52.js +474 -0
- package/dist/send.outbound-S9t0UuHc.js +330 -0
- package/dist/send.receipt-CDn3GBWC.js +3119 -0
- package/dist/send.shared-D4iBnAmn.js +669 -0
- package/dist/sender-identity-CxCe3_1a.js +43 -0
- package/dist/session-contract-Dwhw3RTY.js +6 -0
- package/dist/session-key-api.js +2 -0
- package/dist/session-key-normalization-CP8dPUid.js +23 -0
- package/dist/setup-entry.js +11 -0
- package/dist/setup-plugin-api.js +2 -0
- package/dist/shared-AIlvuZXt.js +171 -0
- package/dist/subagent-hooks-8bK-mgiU.js +120 -0
- package/dist/subagent-hooks-api.js +22 -0
- package/dist/system-events-Ba1TklaL.js +34 -0
- package/dist/target-resolver-BrtFQtoK.js +82 -0
- package/dist/targets-DWLLZE2l.js +3 -0
- package/dist/test-api.js +45 -0
- package/dist/thread-binding-api.js +4 -0
- package/dist/thread-bindings-9aKRmZv0.js +255 -0
- package/dist/thread-bindings.discord-api-ssGH5wc2.js +244 -0
- package/dist/thread-bindings.manager-0YBHGemk.js +534 -0
- package/dist/thread-bindings.session-updates-DJZGIwaU.js +54 -0
- package/dist/thread-bindings.state-eTFl-PqJ.js +318 -0
- package/dist/timeouts-CEwuGaWT.js +52 -0
- package/dist/timeouts.js +2 -0
- package/dist/typing-BmJKRpCS.js +14 -0
- package/package.json +19 -7
- package/account-inspect-api.js +0 -7
- package/action-runtime-api.js +0 -7
- package/api.js +0 -7
- package/channel-config-api.js +0 -7
- package/channel-plugin-api.js +0 -7
- package/configured-state.js +0 -7
- package/contract-api.js +0 -7
- package/directory-contract-api.js +0 -7
- package/doctor-contract-api.js +0 -7
- package/index.js +0 -7
- package/runtime-api.actions.js +0 -7
- package/runtime-api.js +0 -7
- package/runtime-api.lookup.js +0 -7
- package/runtime-api.monitor.js +0 -7
- package/runtime-api.send.js +0 -7
- package/runtime-api.threads.js +0 -7
- package/runtime-setter-api.js +0 -7
- package/secret-contract-api.js +0 -7
- package/security-audit-contract-api.js +0 -7
- package/security-contract-api.js +0 -7
- package/session-key-api.js +0 -7
- package/setup-entry.js +0 -7
- package/setup-plugin-api.js +0 -7
- package/subagent-hooks-api.js +0 -7
- package/test-api.js +0 -7
- package/thread-binding-api.js +0 -7
- package/timeouts.js +0 -7
|
@@ -0,0 +1,1438 @@
|
|
|
1
|
+
import { Ut as resolveDiscordChannelId, c as discord_exports, ft as editChannelMessage, s as chunkDiscordTextWithMode, st as createChannelMessage, ut as deleteChannelMessage } from "./send.receipt-CDn3GBWC.js";
|
|
2
|
+
import { f as resolveDiscordMaxLinesPerMessage } from "./accounts-B7OBFePq.js";
|
|
3
|
+
import { N as createDiscordRestClient, P as createDiscordRuntimeAccountContext, _ as resolveDiscordMessageFlags, d as resolveDiscordTargetChannelId } from "./send.shared-D4iBnAmn.js";
|
|
4
|
+
import { S as resolveTimestampMs, a as normalizeDiscordSlug, r as normalizeDiscordAllowList } from "./allow-list-B0s7evD7.js";
|
|
5
|
+
import { a as removeReactionDiscord, f as editMessageDiscord, r as reactMessageDiscord } from "./send-DCtPCHGk.js";
|
|
6
|
+
import "./targets-DWLLZE2l.js";
|
|
7
|
+
import { t as resolveDiscordConversationIdentity } from "./conversation-identity-DXAm0_Mk.js";
|
|
8
|
+
import { t as beginDiscordInboundEventDeliveryCorrelation } from "./inbound-event-delivery-C-1Ji3WP.js";
|
|
9
|
+
import { t as DISCORD_TEXT_CHUNK_LIMIT } from "./outbound-adapter-C18OAc1y.js";
|
|
10
|
+
import { t as resolveDiscordPreviewStreamMode } from "./preview-streaming-D_slNIiO.js";
|
|
11
|
+
import { n as DISCORD_ATTACHMENT_TOTAL_TIMEOUT_MS, t as DISCORD_ATTACHMENT_IDLE_TIMEOUT_MS } from "./timeouts-CEwuGaWT.js";
|
|
12
|
+
import { a as resolveReplyContext, i as buildGuildLabel, n as deliverDiscordReply, r as buildDirectLabel, x as resolveDiscordThreadStarter, y as resolveDiscordAutoThreadReplyPlan } from "./provider-B2-31CIT.js";
|
|
13
|
+
import { a as resolveForwardedMediaList, o as resolveMediaList, r as resolveDiscordMessageText, s as resolveReferencedReplyMediaList } from "./message-utils-dUbem16p.js";
|
|
14
|
+
import { t as sendTyping } from "./typing-BmJKRpCS.js";
|
|
15
|
+
import { n as buildDiscordInboundAccessContext, r as createDiscordSupplementalContextAccessChecker } from "./inbound-context-WAOqhGlT.js";
|
|
16
|
+
import { buildAgentSessionKey, normalizeAccountId, resolveAccountEntry, resolveThreadSessionKeys } from "klaw/plugin-sdk/routing";
|
|
17
|
+
import { MessageFlags } from "discord-api-types/v10";
|
|
18
|
+
import path from "node:path";
|
|
19
|
+
import { evaluateSupplementalContextVisibility } from "klaw/plugin-sdk/security-runtime";
|
|
20
|
+
import { getAgentScopedMediaLocalRoots } from "klaw/plugin-sdk/media-runtime";
|
|
21
|
+
import { buildTtsSupplementMediaPayload, getReplyPayloadTtsSupplement, resolveSendableOutboundReplyParts } from "klaw/plugin-sdk/reply-payload";
|
|
22
|
+
import { resolveChunkMode, resolveTextChunkLimit } from "klaw/plugin-sdk/reply-chunking";
|
|
23
|
+
import { danger, logVerbose, shouldLogVerbose } from "klaw/plugin-sdk/runtime-env";
|
|
24
|
+
import { formatErrorMessage } from "klaw/plugin-sdk/error-runtime";
|
|
25
|
+
import { resolveMarkdownTableMode } from "klaw/plugin-sdk/markdown-table-runtime";
|
|
26
|
+
import { convertMarkdownTables, stripInlineDirectiveTagsForDelivery, stripReasoningTagsFromText } from "klaw/plugin-sdk/text-chunking";
|
|
27
|
+
import { createChannelMessageReplyPipeline, defineFinalizableLivePreviewAdapter, deliverWithFinalizableLivePreviewAdapter, resolveChannelMessageSourceReplyDeliveryMode } from "klaw/plugin-sdk/channel-message";
|
|
28
|
+
import { buildChannelProgressDraftLine, buildChannelProgressDraftLineForEntry, createChannelProgressDraftGate, formatChannelProgressDraftText, isChannelProgressDraftWorkToolName, mergeChannelProgressDraftLine, normalizeChannelProgressDraftLineIdentity, resolveChannelProgressDraftMaxLines, resolveChannelStreamingBlockEnabled, resolveChannelStreamingPreviewChunk, resolveChannelStreamingPreviewToolProgress, resolveChannelStreamingSuppressDefaultToolProgressMessages, resolveTranscriptBackedChannelFinalText } from "klaw/plugin-sdk/channel-streaming";
|
|
29
|
+
import { recordInboundSession, resolvePinnedMainDmOwnerFromAllowlist } from "klaw/plugin-sdk/conversation-runtime";
|
|
30
|
+
import { EmbeddedBlockChunker, formatReasoningMessage, resolveAckReaction, resolveHumanDelayConfig } from "klaw/plugin-sdk/agent-runtime";
|
|
31
|
+
import { truncateUtf16Safe } from "klaw/plugin-sdk/text-utility-runtime";
|
|
32
|
+
import { isDangerousNameMatchingEnabled } from "klaw/plugin-sdk/dangerous-name-runtime";
|
|
33
|
+
import { loadSessionStore, readLatestAssistantTextFromSessionTranscript, readSessionUpdatedAt, resolveAndPersistSessionFile, resolveSessionStoreEntry, resolveStorePath } from "klaw/plugin-sdk/session-store-runtime";
|
|
34
|
+
import { buildChannelInboundEventContext, formatInboundEnvelope, resolveEnvelopeFormatOptions, toHistoryMediaEntries, toInboundMediaFacts } from "klaw/plugin-sdk/channel-inbound";
|
|
35
|
+
import { createFinalizableDraftLifecycle } from "klaw/plugin-sdk/channel-lifecycle";
|
|
36
|
+
import { hasFinalInboundReplyDispatch, recordChannelBotPairLoopAndCheckSuppression, runPreparedInboundReplyTurn } from "klaw/plugin-sdk/inbound-reply-dispatch";
|
|
37
|
+
import { DEFAULT_TIMING, createStatusReactionController, logAckFailure, logTypingFailure, shouldAckReaction } from "klaw/plugin-sdk/channel-feedback";
|
|
38
|
+
import { createChannelHistoryWindow } from "klaw/plugin-sdk/reply-history";
|
|
39
|
+
import { resolveChannelContextVisibilityMode } from "klaw/plugin-sdk/context-visibility-runtime";
|
|
40
|
+
//#region extensions/discord/src/monitor/ack-reactions.ts
|
|
41
|
+
function createDiscordAckReactionContext(params) {
|
|
42
|
+
return {
|
|
43
|
+
rest: params.rest,
|
|
44
|
+
...createDiscordRuntimeAccountContext({
|
|
45
|
+
cfg: params.cfg,
|
|
46
|
+
accountId: params.accountId
|
|
47
|
+
})
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function createDiscordAckReactionAdapter(params) {
|
|
51
|
+
return {
|
|
52
|
+
setReaction: async (emoji) => {
|
|
53
|
+
await reactMessageDiscord(params.channelId, params.messageId, emoji, params.reactionContext);
|
|
54
|
+
},
|
|
55
|
+
removeReaction: async (emoji) => {
|
|
56
|
+
await removeReactionDiscord(params.channelId, params.messageId, emoji, params.reactionContext);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function queueInitialDiscordAckReaction(params) {
|
|
61
|
+
if (params.enabled) {
|
|
62
|
+
params.statusReactions.setQueued();
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
if (!params.shouldSendAckReaction || !params.ackReaction) return;
|
|
66
|
+
params.reactionAdapter.setReaction(params.ackReaction).catch((err) => {
|
|
67
|
+
logAckFailure({
|
|
68
|
+
log: logVerbose,
|
|
69
|
+
channel: "discord",
|
|
70
|
+
target: params.target,
|
|
71
|
+
error: err
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region extensions/discord/src/monitor/message-handler.context.ts
|
|
77
|
+
function normalizeDiscordDmOwnerEntry(entry) {
|
|
78
|
+
const candidate = normalizeDiscordAllowList([entry], [
|
|
79
|
+
"discord:",
|
|
80
|
+
"user:",
|
|
81
|
+
"pk:"
|
|
82
|
+
])?.ids.values().next().value;
|
|
83
|
+
return typeof candidate === "string" && /^\d+$/.test(candidate) ? candidate : void 0;
|
|
84
|
+
}
|
|
85
|
+
function isContextAborted(abortSignal) {
|
|
86
|
+
return Boolean(abortSignal?.aborted);
|
|
87
|
+
}
|
|
88
|
+
async function buildDiscordMessageProcessContext(params) {
|
|
89
|
+
const { ctx, text, mediaList } = params;
|
|
90
|
+
const { cfg, discordConfig, accountId, runtime, 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;
|
|
91
|
+
const fromLabel = isDirectMessage ? buildDirectLabel(author) : buildGuildLabel({
|
|
92
|
+
guild: data.guild ?? void 0,
|
|
93
|
+
channelName: channelName ?? messageChannelId,
|
|
94
|
+
channelId: messageChannelId
|
|
95
|
+
});
|
|
96
|
+
const senderLabel = sender.label;
|
|
97
|
+
const isForumParent = threadParentType === discord_exports.ChannelType.GuildForum || threadParentType === discord_exports.ChannelType.GuildMedia;
|
|
98
|
+
const forumParentSlug = isForumParent && threadParentName ? normalizeDiscordSlug(threadParentName) : "";
|
|
99
|
+
const threadChannelId = threadChannel?.id;
|
|
100
|
+
const threadParentInheritanceEnabled = discordConfig?.thread?.inheritParent ?? false;
|
|
101
|
+
const forumContextLine = Boolean(threadChannelId && isForumParent && forumParentSlug) && message.id === threadChannelId ? `[Forum parent: #${forumParentSlug}]` : null;
|
|
102
|
+
const groupChannel = isGuildMessage && displayChannelSlug ? `#${displayChannelSlug}` : void 0;
|
|
103
|
+
const groupSubject = isDirectMessage ? void 0 : groupChannel;
|
|
104
|
+
const senderName = sender.isPluralKit ? sender.name ?? author.username : data.member?.nickname ?? author.globalName ?? author.username;
|
|
105
|
+
const senderUsername = sender.isPluralKit ? sender.tag ?? sender.name ?? author.username : author.username;
|
|
106
|
+
const { groupSystemPrompt, ownerAllowFrom, untrustedContext } = buildDiscordInboundAccessContext({
|
|
107
|
+
channelConfig,
|
|
108
|
+
guildInfo,
|
|
109
|
+
sender: {
|
|
110
|
+
id: sender.id,
|
|
111
|
+
name: sender.name,
|
|
112
|
+
tag: sender.tag
|
|
113
|
+
},
|
|
114
|
+
allowNameMatching: isDangerousNameMatchingEnabled(discordConfig),
|
|
115
|
+
isGuild: isGuildMessage,
|
|
116
|
+
channelTopic: channelInfo?.topic
|
|
117
|
+
});
|
|
118
|
+
const pinnedMainDmOwner = isDirectMessage ? resolvePinnedMainDmOwnerFromAllowlist({
|
|
119
|
+
dmScope: cfg.session?.dmScope,
|
|
120
|
+
allowFrom: channelConfig?.users ?? guildInfo?.users,
|
|
121
|
+
normalizeEntry: normalizeDiscordDmOwnerEntry
|
|
122
|
+
}) : null;
|
|
123
|
+
const contextVisibilityMode = resolveChannelContextVisibilityMode({
|
|
124
|
+
cfg,
|
|
125
|
+
channel: "discord",
|
|
126
|
+
accountId
|
|
127
|
+
});
|
|
128
|
+
const isSupplementalContextSenderAllowed = createDiscordSupplementalContextAccessChecker({
|
|
129
|
+
channelConfig,
|
|
130
|
+
guildInfo,
|
|
131
|
+
allowNameMatching: isDangerousNameMatchingEnabled(discordConfig),
|
|
132
|
+
isGuild: isGuildMessage
|
|
133
|
+
});
|
|
134
|
+
const storePath = resolveStorePath(cfg.session?.store, { agentId: route.agentId });
|
|
135
|
+
const envelopeOptions = resolveEnvelopeFormatOptions(cfg);
|
|
136
|
+
const previousTimestamp = readSessionUpdatedAt({
|
|
137
|
+
storePath,
|
|
138
|
+
sessionKey: route.sessionKey
|
|
139
|
+
});
|
|
140
|
+
const channelHistory = createChannelHistoryWindow({ historyMap: guildHistories });
|
|
141
|
+
const isRoomEvent = ctx.inboundEventKind === "room_event";
|
|
142
|
+
let combinedBody = formatInboundEnvelope({
|
|
143
|
+
channel: "Discord",
|
|
144
|
+
from: fromLabel,
|
|
145
|
+
timestamp: resolveTimestampMs(message.timestamp),
|
|
146
|
+
body: text,
|
|
147
|
+
chatType: isDirectMessage ? "direct" : "channel",
|
|
148
|
+
senderLabel,
|
|
149
|
+
previousTimestamp,
|
|
150
|
+
envelope: envelopeOptions
|
|
151
|
+
});
|
|
152
|
+
const shouldIncludeChannelHistory = !isDirectMessage && (isRoomEvent || !(isGuildMessage && channelConfig?.autoThread && !threadChannel));
|
|
153
|
+
if (shouldIncludeChannelHistory) combinedBody = channelHistory.buildPendingContext({
|
|
154
|
+
historyKey: messageChannelId,
|
|
155
|
+
limit: historyLimit,
|
|
156
|
+
currentMessage: combinedBody,
|
|
157
|
+
formatEntry: (entry) => formatInboundEnvelope({
|
|
158
|
+
channel: "Discord",
|
|
159
|
+
from: fromLabel,
|
|
160
|
+
timestamp: entry.timestamp,
|
|
161
|
+
body: `${entry.body} [id:${entry.messageId ?? "unknown"} channel:${messageChannelId}]`,
|
|
162
|
+
chatType: "channel",
|
|
163
|
+
senderLabel: entry.sender,
|
|
164
|
+
envelope: envelopeOptions
|
|
165
|
+
})
|
|
166
|
+
});
|
|
167
|
+
const replyContext = resolveReplyContext(message, resolveDiscordMessageText);
|
|
168
|
+
const replyVisibility = replyContext ? evaluateSupplementalContextVisibility({
|
|
169
|
+
mode: contextVisibilityMode,
|
|
170
|
+
kind: "quote",
|
|
171
|
+
senderAllowed: isSupplementalContextSenderAllowed({
|
|
172
|
+
id: replyContext.senderId,
|
|
173
|
+
name: replyContext.senderName,
|
|
174
|
+
tag: replyContext.senderTag,
|
|
175
|
+
memberRoleIds: replyContext.memberRoleIds
|
|
176
|
+
})
|
|
177
|
+
}) : null;
|
|
178
|
+
const filteredReplyContext = replyContext && replyVisibility?.include ? replyContext : null;
|
|
179
|
+
if (replyContext && !filteredReplyContext && isGuildMessage) logVerbose(`discord: drop reply context (mode=${contextVisibilityMode})`);
|
|
180
|
+
const mediaListForContext = [...mediaList];
|
|
181
|
+
if (filteredReplyContext) {
|
|
182
|
+
const referencedReplyMediaList = await resolveReferencedReplyMediaList(message, mediaMaxBytes, {
|
|
183
|
+
fetchImpl: discordRestFetch,
|
|
184
|
+
ssrfPolicy: cfg.browser?.ssrfPolicy,
|
|
185
|
+
readIdleTimeoutMs: DISCORD_ATTACHMENT_IDLE_TIMEOUT_MS,
|
|
186
|
+
totalTimeoutMs: DISCORD_ATTACHMENT_TOTAL_TIMEOUT_MS,
|
|
187
|
+
abortSignal
|
|
188
|
+
});
|
|
189
|
+
if (!isContextAborted(abortSignal)) mediaListForContext.push(...referencedReplyMediaList);
|
|
190
|
+
}
|
|
191
|
+
if (forumContextLine) combinedBody = `${combinedBody}\n${forumContextLine}`;
|
|
192
|
+
let threadStarterBody;
|
|
193
|
+
let threadLabel;
|
|
194
|
+
let parentSessionKey;
|
|
195
|
+
let modelParentSessionKey;
|
|
196
|
+
if (threadChannel) {
|
|
197
|
+
if (channelConfig?.includeThreadStarter !== false) {
|
|
198
|
+
const starter = await resolveDiscordThreadStarter({
|
|
199
|
+
channel: threadChannel,
|
|
200
|
+
client,
|
|
201
|
+
parentId: threadParentId,
|
|
202
|
+
parentType: threadParentType,
|
|
203
|
+
resolveTimestampMs
|
|
204
|
+
});
|
|
205
|
+
if (starter?.text) if (evaluateSupplementalContextVisibility({
|
|
206
|
+
mode: contextVisibilityMode,
|
|
207
|
+
kind: "thread",
|
|
208
|
+
senderAllowed: isSupplementalContextSenderAllowed({
|
|
209
|
+
id: starter.authorId,
|
|
210
|
+
name: starter.authorName ?? starter.author,
|
|
211
|
+
tag: starter.authorTag,
|
|
212
|
+
memberRoleIds: starter.memberRoleIds
|
|
213
|
+
})
|
|
214
|
+
}).include) threadStarterBody = starter.text;
|
|
215
|
+
else logVerbose(`discord: drop thread starter context (mode=${contextVisibilityMode})`);
|
|
216
|
+
}
|
|
217
|
+
const parentName = threadParentName ?? "parent";
|
|
218
|
+
threadLabel = threadName ? `Discord thread #${normalizeDiscordSlug(parentName)} › ${threadName}` : `Discord thread #${normalizeDiscordSlug(parentName)}`;
|
|
219
|
+
if (threadParentId) {
|
|
220
|
+
parentSessionKey = buildAgentSessionKey({
|
|
221
|
+
agentId: route.agentId,
|
|
222
|
+
channel: route.channel,
|
|
223
|
+
peer: {
|
|
224
|
+
kind: "channel",
|
|
225
|
+
id: threadParentId
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
modelParentSessionKey = parentSessionKey;
|
|
229
|
+
}
|
|
230
|
+
if (!threadParentInheritanceEnabled) parentSessionKey = void 0;
|
|
231
|
+
}
|
|
232
|
+
const preflightAudioIndex = preflightAudioTranscript === void 0 ? -1 : mediaListForContext.findIndex((media) => media.contentType?.startsWith("audio/"));
|
|
233
|
+
const threadKeys = resolveThreadSessionKeys({
|
|
234
|
+
baseSessionKey,
|
|
235
|
+
threadId: threadChannel ? messageChannelId : void 0,
|
|
236
|
+
parentSessionKey,
|
|
237
|
+
useSuffix: false
|
|
238
|
+
});
|
|
239
|
+
const replyPlan = await resolveDiscordAutoThreadReplyPlan({
|
|
240
|
+
client,
|
|
241
|
+
message,
|
|
242
|
+
messageChannelId,
|
|
243
|
+
isGuildMessage,
|
|
244
|
+
channelConfig: isRoomEvent ? null : channelConfig,
|
|
245
|
+
threadChannel,
|
|
246
|
+
channelType: channelInfo?.type,
|
|
247
|
+
channelName: channelInfo?.name,
|
|
248
|
+
channelDescription: channelInfo?.topic,
|
|
249
|
+
baseText: baseText ?? "",
|
|
250
|
+
combinedBody,
|
|
251
|
+
replyToMode,
|
|
252
|
+
agentId: route.agentId,
|
|
253
|
+
channel: route.channel,
|
|
254
|
+
cfg,
|
|
255
|
+
threadParentInheritanceEnabled
|
|
256
|
+
});
|
|
257
|
+
const deliverTarget = replyPlan.deliverTarget;
|
|
258
|
+
const replyTarget = replyPlan.replyTarget;
|
|
259
|
+
const replyReference = replyPlan.replyReference;
|
|
260
|
+
const autoThreadContext = replyPlan.autoThreadContext;
|
|
261
|
+
const effectiveFrom = isDirectMessage ? `discord:${author.id}` : autoThreadContext?.From ?? `discord:channel:${messageChannelId}`;
|
|
262
|
+
const dmConversationTarget = isDirectMessage ? resolveDiscordConversationIdentity({
|
|
263
|
+
isDirectMessage,
|
|
264
|
+
userId: author.id
|
|
265
|
+
}) : void 0;
|
|
266
|
+
const effectiveTo = autoThreadContext?.To ?? dmConversationTarget ?? replyTarget;
|
|
267
|
+
if (!effectiveTo) {
|
|
268
|
+
runtime.error?.(danger("discord: missing reply target"));
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
const lastRouteTo = dmConversationTarget ?? effectiveTo;
|
|
272
|
+
const inboundHistory = shouldIncludeChannelHistory ? channelHistory.buildInboundHistory({
|
|
273
|
+
historyKey: messageChannelId,
|
|
274
|
+
limit: historyLimit
|
|
275
|
+
}) : void 0;
|
|
276
|
+
const originatingTo = autoThreadContext?.OriginatingTo ?? dmConversationTarget ?? replyTarget;
|
|
277
|
+
const effectiveSessionKey = boundSessionKey ?? autoThreadContext?.SessionKey ?? threadKeys.sessionKey;
|
|
278
|
+
const effectivePreviousTimestamp = effectiveSessionKey === route.sessionKey ? previousTimestamp : readSessionUpdatedAt({
|
|
279
|
+
storePath,
|
|
280
|
+
sessionKey: effectiveSessionKey
|
|
281
|
+
});
|
|
282
|
+
const ctxPayload = buildChannelInboundEventContext({
|
|
283
|
+
channel: "discord",
|
|
284
|
+
provider: "discord",
|
|
285
|
+
surface: "discord",
|
|
286
|
+
accountId: route.accountId,
|
|
287
|
+
messageId: canonicalMessageId ?? message.id,
|
|
288
|
+
messageIdFull: canonicalMessageId && canonicalMessageId !== message.id ? message.id : void 0,
|
|
289
|
+
timestamp: resolveTimestampMs(message.timestamp),
|
|
290
|
+
from: effectiveFrom,
|
|
291
|
+
sender: {
|
|
292
|
+
id: sender.id,
|
|
293
|
+
name: senderName,
|
|
294
|
+
username: senderUsername,
|
|
295
|
+
tag: sender.tag,
|
|
296
|
+
roles: memberRoleIds,
|
|
297
|
+
displayLabel: senderLabel
|
|
298
|
+
},
|
|
299
|
+
conversation: {
|
|
300
|
+
kind: isDirectMessage ? "direct" : "channel",
|
|
301
|
+
id: messageChannelId,
|
|
302
|
+
label: fromLabel,
|
|
303
|
+
spaceId: isGuildMessage ? (guildInfo?.id ?? guildSlug) || void 0 : void 0,
|
|
304
|
+
threadId: threadChannel?.id ?? autoThreadContext?.createdThreadId ?? void 0,
|
|
305
|
+
routePeer: {
|
|
306
|
+
kind: isDirectMessage ? "direct" : "channel",
|
|
307
|
+
id: isDirectMessage ? author.id : messageChannelId
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
route: {
|
|
311
|
+
agentId: route.agentId,
|
|
312
|
+
accountId: route.accountId,
|
|
313
|
+
routeSessionKey: route.sessionKey,
|
|
314
|
+
dispatchSessionKey: effectiveSessionKey,
|
|
315
|
+
parentSessionKey: autoThreadContext?.ParentSessionKey ?? threadKeys.parentSessionKey,
|
|
316
|
+
modelParentSessionKey: autoThreadContext?.ModelParentSessionKey ?? modelParentSessionKey ?? void 0
|
|
317
|
+
},
|
|
318
|
+
reply: {
|
|
319
|
+
to: effectiveTo,
|
|
320
|
+
originatingTo
|
|
321
|
+
},
|
|
322
|
+
message: {
|
|
323
|
+
inboundEventKind: ctx.inboundEventKind,
|
|
324
|
+
body: combinedBody,
|
|
325
|
+
rawBody: preflightAudioTranscript ?? baseText,
|
|
326
|
+
bodyForAgent: preflightAudioTranscript ?? baseText ?? text,
|
|
327
|
+
commandBody: preflightAudioTranscript ?? baseText,
|
|
328
|
+
envelopeFrom: fromLabel,
|
|
329
|
+
inboundHistory
|
|
330
|
+
},
|
|
331
|
+
access: {
|
|
332
|
+
mentions: {
|
|
333
|
+
canDetectMention: ctx.canDetectMention,
|
|
334
|
+
wasMentioned: ctx.effectiveWasMentioned,
|
|
335
|
+
hasAnyMention: ctx.hasAnyMention,
|
|
336
|
+
requireMention: ctx.shouldRequireMention,
|
|
337
|
+
effectiveWasMentioned: ctx.effectiveWasMentioned
|
|
338
|
+
},
|
|
339
|
+
commands: {
|
|
340
|
+
authorized: commandAuthorized,
|
|
341
|
+
allowTextCommands: ctx.allowTextCommands,
|
|
342
|
+
useAccessGroups: false,
|
|
343
|
+
authorizers: []
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
commandTurn: {
|
|
347
|
+
kind: "text-slash",
|
|
348
|
+
source: "text",
|
|
349
|
+
authorized: commandAuthorized,
|
|
350
|
+
body: preflightAudioTranscript ?? baseText
|
|
351
|
+
},
|
|
352
|
+
media: toInboundMediaFacts(mediaListForContext, { transcribed: (_media, index) => index === preflightAudioIndex }),
|
|
353
|
+
supplemental: {
|
|
354
|
+
quote: filteredReplyContext ? {
|
|
355
|
+
id: filteredReplyContext.id,
|
|
356
|
+
body: filteredReplyContext.body,
|
|
357
|
+
sender: filteredReplyContext.sender
|
|
358
|
+
} : void 0,
|
|
359
|
+
thread: {
|
|
360
|
+
starterBody: !effectivePreviousTimestamp ? threadStarterBody : void 0,
|
|
361
|
+
label: threadLabel
|
|
362
|
+
},
|
|
363
|
+
groupSystemPrompt: isGuildMessage ? groupSystemPrompt : void 0
|
|
364
|
+
},
|
|
365
|
+
extra: {
|
|
366
|
+
...preflightAudioTranscript !== void 0 ? { Transcript: preflightAudioTranscript } : {},
|
|
367
|
+
GroupSubject: groupSubject,
|
|
368
|
+
GroupChannel: groupChannel,
|
|
369
|
+
UntrustedStructuredContext: untrustedContext,
|
|
370
|
+
OwnerAllowFrom: ownerAllowFrom
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
const persistedSessionKey = ctxPayload.SessionKey ?? route.sessionKey;
|
|
374
|
+
if (isRoomEvent && shouldIncludeChannelHistory) await channelHistory.recordWithMedia({
|
|
375
|
+
historyKey: messageChannelId,
|
|
376
|
+
limit: historyLimit,
|
|
377
|
+
entry: {
|
|
378
|
+
sender: senderName,
|
|
379
|
+
body: text,
|
|
380
|
+
timestamp: resolveTimestampMs(message.timestamp),
|
|
381
|
+
messageId: message.id
|
|
382
|
+
},
|
|
383
|
+
media: toHistoryMediaEntries(mediaList, { messageId: message.id }),
|
|
384
|
+
messageId: message.id
|
|
385
|
+
});
|
|
386
|
+
if (shouldLogVerbose()) {
|
|
387
|
+
const preview = truncateUtf16Safe(combinedBody, 200).replace(/\n/g, "\\n");
|
|
388
|
+
logVerbose(`discord inbound: channel=${messageChannelId} deliver=${deliverTarget} from=${ctxPayload.From} preview="${preview}"`);
|
|
389
|
+
}
|
|
390
|
+
return {
|
|
391
|
+
ctxPayload,
|
|
392
|
+
persistedSessionKey,
|
|
393
|
+
turn: {
|
|
394
|
+
storePath,
|
|
395
|
+
record: {
|
|
396
|
+
updateLastRoute: {
|
|
397
|
+
sessionKey: persistedSessionKey,
|
|
398
|
+
channel: "discord",
|
|
399
|
+
to: lastRouteTo,
|
|
400
|
+
accountId: route.accountId,
|
|
401
|
+
mainDmOwnerPin: isDirectMessage && persistedSessionKey === route.mainSessionKey && pinnedMainDmOwner ? {
|
|
402
|
+
ownerRecipient: pinnedMainDmOwner,
|
|
403
|
+
senderRecipient: author.id,
|
|
404
|
+
onSkip: ({ ownerRecipient, senderRecipient }) => {
|
|
405
|
+
logVerbose(`discord: skip main-session last route for ${senderRecipient} (pinned owner ${ownerRecipient})`);
|
|
406
|
+
}
|
|
407
|
+
} : void 0
|
|
408
|
+
},
|
|
409
|
+
onRecordError: (err) => {
|
|
410
|
+
logVerbose(`discord: failed updating session meta: ${String(err)}`);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
},
|
|
414
|
+
replyPlan,
|
|
415
|
+
deliverTarget,
|
|
416
|
+
replyTarget,
|
|
417
|
+
replyReference
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
//#endregion
|
|
421
|
+
//#region extensions/discord/src/draft-chunking.ts
|
|
422
|
+
const DEFAULT_DISCORD_DRAFT_STREAM_MIN = 200;
|
|
423
|
+
const DEFAULT_DISCORD_DRAFT_STREAM_MAX = 800;
|
|
424
|
+
function resolveDiscordDraftStreamingChunking(cfg, accountId) {
|
|
425
|
+
const textLimit = resolveTextChunkLimit(cfg, "discord", accountId, { fallbackLimit: DISCORD_TEXT_CHUNK_LIMIT });
|
|
426
|
+
const normalizedAccountId = normalizeAccountId(accountId);
|
|
427
|
+
const draftCfg = resolveChannelStreamingPreviewChunk(resolveAccountEntry(cfg?.channels?.discord?.accounts, normalizedAccountId)) ?? resolveChannelStreamingPreviewChunk(cfg?.channels?.discord);
|
|
428
|
+
const maxRequested = Math.max(1, Math.floor(draftCfg?.maxChars ?? DEFAULT_DISCORD_DRAFT_STREAM_MAX));
|
|
429
|
+
const maxChars = Math.max(1, Math.min(maxRequested, textLimit));
|
|
430
|
+
const minRequested = Math.max(1, Math.floor(draftCfg?.minChars ?? DEFAULT_DISCORD_DRAFT_STREAM_MIN));
|
|
431
|
+
return {
|
|
432
|
+
minChars: Math.min(minRequested, maxChars),
|
|
433
|
+
maxChars,
|
|
434
|
+
breakPreference: draftCfg?.breakPreference === "newline" || draftCfg?.breakPreference === "sentence" ? draftCfg.breakPreference : "paragraph"
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
//#endregion
|
|
438
|
+
//#region extensions/discord/src/draft-stream.ts
|
|
439
|
+
/** Discord messages cap at 2000 characters. */
|
|
440
|
+
const DISCORD_STREAM_MAX_CHARS = 2e3;
|
|
441
|
+
const DEFAULT_THROTTLE_MS = 1200;
|
|
442
|
+
const DISCORD_PREVIEW_ALLOWED_MENTIONS = { parse: [] };
|
|
443
|
+
function createDiscordDraftStream(params) {
|
|
444
|
+
const maxChars = Math.min(params.maxChars ?? DISCORD_STREAM_MAX_CHARS, DISCORD_STREAM_MAX_CHARS);
|
|
445
|
+
const throttleMs = Math.max(250, params.throttleMs ?? DEFAULT_THROTTLE_MS);
|
|
446
|
+
const minInitialChars = params.minInitialChars;
|
|
447
|
+
const channelId = params.channelId;
|
|
448
|
+
const rest = params.rest;
|
|
449
|
+
const flags = resolveDiscordMessageFlags({ suppressEmbeds: params.suppressEmbeds });
|
|
450
|
+
const resolveReplyToMessageId = () => typeof params.replyToMessageId === "function" ? params.replyToMessageId() : params.replyToMessageId;
|
|
451
|
+
const streamState = {
|
|
452
|
+
stopped: false,
|
|
453
|
+
final: false
|
|
454
|
+
};
|
|
455
|
+
let streamMessageId;
|
|
456
|
+
let lastSentText = "";
|
|
457
|
+
const sendOrEditStreamMessage = async (text) => {
|
|
458
|
+
if (streamState.stopped && !streamState.final) return false;
|
|
459
|
+
const trimmed = text.trimEnd();
|
|
460
|
+
if (!trimmed) return false;
|
|
461
|
+
if (trimmed.length > maxChars) {
|
|
462
|
+
streamState.stopped = true;
|
|
463
|
+
params.warn?.(`discord stream preview stopped (text length ${trimmed.length} > ${maxChars})`);
|
|
464
|
+
return false;
|
|
465
|
+
}
|
|
466
|
+
if (trimmed === lastSentText) return true;
|
|
467
|
+
if (streamMessageId === void 0 && minInitialChars != null && !streamState.final) {
|
|
468
|
+
if (trimmed.length < minInitialChars) return false;
|
|
469
|
+
}
|
|
470
|
+
lastSentText = trimmed;
|
|
471
|
+
try {
|
|
472
|
+
if (streamMessageId !== void 0) {
|
|
473
|
+
await editChannelMessage(rest, channelId, streamMessageId, { body: {
|
|
474
|
+
content: trimmed,
|
|
475
|
+
allowed_mentions: DISCORD_PREVIEW_ALLOWED_MENTIONS,
|
|
476
|
+
...flags ? { flags } : {}
|
|
477
|
+
} });
|
|
478
|
+
return true;
|
|
479
|
+
}
|
|
480
|
+
const replyToMessageId = resolveReplyToMessageId()?.trim();
|
|
481
|
+
const messageReference = replyToMessageId ? {
|
|
482
|
+
message_id: replyToMessageId,
|
|
483
|
+
fail_if_not_exists: false
|
|
484
|
+
} : void 0;
|
|
485
|
+
const sentMessageId = (await createChannelMessage(rest, channelId, { body: {
|
|
486
|
+
content: trimmed,
|
|
487
|
+
allowed_mentions: DISCORD_PREVIEW_ALLOWED_MENTIONS,
|
|
488
|
+
...flags ? { flags } : {},
|
|
489
|
+
...messageReference ? { message_reference: messageReference } : {}
|
|
490
|
+
} }))?.id;
|
|
491
|
+
if (typeof sentMessageId !== "string" || !sentMessageId) {
|
|
492
|
+
streamState.stopped = true;
|
|
493
|
+
params.warn?.("discord stream preview stopped (missing message id from send)");
|
|
494
|
+
return false;
|
|
495
|
+
}
|
|
496
|
+
streamMessageId = sentMessageId;
|
|
497
|
+
return true;
|
|
498
|
+
} catch (err) {
|
|
499
|
+
streamState.stopped = true;
|
|
500
|
+
params.warn?.(`discord stream preview failed: ${formatErrorMessage(err)}`);
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
const readMessageId = () => streamMessageId;
|
|
505
|
+
const clearMessageId = () => {
|
|
506
|
+
streamMessageId = void 0;
|
|
507
|
+
};
|
|
508
|
+
const isValidStreamMessageId = (value) => typeof value === "string";
|
|
509
|
+
const deleteStreamMessage = async (messageId) => {
|
|
510
|
+
await deleteChannelMessage(rest, channelId, messageId);
|
|
511
|
+
};
|
|
512
|
+
const { loop, update, stop, clear, discardPending, seal } = createFinalizableDraftLifecycle({
|
|
513
|
+
throttleMs,
|
|
514
|
+
state: streamState,
|
|
515
|
+
sendOrEditStreamMessage,
|
|
516
|
+
readMessageId,
|
|
517
|
+
clearMessageId,
|
|
518
|
+
isValidMessageId: isValidStreamMessageId,
|
|
519
|
+
deleteMessage: deleteStreamMessage,
|
|
520
|
+
warn: params.warn,
|
|
521
|
+
warnPrefix: "discord stream preview cleanup failed"
|
|
522
|
+
});
|
|
523
|
+
const forceNewMessage = () => {
|
|
524
|
+
streamMessageId = void 0;
|
|
525
|
+
lastSentText = "";
|
|
526
|
+
loop.resetPending();
|
|
527
|
+
};
|
|
528
|
+
params.log?.(`discord stream preview ready (maxChars=${maxChars}, throttleMs=${throttleMs})`);
|
|
529
|
+
return {
|
|
530
|
+
update,
|
|
531
|
+
flush: loop.flush,
|
|
532
|
+
messageId: () => streamMessageId,
|
|
533
|
+
clear,
|
|
534
|
+
discardPending,
|
|
535
|
+
seal,
|
|
536
|
+
stop,
|
|
537
|
+
forceNewMessage
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
//#endregion
|
|
541
|
+
//#region extensions/discord/src/monitor/message-handler.draft-preview.ts
|
|
542
|
+
function createDiscordDraftPreviewController(params) {
|
|
543
|
+
const discordStreamMode = resolveDiscordPreviewStreamMode(params.discordConfig);
|
|
544
|
+
const draftMaxChars = Math.min(params.textLimit, 2e3);
|
|
545
|
+
const accountBlockStreamingEnabled = resolveChannelStreamingBlockEnabled(params.discordConfig) ?? params.cfg.agents?.defaults?.blockStreamingDefault === "on";
|
|
546
|
+
const canStreamProgressDraftForToolOnlySource = params.sourceRepliesAreToolOnly && discordStreamMode === "progress";
|
|
547
|
+
const draftStream = (!params.sourceRepliesAreToolOnly || canStreamProgressDraftForToolOnlySource) && discordStreamMode !== "off" && !accountBlockStreamingEnabled ? createDiscordDraftStream({
|
|
548
|
+
rest: params.deliveryRest,
|
|
549
|
+
channelId: params.deliverChannelId,
|
|
550
|
+
maxChars: draftMaxChars,
|
|
551
|
+
replyToMessageId: () => params.replyReference.peek(),
|
|
552
|
+
minInitialChars: discordStreamMode === "progress" ? 0 : 30,
|
|
553
|
+
suppressEmbeds: params.discordConfig?.suppressEmbeds ?? true,
|
|
554
|
+
throttleMs: 1200,
|
|
555
|
+
log: params.log,
|
|
556
|
+
warn: params.log
|
|
557
|
+
}) : void 0;
|
|
558
|
+
const draftChunking = draftStream && discordStreamMode === "block" ? resolveDiscordDraftStreamingChunking(params.cfg, params.accountId) : void 0;
|
|
559
|
+
const shouldSplitPreviewMessages = discordStreamMode === "block";
|
|
560
|
+
const draftChunker = draftChunking ? new EmbeddedBlockChunker(draftChunking) : void 0;
|
|
561
|
+
let lastPartialText = "";
|
|
562
|
+
let draftText = "";
|
|
563
|
+
let hasStreamedMessage = false;
|
|
564
|
+
let finalizedViaPreviewMessage = false;
|
|
565
|
+
let finalReplyDelivered = false;
|
|
566
|
+
const previewToolProgressEnabled = Boolean(draftStream) && resolveChannelStreamingPreviewToolProgress(params.discordConfig);
|
|
567
|
+
const suppressDefaultToolProgressMessages = Boolean(draftStream) && resolveChannelStreamingSuppressDefaultToolProgressMessages(params.discordConfig, {
|
|
568
|
+
draftStreamActive: true,
|
|
569
|
+
previewToolProgressEnabled
|
|
570
|
+
});
|
|
571
|
+
let previewToolProgressSuppressed = false;
|
|
572
|
+
let previewToolProgressLines = [];
|
|
573
|
+
let reasoningProgressRawText = "";
|
|
574
|
+
let lastReasoningProgressLine;
|
|
575
|
+
const progressSeed = `${params.accountId}:${params.deliverChannelId}`;
|
|
576
|
+
const renderProgressDraft = async (options) => {
|
|
577
|
+
if (!draftStream || discordStreamMode !== "progress") return;
|
|
578
|
+
const previewText = formatChannelProgressDraftText({
|
|
579
|
+
entry: params.discordConfig,
|
|
580
|
+
lines: previewToolProgressLines,
|
|
581
|
+
seed: progressSeed
|
|
582
|
+
});
|
|
583
|
+
if (!previewText || previewText === lastPartialText) return;
|
|
584
|
+
lastPartialText = previewText;
|
|
585
|
+
draftText = previewText;
|
|
586
|
+
hasStreamedMessage = true;
|
|
587
|
+
draftChunker?.reset();
|
|
588
|
+
draftStream.update(previewText);
|
|
589
|
+
if (options?.flush) await draftStream.flush();
|
|
590
|
+
};
|
|
591
|
+
const progressDraftGate = createChannelProgressDraftGate({ onStart: () => renderProgressDraft({ flush: true }) });
|
|
592
|
+
const resetProgressState = () => {
|
|
593
|
+
lastPartialText = "";
|
|
594
|
+
draftText = "";
|
|
595
|
+
draftChunker?.reset();
|
|
596
|
+
previewToolProgressSuppressed = false;
|
|
597
|
+
previewToolProgressLines = [];
|
|
598
|
+
reasoningProgressRawText = "";
|
|
599
|
+
lastReasoningProgressLine = void 0;
|
|
600
|
+
};
|
|
601
|
+
const forceNewMessageIfNeeded = () => {
|
|
602
|
+
if (shouldSplitPreviewMessages && hasStreamedMessage) {
|
|
603
|
+
params.log("discord: calling forceNewMessage() for draft stream");
|
|
604
|
+
draftStream?.forceNewMessage();
|
|
605
|
+
}
|
|
606
|
+
resetProgressState();
|
|
607
|
+
};
|
|
608
|
+
return {
|
|
609
|
+
draftStream,
|
|
610
|
+
previewToolProgressEnabled,
|
|
611
|
+
suppressDefaultToolProgressMessages,
|
|
612
|
+
get isProgressMode() {
|
|
613
|
+
return discordStreamMode === "progress";
|
|
614
|
+
},
|
|
615
|
+
get hasProgressDraftStarted() {
|
|
616
|
+
return progressDraftGate.hasStarted;
|
|
617
|
+
},
|
|
618
|
+
get finalizedViaPreviewMessage() {
|
|
619
|
+
return finalizedViaPreviewMessage;
|
|
620
|
+
},
|
|
621
|
+
markFinalReplyDelivered() {
|
|
622
|
+
finalReplyDelivered = true;
|
|
623
|
+
},
|
|
624
|
+
markPreviewFinalized() {
|
|
625
|
+
finalizedViaPreviewMessage = true;
|
|
626
|
+
},
|
|
627
|
+
disableBlockStreamingForDraft: draftStream ? true : void 0,
|
|
628
|
+
async startProgressDraft() {
|
|
629
|
+
if (!draftStream || discordStreamMode !== "progress") return;
|
|
630
|
+
await progressDraftGate.startNow();
|
|
631
|
+
},
|
|
632
|
+
async pushToolProgress(line, options) {
|
|
633
|
+
if (!draftStream) return;
|
|
634
|
+
if (options?.toolName !== void 0 && !isChannelProgressDraftWorkToolName(options.toolName)) return;
|
|
635
|
+
if (isEmptyDiscordProgressLine(line)) return;
|
|
636
|
+
const normalized = normalizeChannelProgressDraftLineIdentity(line);
|
|
637
|
+
if (!normalized) return;
|
|
638
|
+
const progressLine = typeof line === "object" && line !== void 0 ? line : normalized;
|
|
639
|
+
if (discordStreamMode !== "progress") {
|
|
640
|
+
if (!previewToolProgressEnabled || previewToolProgressSuppressed) return;
|
|
641
|
+
const nextLines = mergeChannelProgressDraftLine(previewToolProgressLines, progressLine, { maxLines: resolveChannelProgressDraftMaxLines(params.discordConfig) });
|
|
642
|
+
if (nextLines === previewToolProgressLines) return;
|
|
643
|
+
previewToolProgressLines = nextLines;
|
|
644
|
+
const previewText = formatChannelProgressDraftText({
|
|
645
|
+
entry: params.discordConfig,
|
|
646
|
+
lines: previewToolProgressLines,
|
|
647
|
+
seed: progressSeed
|
|
648
|
+
});
|
|
649
|
+
lastPartialText = previewText;
|
|
650
|
+
draftText = previewText;
|
|
651
|
+
hasStreamedMessage = true;
|
|
652
|
+
draftChunker?.reset();
|
|
653
|
+
draftStream.update(previewText);
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
if (previewToolProgressEnabled && !previewToolProgressSuppressed && normalized) previewToolProgressLines = mergeChannelProgressDraftLine(previewToolProgressLines, progressLine, { maxLines: resolveChannelProgressDraftMaxLines(params.discordConfig) });
|
|
657
|
+
const alreadyStarted = progressDraftGate.hasStarted;
|
|
658
|
+
if (shouldStartDiscordProgressDraftNow(line)) await progressDraftGate.startNow();
|
|
659
|
+
else await progressDraftGate.noteWork();
|
|
660
|
+
if (alreadyStarted && progressDraftGate.hasStarted) await renderProgressDraft();
|
|
661
|
+
},
|
|
662
|
+
async pushReasoningProgress(text) {
|
|
663
|
+
if (!draftStream || discordStreamMode !== "progress" || !text) return;
|
|
664
|
+
reasoningProgressRawText = mergeReasoningProgressText(reasoningProgressRawText, text);
|
|
665
|
+
const normalized = normalizeReasoningProgressLine(reasoningProgressRawText);
|
|
666
|
+
if (!normalized) return;
|
|
667
|
+
if (previewToolProgressEnabled && !previewToolProgressSuppressed) {
|
|
668
|
+
const priorIndex = lastReasoningProgressLine === void 0 ? -1 : previewToolProgressLines.lastIndexOf(lastReasoningProgressLine);
|
|
669
|
+
if (priorIndex >= 0) {
|
|
670
|
+
previewToolProgressLines = [...previewToolProgressLines];
|
|
671
|
+
previewToolProgressLines[priorIndex] = normalized;
|
|
672
|
+
} else previewToolProgressLines = [...previewToolProgressLines, normalized].slice(-resolveChannelProgressDraftMaxLines(params.discordConfig));
|
|
673
|
+
lastReasoningProgressLine = normalized;
|
|
674
|
+
}
|
|
675
|
+
const alreadyStarted = progressDraftGate.hasStarted;
|
|
676
|
+
await progressDraftGate.noteWork();
|
|
677
|
+
if (alreadyStarted && progressDraftGate.hasStarted) await renderProgressDraft();
|
|
678
|
+
},
|
|
679
|
+
resolvePreviewFinalText(text) {
|
|
680
|
+
if (typeof text !== "string") return;
|
|
681
|
+
const formatted = convertMarkdownTables(stripInlineDirectiveTagsForDelivery(text).text, params.tableMode);
|
|
682
|
+
const chunks = chunkDiscordTextWithMode(formatted, {
|
|
683
|
+
maxChars: draftMaxChars,
|
|
684
|
+
maxLines: params.maxLinesPerMessage,
|
|
685
|
+
chunkMode: params.chunkMode
|
|
686
|
+
});
|
|
687
|
+
if (!chunks.length && formatted) chunks.push(formatted);
|
|
688
|
+
if (chunks.length !== 1) return;
|
|
689
|
+
const trimmed = chunks[0].trim();
|
|
690
|
+
if (!trimmed) return;
|
|
691
|
+
const currentPreviewText = discordStreamMode === "block" ? draftText : lastPartialText;
|
|
692
|
+
if (currentPreviewText && currentPreviewText.startsWith(trimmed) && trimmed.length < currentPreviewText.length) return;
|
|
693
|
+
return trimmed;
|
|
694
|
+
},
|
|
695
|
+
updateFromPartial(text) {
|
|
696
|
+
if (!draftStream || !text) return;
|
|
697
|
+
const cleaned = stripInlineDirectiveTagsForDelivery(stripReasoningTagsFromText(text, {
|
|
698
|
+
mode: "strict",
|
|
699
|
+
trim: "both"
|
|
700
|
+
})).text;
|
|
701
|
+
if (!cleaned || cleaned.startsWith("Reasoning:\n")) return;
|
|
702
|
+
if (cleaned === lastPartialText) return;
|
|
703
|
+
if (discordStreamMode === "progress") return;
|
|
704
|
+
previewToolProgressSuppressed = true;
|
|
705
|
+
previewToolProgressLines = [];
|
|
706
|
+
hasStreamedMessage = true;
|
|
707
|
+
if (discordStreamMode === "partial") {
|
|
708
|
+
if (lastPartialText && lastPartialText.startsWith(cleaned) && cleaned.length < lastPartialText.length) return;
|
|
709
|
+
lastPartialText = cleaned;
|
|
710
|
+
draftStream.update(cleaned);
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
let delta = cleaned;
|
|
714
|
+
if (cleaned.startsWith(lastPartialText)) delta = cleaned.slice(lastPartialText.length);
|
|
715
|
+
else {
|
|
716
|
+
draftChunker?.reset();
|
|
717
|
+
draftText = "";
|
|
718
|
+
}
|
|
719
|
+
lastPartialText = cleaned;
|
|
720
|
+
if (!delta) return;
|
|
721
|
+
if (!draftChunker) {
|
|
722
|
+
draftText = cleaned;
|
|
723
|
+
draftStream.update(draftText);
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
726
|
+
draftChunker.append(delta);
|
|
727
|
+
draftChunker.drain({
|
|
728
|
+
force: false,
|
|
729
|
+
emit: (chunk) => {
|
|
730
|
+
draftText += chunk;
|
|
731
|
+
draftStream.update(draftText);
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
},
|
|
735
|
+
handleAssistantMessageBoundary() {
|
|
736
|
+
if (discordStreamMode === "progress") return;
|
|
737
|
+
forceNewMessageIfNeeded();
|
|
738
|
+
},
|
|
739
|
+
async flush() {
|
|
740
|
+
if (!draftStream) return;
|
|
741
|
+
if (draftChunker?.hasBuffered()) {
|
|
742
|
+
draftChunker.drain({
|
|
743
|
+
force: true,
|
|
744
|
+
emit: (chunk) => {
|
|
745
|
+
draftText += chunk;
|
|
746
|
+
}
|
|
747
|
+
});
|
|
748
|
+
draftChunker.reset();
|
|
749
|
+
if (draftText) draftStream.update(draftText);
|
|
750
|
+
}
|
|
751
|
+
await draftStream.flush();
|
|
752
|
+
},
|
|
753
|
+
async cleanup() {
|
|
754
|
+
try {
|
|
755
|
+
progressDraftGate.cancel();
|
|
756
|
+
if (!finalReplyDelivered) await draftStream?.discardPending();
|
|
757
|
+
if (!finalReplyDelivered && !finalizedViaPreviewMessage && draftStream?.messageId()) await draftStream.clear();
|
|
758
|
+
} catch (err) {
|
|
759
|
+
params.log(`discord: draft cleanup failed: ${String(err)}`);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
function normalizeReasoningProgressLine(text) {
|
|
765
|
+
return text.replace(/^\s*(?:>\s*)?(?:Reasoning:|Thinking\.{0,3})\s*/i, "").replace(/\s+/g, " ").trim();
|
|
766
|
+
}
|
|
767
|
+
function mergeReasoningProgressText(current, incoming) {
|
|
768
|
+
if (!current) return incoming;
|
|
769
|
+
const normalizedCurrent = normalizeReasoningProgressLine(current);
|
|
770
|
+
const normalizedIncoming = normalizeReasoningProgressLine(incoming);
|
|
771
|
+
if (!normalizedIncoming || normalizedIncoming === normalizedCurrent) return current;
|
|
772
|
+
if (isReasoningSnapshotText(incoming) || normalizedIncoming.startsWith(normalizedCurrent)) return incoming;
|
|
773
|
+
return `${current}${incoming}`;
|
|
774
|
+
}
|
|
775
|
+
function isReasoningSnapshotText(text) {
|
|
776
|
+
return /^\s*(?:>\s*)?(?:Reasoning:|Thinking\.{0,3})\s*/i.test(text);
|
|
777
|
+
}
|
|
778
|
+
function isEmptyDiscordProgressLine(line) {
|
|
779
|
+
if (!line || typeof line === "string") return false;
|
|
780
|
+
return line.toolName === "apply_patch" && !line.detail && !line.status;
|
|
781
|
+
}
|
|
782
|
+
function shouldStartDiscordProgressDraftNow(line) {
|
|
783
|
+
return typeof line === "object" && line?.kind === "patch" && Boolean(line.detail);
|
|
784
|
+
}
|
|
785
|
+
//#endregion
|
|
786
|
+
//#region extensions/discord/src/monitor/message-handler.process.ts
|
|
787
|
+
function sleep(ms) {
|
|
788
|
+
return new Promise((resolve) => {
|
|
789
|
+
setTimeout(resolve, ms);
|
|
790
|
+
});
|
|
791
|
+
}
|
|
792
|
+
const DISCORD_TYPING_MAX_DURATION_MS = 20 * 6e4;
|
|
793
|
+
let replyRuntimePromise;
|
|
794
|
+
async function loadReplyRuntime() {
|
|
795
|
+
replyRuntimePromise ??= import("klaw/plugin-sdk/reply-runtime");
|
|
796
|
+
return await replyRuntimePromise;
|
|
797
|
+
}
|
|
798
|
+
function isProcessAborted(abortSignal) {
|
|
799
|
+
return Boolean(abortSignal?.aborted);
|
|
800
|
+
}
|
|
801
|
+
function formatDiscordReplyDeliveryFailure(params) {
|
|
802
|
+
const context = [`target=${params.target}`, params.sessionKey ? `session=${params.sessionKey}` : void 0].filter(Boolean).join(" ");
|
|
803
|
+
return `discord ${params.kind} reply failed (${context}): ${String(params.err)}`;
|
|
804
|
+
}
|
|
805
|
+
function readToolStringArg(args, key) {
|
|
806
|
+
const value = args[key];
|
|
807
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
808
|
+
}
|
|
809
|
+
function readToolBooleanArg(args, key) {
|
|
810
|
+
return args[key] === true;
|
|
811
|
+
}
|
|
812
|
+
async function processDiscordMessage(ctx, observer) {
|
|
813
|
+
const dispatchStartedAt = Date.now();
|
|
814
|
+
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 } = ctx;
|
|
815
|
+
if (isProcessAborted(abortSignal)) return;
|
|
816
|
+
if (botLoopProtection) {
|
|
817
|
+
const botLoopResult = recordChannelBotPairLoopAndCheckSuppression(botLoopProtection);
|
|
818
|
+
if (botLoopResult.suppressed) {
|
|
819
|
+
logVerbose(`discord: bot-to-bot loop detected before dispatch setup, suppressing for ${Math.max(0, Math.ceil((botLoopResult.cooldownUntilMs - Date.now()) / 1e3))}s`);
|
|
820
|
+
return;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
const mediaResolveOptions = {
|
|
824
|
+
fetchImpl: discordRestFetch,
|
|
825
|
+
ssrfPolicy: cfg.browser?.ssrfPolicy,
|
|
826
|
+
readIdleTimeoutMs: DISCORD_ATTACHMENT_IDLE_TIMEOUT_MS,
|
|
827
|
+
totalTimeoutMs: DISCORD_ATTACHMENT_TOTAL_TIMEOUT_MS,
|
|
828
|
+
abortSignal
|
|
829
|
+
};
|
|
830
|
+
const mediaList = await resolveMediaList(message, mediaMaxBytes, mediaResolveOptions);
|
|
831
|
+
if (isProcessAborted(abortSignal)) return;
|
|
832
|
+
const forwardedMediaList = await resolveForwardedMediaList(message, mediaMaxBytes, mediaResolveOptions);
|
|
833
|
+
if (isProcessAborted(abortSignal)) return;
|
|
834
|
+
mediaList.push(...forwardedMediaList);
|
|
835
|
+
const text = messageText;
|
|
836
|
+
if (!text) {
|
|
837
|
+
logVerbose("discord: drop message " + message.id + " (empty content)");
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
const boundThreadId = ctx.threadBinding?.conversation?.conversationId?.trim();
|
|
841
|
+
if (boundThreadId && typeof threadBindings.touchThread === "function") threadBindings.touchThread({ threadId: boundThreadId });
|
|
842
|
+
const { createReplyDispatcherWithTyping, dispatchInboundMessage, settleReplyDispatcher } = await loadReplyRuntime();
|
|
843
|
+
const sourceReplyDeliveryMode = resolveChannelMessageSourceReplyDeliveryMode({
|
|
844
|
+
cfg,
|
|
845
|
+
ctx: {
|
|
846
|
+
ChatType: isDirectMessage ? "direct" : isGroupDm ? "group" : isGuildMessage ? "channel" : void 0,
|
|
847
|
+
InboundEventKind: ctx.inboundEventKind
|
|
848
|
+
}
|
|
849
|
+
});
|
|
850
|
+
const sourceRepliesAreToolOnly = sourceReplyDeliveryMode === "message_tool_only";
|
|
851
|
+
const ackReaction = resolveAckReaction(cfg, route.agentId, {
|
|
852
|
+
channel: "discord",
|
|
853
|
+
accountId
|
|
854
|
+
});
|
|
855
|
+
const removeAckAfterReply = cfg.messages?.removeAckAfterReply ?? false;
|
|
856
|
+
const mediaLocalRoots = getAgentScopedMediaLocalRoots(cfg, route.agentId);
|
|
857
|
+
const isRoomEvent = ctx.inboundEventKind === "room_event";
|
|
858
|
+
const shouldAckReaction$1 = () => Boolean(!isRoomEvent && ackReaction && shouldAckReaction({
|
|
859
|
+
scope: ackReactionScope,
|
|
860
|
+
isDirect: isDirectMessage,
|
|
861
|
+
isGroup: isGuildMessage || isGroupDm,
|
|
862
|
+
isMentionableGroup: isGuildMessage,
|
|
863
|
+
requireMention: shouldRequireMention,
|
|
864
|
+
canDetectMention,
|
|
865
|
+
effectiveWasMentioned,
|
|
866
|
+
shouldBypassMention
|
|
867
|
+
}));
|
|
868
|
+
const shouldSendAckReaction = shouldAckReaction$1();
|
|
869
|
+
const statusReactionsExplicitlyEnabled = cfg.messages?.statusReactions?.enabled === true;
|
|
870
|
+
const statusReactionsEnabled = !isRoomEvent && shouldSendAckReaction && cfg.messages?.statusReactions?.enabled !== false && (!sourceRepliesAreToolOnly || statusReactionsExplicitlyEnabled);
|
|
871
|
+
const feedbackRest = createDiscordRestClient({
|
|
872
|
+
cfg,
|
|
873
|
+
token,
|
|
874
|
+
accountId
|
|
875
|
+
}).rest;
|
|
876
|
+
const deliveryRest = createDiscordRestClient({
|
|
877
|
+
cfg,
|
|
878
|
+
token,
|
|
879
|
+
accountId
|
|
880
|
+
}).rest;
|
|
881
|
+
const ackReactionContext = createDiscordAckReactionContext({
|
|
882
|
+
rest: feedbackRest,
|
|
883
|
+
cfg,
|
|
884
|
+
accountId
|
|
885
|
+
});
|
|
886
|
+
const discordAdapter = createDiscordAckReactionAdapter({
|
|
887
|
+
channelId: messageChannelId,
|
|
888
|
+
messageId: message.id,
|
|
889
|
+
reactionContext: ackReactionContext
|
|
890
|
+
});
|
|
891
|
+
let statusReactionTarget = `${messageChannelId}/${message.id}`;
|
|
892
|
+
let statusReactionsActive = statusReactionsEnabled;
|
|
893
|
+
let statusReactions = createStatusReactionController({
|
|
894
|
+
enabled: statusReactionsEnabled,
|
|
895
|
+
adapter: discordAdapter,
|
|
896
|
+
initialEmoji: ackReaction,
|
|
897
|
+
emojis: cfg.messages?.statusReactions?.emojis,
|
|
898
|
+
timing: cfg.messages?.statusReactions?.timing,
|
|
899
|
+
onError: (err) => {
|
|
900
|
+
logAckFailure({
|
|
901
|
+
log: logVerbose,
|
|
902
|
+
channel: "discord",
|
|
903
|
+
target: statusReactionTarget,
|
|
904
|
+
error: err
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
});
|
|
908
|
+
const resolveTrackedReactionChannelId = async (args) => {
|
|
909
|
+
const target = readToolStringArg(args, "channelId") ?? readToolStringArg(args, "channel_id") ?? readToolStringArg(args, "to");
|
|
910
|
+
if (!target) return messageChannelId;
|
|
911
|
+
try {
|
|
912
|
+
return resolveDiscordChannelId(target);
|
|
913
|
+
} catch {
|
|
914
|
+
return (await resolveDiscordTargetChannelId(target, {
|
|
915
|
+
cfg,
|
|
916
|
+
token,
|
|
917
|
+
accountId
|
|
918
|
+
})).channelId;
|
|
919
|
+
}
|
|
920
|
+
};
|
|
921
|
+
const maybeBindStatusReactionsToToolReaction = async (payload) => {
|
|
922
|
+
if (sourceRepliesAreToolOnly || cfg.messages?.statusReactions?.enabled === false || payload.phase !== "start" || payload.name !== "message" || !payload.args) return;
|
|
923
|
+
const args = payload.args;
|
|
924
|
+
if (readToolStringArg(args, "action")?.toLowerCase() !== "react") return;
|
|
925
|
+
if (!(readToolBooleanArg(args, "trackToolCalls") || readToolBooleanArg(args, "track_tool_calls"))) return;
|
|
926
|
+
const emoji = readToolStringArg(args, "emoji");
|
|
927
|
+
const remove = readToolBooleanArg(args, "remove");
|
|
928
|
+
if (!emoji || remove) return;
|
|
929
|
+
const trackedMessageId = readToolStringArg(args, "messageId") ?? readToolStringArg(args, "message_id") ?? message.id;
|
|
930
|
+
let trackedChannelId;
|
|
931
|
+
try {
|
|
932
|
+
trackedChannelId = await resolveTrackedReactionChannelId(args);
|
|
933
|
+
} catch (err) {
|
|
934
|
+
logAckFailure({
|
|
935
|
+
log: logVerbose,
|
|
936
|
+
channel: "discord",
|
|
937
|
+
target: `${readToolStringArg(args, "to") ?? readToolStringArg(args, "channelId") ?? messageChannelId}/${trackedMessageId}`,
|
|
938
|
+
error: err
|
|
939
|
+
});
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
statusReactionTarget = `${trackedChannelId}/${trackedMessageId}`;
|
|
943
|
+
if (statusReactionsActive) statusReactions.clear();
|
|
944
|
+
statusReactions = createStatusReactionController({
|
|
945
|
+
enabled: true,
|
|
946
|
+
adapter: createDiscordAckReactionAdapter({
|
|
947
|
+
channelId: trackedChannelId,
|
|
948
|
+
messageId: trackedMessageId,
|
|
949
|
+
reactionContext: ackReactionContext
|
|
950
|
+
}),
|
|
951
|
+
initialEmoji: emoji,
|
|
952
|
+
emojis: cfg.messages?.statusReactions?.emojis,
|
|
953
|
+
timing: cfg.messages?.statusReactions?.timing,
|
|
954
|
+
onError: (err) => {
|
|
955
|
+
logAckFailure({
|
|
956
|
+
log: logVerbose,
|
|
957
|
+
channel: "discord",
|
|
958
|
+
target: statusReactionTarget,
|
|
959
|
+
error: err
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
});
|
|
963
|
+
statusReactionsActive = true;
|
|
964
|
+
statusReactions.setQueued();
|
|
965
|
+
};
|
|
966
|
+
queueInitialDiscordAckReaction({
|
|
967
|
+
enabled: statusReactionsEnabled,
|
|
968
|
+
shouldSendAckReaction,
|
|
969
|
+
ackReaction,
|
|
970
|
+
statusReactions,
|
|
971
|
+
reactionAdapter: discordAdapter,
|
|
972
|
+
target: `${messageChannelId}/${message.id}`
|
|
973
|
+
});
|
|
974
|
+
const processContext = await buildDiscordMessageProcessContext({
|
|
975
|
+
ctx,
|
|
976
|
+
text,
|
|
977
|
+
mediaList
|
|
978
|
+
});
|
|
979
|
+
if (!processContext) return;
|
|
980
|
+
const { ctxPayload, persistedSessionKey, turn, replyPlan, deliverTarget, replyTarget, replyReference } = processContext;
|
|
981
|
+
observer?.onReplyPlanResolved?.({
|
|
982
|
+
createdThreadId: replyPlan.createdThreadId,
|
|
983
|
+
sessionKey: persistedSessionKey
|
|
984
|
+
});
|
|
985
|
+
const typingChannelId = deliverTarget.startsWith("channel:") ? deliverTarget.slice(8) : messageChannelId;
|
|
986
|
+
const { onModelSelected, ...replyPipeline } = createChannelMessageReplyPipeline({
|
|
987
|
+
cfg,
|
|
988
|
+
agentId: route.agentId,
|
|
989
|
+
channel: "discord",
|
|
990
|
+
accountId: route.accountId,
|
|
991
|
+
typing: {
|
|
992
|
+
start: () => sendTyping({
|
|
993
|
+
rest: feedbackRest,
|
|
994
|
+
channelId: typingChannelId
|
|
995
|
+
}),
|
|
996
|
+
onStartError: (err) => {
|
|
997
|
+
logTypingFailure({
|
|
998
|
+
log: logVerbose,
|
|
999
|
+
channel: "discord",
|
|
1000
|
+
target: typingChannelId,
|
|
1001
|
+
error: err
|
|
1002
|
+
});
|
|
1003
|
+
},
|
|
1004
|
+
maxDurationMs: DISCORD_TYPING_MAX_DURATION_MS
|
|
1005
|
+
}
|
|
1006
|
+
});
|
|
1007
|
+
const tableMode = resolveMarkdownTableMode({
|
|
1008
|
+
cfg,
|
|
1009
|
+
channel: "discord",
|
|
1010
|
+
accountId
|
|
1011
|
+
});
|
|
1012
|
+
const maxLinesPerMessage = resolveDiscordMaxLinesPerMessage({
|
|
1013
|
+
cfg,
|
|
1014
|
+
discordConfig,
|
|
1015
|
+
accountId
|
|
1016
|
+
});
|
|
1017
|
+
const chunkMode = resolveChunkMode(cfg, "discord", accountId);
|
|
1018
|
+
const clearGroupHistory = () => {
|
|
1019
|
+
if (isDirectMessage) return;
|
|
1020
|
+
createChannelHistoryWindow({ historyMap: guildHistories }).clear({
|
|
1021
|
+
historyKey: messageChannelId,
|
|
1022
|
+
limit: historyLimit
|
|
1023
|
+
});
|
|
1024
|
+
};
|
|
1025
|
+
const beginDeliveryCorrelation = () => isRoomEvent ? beginDiscordInboundEventDeliveryCorrelation(ctxPayload.SessionKey, {
|
|
1026
|
+
outboundTo: messageChannelId,
|
|
1027
|
+
outboundAccountId: route.accountId,
|
|
1028
|
+
markInboundEventDelivered: clearGroupHistory
|
|
1029
|
+
}, { inboundEventKind: ctxPayload.InboundEventKind }) : () => {};
|
|
1030
|
+
const endDiscordInboundEventDeliveryCorrelation = beginDeliveryCorrelation();
|
|
1031
|
+
const resolveCurrentTurnTranscriptFinalText = async () => {
|
|
1032
|
+
const sessionKey = ctxPayload.SessionKey;
|
|
1033
|
+
if (!sessionKey) return;
|
|
1034
|
+
try {
|
|
1035
|
+
const storePath = resolveStorePath(cfg.session?.store, { agentId: route.agentId });
|
|
1036
|
+
const store = loadSessionStore(storePath, { clone: false });
|
|
1037
|
+
const sessionEntry = resolveSessionStoreEntry({
|
|
1038
|
+
store,
|
|
1039
|
+
sessionKey
|
|
1040
|
+
}).existing;
|
|
1041
|
+
if (!sessionEntry?.sessionId) return;
|
|
1042
|
+
const { sessionFile } = await resolveAndPersistSessionFile({
|
|
1043
|
+
sessionId: sessionEntry.sessionId,
|
|
1044
|
+
sessionKey,
|
|
1045
|
+
sessionStore: store,
|
|
1046
|
+
storePath,
|
|
1047
|
+
sessionEntry,
|
|
1048
|
+
agentId: route.agentId,
|
|
1049
|
+
sessionsDir: path.dirname(storePath)
|
|
1050
|
+
});
|
|
1051
|
+
const latest = await readLatestAssistantTextFromSessionTranscript(sessionFile);
|
|
1052
|
+
if (!latest?.timestamp || latest.timestamp < dispatchStartedAt) return;
|
|
1053
|
+
return latest.text;
|
|
1054
|
+
} catch (err) {
|
|
1055
|
+
logVerbose(`discord transcript final candidate lookup failed: ${String(err)}`);
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
};
|
|
1059
|
+
const deliverChannelId = deliverTarget.startsWith("channel:") ? deliverTarget.slice(8) : messageChannelId;
|
|
1060
|
+
const draftPreview = createDiscordDraftPreviewController({
|
|
1061
|
+
cfg,
|
|
1062
|
+
discordConfig,
|
|
1063
|
+
accountId,
|
|
1064
|
+
sourceRepliesAreToolOnly,
|
|
1065
|
+
textLimit,
|
|
1066
|
+
deliveryRest,
|
|
1067
|
+
deliverChannelId,
|
|
1068
|
+
replyReference,
|
|
1069
|
+
tableMode,
|
|
1070
|
+
maxLinesPerMessage,
|
|
1071
|
+
chunkMode,
|
|
1072
|
+
log: logVerbose
|
|
1073
|
+
});
|
|
1074
|
+
const finalPreviewFlags = discordConfig?.suppressEmbeds ?? true ? MessageFlags.SuppressEmbeds : void 0;
|
|
1075
|
+
let finalReplyStartNotified = false;
|
|
1076
|
+
const notifyFinalReplyStart = () => {
|
|
1077
|
+
if (finalReplyStartNotified) return;
|
|
1078
|
+
finalReplyStartNotified = true;
|
|
1079
|
+
observer?.onFinalReplyStart?.();
|
|
1080
|
+
};
|
|
1081
|
+
const { dispatcher, replyOptions, markDispatchIdle, markRunComplete } = createReplyDispatcherWithTyping({
|
|
1082
|
+
...replyPipeline,
|
|
1083
|
+
humanDelay: resolveHumanDelayConfig(cfg, route.agentId),
|
|
1084
|
+
deliver: async (payload, info) => {
|
|
1085
|
+
if (isProcessAborted(abortSignal)) return;
|
|
1086
|
+
const isFinal = info.kind === "final";
|
|
1087
|
+
if (payload.isReasoning) return;
|
|
1088
|
+
const finalText = isFinal && typeof payload.text === "string" ? await resolveTranscriptBackedChannelFinalText({
|
|
1089
|
+
finalText: payload.text,
|
|
1090
|
+
resolveCandidateText: resolveCurrentTurnTranscriptFinalText
|
|
1091
|
+
}) : payload.text;
|
|
1092
|
+
const effectivePayload = finalText !== payload.text ? {
|
|
1093
|
+
...payload,
|
|
1094
|
+
text: finalText
|
|
1095
|
+
} : payload;
|
|
1096
|
+
const draftStream = draftPreview.draftStream;
|
|
1097
|
+
if (draftStream && draftPreview.isProgressMode && info.kind === "block") {
|
|
1098
|
+
if (!resolveSendableOutboundReplyParts(effectivePayload).hasMedia && !payload.isError) return;
|
|
1099
|
+
}
|
|
1100
|
+
if (draftStream && isFinal && (!draftPreview.isProgressMode || draftPreview.hasProgressDraftStarted)) {
|
|
1101
|
+
const hasMedia = resolveSendableOutboundReplyParts(effectivePayload).hasMedia;
|
|
1102
|
+
const ttsSupplement = getReplyPayloadTtsSupplement(effectivePayload);
|
|
1103
|
+
const previewSourceText = finalText ?? ttsSupplement?.spokenText;
|
|
1104
|
+
const previewFinalText = draftPreview.resolvePreviewFinalText(previewSourceText);
|
|
1105
|
+
const previewReplyToId = replyReference.peek();
|
|
1106
|
+
const hasExplicitReplyDirective = Boolean(effectivePayload.replyToTag || effectivePayload.replyToCurrent) || typeof previewSourceText === "string" && /\[\[\s*reply_to(?:_current|\s*:)/i.test(previewSourceText);
|
|
1107
|
+
if ((await deliverWithFinalizableLivePreviewAdapter({
|
|
1108
|
+
kind: info.kind,
|
|
1109
|
+
payload: effectivePayload,
|
|
1110
|
+
adapter: defineFinalizableLivePreviewAdapter({
|
|
1111
|
+
draft: {
|
|
1112
|
+
flush: () => draftPreview.flush(),
|
|
1113
|
+
clear: () => draftStream.clear(),
|
|
1114
|
+
discardPending: () => draftStream.discardPending(),
|
|
1115
|
+
seal: () => draftStream.seal(),
|
|
1116
|
+
id: draftStream.messageId
|
|
1117
|
+
},
|
|
1118
|
+
buildFinalEdit: () => {
|
|
1119
|
+
if (draftPreview.finalizedViaPreviewMessage || hasMedia && !ttsSupplement || typeof previewFinalText !== "string" || hasExplicitReplyDirective || payload.isError) return;
|
|
1120
|
+
return {
|
|
1121
|
+
content: previewFinalText,
|
|
1122
|
+
...finalPreviewFlags ? { flags: finalPreviewFlags } : {}
|
|
1123
|
+
};
|
|
1124
|
+
},
|
|
1125
|
+
editFinal: async (previewMessageId, edit) => {
|
|
1126
|
+
if (isProcessAborted(abortSignal)) throw new Error("process aborted");
|
|
1127
|
+
notifyFinalReplyStart();
|
|
1128
|
+
await editMessageDiscord(deliverChannelId, previewMessageId, edit, {
|
|
1129
|
+
cfg,
|
|
1130
|
+
accountId,
|
|
1131
|
+
rest: deliveryRest
|
|
1132
|
+
});
|
|
1133
|
+
},
|
|
1134
|
+
onPreviewFinalized: () => {
|
|
1135
|
+
draftPreview.markFinalReplyDelivered();
|
|
1136
|
+
draftPreview.markPreviewFinalized();
|
|
1137
|
+
replyReference.markSent();
|
|
1138
|
+
observer?.onFinalReplyDelivered?.();
|
|
1139
|
+
},
|
|
1140
|
+
buildSupplementalPayload: () => ttsSupplement ? buildTtsSupplementMediaPayload(effectivePayload) : void 0,
|
|
1141
|
+
deliverSupplemental: async (supplementalPayload) => {
|
|
1142
|
+
if (isProcessAborted(abortSignal)) return false;
|
|
1143
|
+
const supplementalReplyToId = previewReplyToId ?? replyReference.peek() ?? (replyToMode === "all" ? typeof message.id === "string" && message.id ? message.id : ctxPayload.MessageSid : void 0);
|
|
1144
|
+
await deliverDiscordReply({
|
|
1145
|
+
cfg,
|
|
1146
|
+
replies: [supplementalPayload],
|
|
1147
|
+
target: deliverTarget,
|
|
1148
|
+
token,
|
|
1149
|
+
accountId,
|
|
1150
|
+
rest: deliveryRest,
|
|
1151
|
+
runtime,
|
|
1152
|
+
replyToId: supplementalReplyToId,
|
|
1153
|
+
replyToMode,
|
|
1154
|
+
textLimit,
|
|
1155
|
+
maxLinesPerMessage,
|
|
1156
|
+
tableMode,
|
|
1157
|
+
chunkMode,
|
|
1158
|
+
sessionKey: ctxPayload.SessionKey,
|
|
1159
|
+
threadBindings,
|
|
1160
|
+
mediaLocalRoots,
|
|
1161
|
+
kind: info.kind
|
|
1162
|
+
});
|
|
1163
|
+
return true;
|
|
1164
|
+
},
|
|
1165
|
+
logPreviewEditFailure: (err) => {
|
|
1166
|
+
logVerbose(`discord: preview final edit failed; falling back to standard send (${String(err)})`);
|
|
1167
|
+
}
|
|
1168
|
+
}),
|
|
1169
|
+
deliverNormally: async () => {
|
|
1170
|
+
if (isProcessAborted(abortSignal)) return false;
|
|
1171
|
+
const fallbackPayload = ttsSupplement && ttsSupplement.visibleTextAlreadyDelivered !== true && !effectivePayload.text?.trim() ? {
|
|
1172
|
+
...effectivePayload,
|
|
1173
|
+
text: ttsSupplement.spokenText
|
|
1174
|
+
} : effectivePayload;
|
|
1175
|
+
const replyToId = replyReference.use();
|
|
1176
|
+
notifyFinalReplyStart();
|
|
1177
|
+
await deliverDiscordReply({
|
|
1178
|
+
cfg,
|
|
1179
|
+
replies: [fallbackPayload],
|
|
1180
|
+
target: deliverTarget,
|
|
1181
|
+
token,
|
|
1182
|
+
accountId,
|
|
1183
|
+
rest: deliveryRest,
|
|
1184
|
+
runtime,
|
|
1185
|
+
replyToId,
|
|
1186
|
+
replyToMode,
|
|
1187
|
+
textLimit,
|
|
1188
|
+
maxLinesPerMessage,
|
|
1189
|
+
tableMode,
|
|
1190
|
+
chunkMode,
|
|
1191
|
+
sessionKey: ctxPayload.SessionKey,
|
|
1192
|
+
threadBindings,
|
|
1193
|
+
mediaLocalRoots,
|
|
1194
|
+
kind: info.kind
|
|
1195
|
+
});
|
|
1196
|
+
return true;
|
|
1197
|
+
},
|
|
1198
|
+
onNormalDelivered: () => {
|
|
1199
|
+
draftPreview.markFinalReplyDelivered();
|
|
1200
|
+
replyReference.markSent();
|
|
1201
|
+
observer?.onFinalReplyDelivered?.();
|
|
1202
|
+
}
|
|
1203
|
+
})).kind !== "normal-skipped") return;
|
|
1204
|
+
}
|
|
1205
|
+
if (isProcessAborted(abortSignal)) return;
|
|
1206
|
+
const replyToId = replyReference.use();
|
|
1207
|
+
if (isFinal) notifyFinalReplyStart();
|
|
1208
|
+
await deliverDiscordReply({
|
|
1209
|
+
cfg,
|
|
1210
|
+
replies: [effectivePayload],
|
|
1211
|
+
target: deliverTarget,
|
|
1212
|
+
token,
|
|
1213
|
+
accountId,
|
|
1214
|
+
rest: deliveryRest,
|
|
1215
|
+
runtime,
|
|
1216
|
+
replyToId,
|
|
1217
|
+
replyToMode,
|
|
1218
|
+
textLimit,
|
|
1219
|
+
maxLinesPerMessage,
|
|
1220
|
+
tableMode,
|
|
1221
|
+
chunkMode,
|
|
1222
|
+
sessionKey: ctxPayload.SessionKey,
|
|
1223
|
+
threadBindings,
|
|
1224
|
+
mediaLocalRoots,
|
|
1225
|
+
kind: info.kind
|
|
1226
|
+
});
|
|
1227
|
+
replyReference.markSent();
|
|
1228
|
+
if (isFinal) observer?.onFinalReplyDelivered?.();
|
|
1229
|
+
},
|
|
1230
|
+
onError: (err, info) => {
|
|
1231
|
+
runtime.error?.(danger(formatDiscordReplyDeliveryFailure({
|
|
1232
|
+
kind: info.kind,
|
|
1233
|
+
err,
|
|
1234
|
+
target: deliverTarget,
|
|
1235
|
+
sessionKey: ctxPayload.SessionKey
|
|
1236
|
+
})));
|
|
1237
|
+
},
|
|
1238
|
+
onReplyStart: async () => {
|
|
1239
|
+
if (isProcessAborted(abortSignal)) return;
|
|
1240
|
+
await replyPipeline.typingCallbacks?.onReplyStart();
|
|
1241
|
+
await statusReactions.setThinking();
|
|
1242
|
+
}
|
|
1243
|
+
});
|
|
1244
|
+
const resolvedBlockStreamingEnabled = resolveChannelStreamingBlockEnabled(discordConfig);
|
|
1245
|
+
let dispatchResult = null;
|
|
1246
|
+
let dispatchError = false;
|
|
1247
|
+
let dispatchAborted = false;
|
|
1248
|
+
let dispatchSettledBeforeStart = false;
|
|
1249
|
+
const settleDispatchBeforeStart = async () => {
|
|
1250
|
+
dispatchSettledBeforeStart = true;
|
|
1251
|
+
await settleReplyDispatcher({
|
|
1252
|
+
dispatcher,
|
|
1253
|
+
onSettled: () => {
|
|
1254
|
+
markRunComplete();
|
|
1255
|
+
markDispatchIdle();
|
|
1256
|
+
}
|
|
1257
|
+
});
|
|
1258
|
+
};
|
|
1259
|
+
try {
|
|
1260
|
+
if (isProcessAborted(abortSignal)) {
|
|
1261
|
+
dispatchAborted = true;
|
|
1262
|
+
await settleDispatchBeforeStart();
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
const preparedResult = await runPreparedInboundReplyTurn({
|
|
1266
|
+
channel: "discord",
|
|
1267
|
+
accountId: route.accountId,
|
|
1268
|
+
routeSessionKey: persistedSessionKey,
|
|
1269
|
+
storePath: turn.storePath,
|
|
1270
|
+
ctxPayload,
|
|
1271
|
+
recordInboundSession,
|
|
1272
|
+
record: turn.record,
|
|
1273
|
+
history: isRoomEvent ? void 0 : {
|
|
1274
|
+
isGroup: isGuildMessage,
|
|
1275
|
+
historyKey: messageChannelId,
|
|
1276
|
+
historyMap: guildHistories,
|
|
1277
|
+
limit: historyLimit
|
|
1278
|
+
},
|
|
1279
|
+
onPreDispatchFailure: settleDispatchBeforeStart,
|
|
1280
|
+
runDispatch: async () => await dispatchInboundMessage({
|
|
1281
|
+
ctx: ctxPayload,
|
|
1282
|
+
cfg,
|
|
1283
|
+
dispatcher,
|
|
1284
|
+
replyOptions: {
|
|
1285
|
+
...replyOptions,
|
|
1286
|
+
abortSignal,
|
|
1287
|
+
skillFilter: channelConfig?.skills,
|
|
1288
|
+
sourceReplyDeliveryMode,
|
|
1289
|
+
queuedDeliveryCorrelations: isRoomEvent ? [{ begin: beginDeliveryCorrelation }] : void 0,
|
|
1290
|
+
suppressTyping: isRoomEvent ? true : void 0,
|
|
1291
|
+
allowProgressCallbacksWhenSourceDeliverySuppressed: sourceRepliesAreToolOnly && draftPreview.draftStream && draftPreview.isProgressMode ? true : void 0,
|
|
1292
|
+
disableBlockStreaming: sourceRepliesAreToolOnly ? true : draftPreview.disableBlockStreamingForDraft ?? (typeof resolvedBlockStreamingEnabled === "boolean" ? !resolvedBlockStreamingEnabled : void 0),
|
|
1293
|
+
onPartialReply: draftPreview.draftStream && !draftPreview.isProgressMode ? (payload) => draftPreview.updateFromPartial(payload.text) : void 0,
|
|
1294
|
+
onAssistantMessageStart: draftPreview.draftStream ? () => draftPreview.handleAssistantMessageBoundary() : void 0,
|
|
1295
|
+
onReasoningEnd: draftPreview.draftStream ? () => draftPreview.handleAssistantMessageBoundary() : void 0,
|
|
1296
|
+
onModelSelected,
|
|
1297
|
+
suppressDefaultToolProgressMessages: draftPreview.suppressDefaultToolProgressMessages ? true : void 0,
|
|
1298
|
+
onReasoningStream: async (payload) => {
|
|
1299
|
+
await statusReactions.setThinking();
|
|
1300
|
+
const formattedText = payload?.text ? formatReasoningMessage(payload.text) : void 0;
|
|
1301
|
+
await draftPreview.pushReasoningProgress(formattedText);
|
|
1302
|
+
},
|
|
1303
|
+
onToolStart: async (payload) => {
|
|
1304
|
+
if (isProcessAborted(abortSignal)) return;
|
|
1305
|
+
await maybeBindStatusReactionsToToolReaction(payload);
|
|
1306
|
+
await statusReactions.setTool(payload.name);
|
|
1307
|
+
await draftPreview.pushToolProgress(buildChannelProgressDraftLineForEntry(discordConfig, {
|
|
1308
|
+
event: "tool",
|
|
1309
|
+
name: payload.name,
|
|
1310
|
+
phase: payload.phase,
|
|
1311
|
+
args: payload.args
|
|
1312
|
+
}, payload.detailMode ? { detailMode: payload.detailMode } : void 0), { toolName: payload.name });
|
|
1313
|
+
},
|
|
1314
|
+
onItemEvent: async (payload) => {
|
|
1315
|
+
await draftPreview.pushToolProgress(buildChannelProgressDraftLineForEntry(discordConfig, {
|
|
1316
|
+
event: "item",
|
|
1317
|
+
itemId: payload.itemId,
|
|
1318
|
+
itemKind: payload.kind,
|
|
1319
|
+
title: payload.title,
|
|
1320
|
+
name: payload.name,
|
|
1321
|
+
phase: payload.phase,
|
|
1322
|
+
status: payload.status,
|
|
1323
|
+
summary: payload.summary,
|
|
1324
|
+
progressText: payload.progressText,
|
|
1325
|
+
meta: payload.meta
|
|
1326
|
+
}));
|
|
1327
|
+
},
|
|
1328
|
+
onPlanUpdate: async (payload) => {
|
|
1329
|
+
if (payload.phase !== "update") return;
|
|
1330
|
+
await draftPreview.pushToolProgress(buildChannelProgressDraftLine({
|
|
1331
|
+
event: "plan",
|
|
1332
|
+
phase: payload.phase,
|
|
1333
|
+
title: payload.title,
|
|
1334
|
+
explanation: payload.explanation,
|
|
1335
|
+
steps: payload.steps
|
|
1336
|
+
}));
|
|
1337
|
+
},
|
|
1338
|
+
onApprovalEvent: async (payload) => {
|
|
1339
|
+
if (payload.phase !== "requested") return;
|
|
1340
|
+
await draftPreview.pushToolProgress(buildChannelProgressDraftLine({
|
|
1341
|
+
event: "approval",
|
|
1342
|
+
phase: payload.phase,
|
|
1343
|
+
title: payload.title,
|
|
1344
|
+
command: payload.command,
|
|
1345
|
+
reason: payload.reason,
|
|
1346
|
+
message: payload.message
|
|
1347
|
+
}));
|
|
1348
|
+
},
|
|
1349
|
+
onCommandOutput: async (payload) => {
|
|
1350
|
+
if (payload.phase !== "end") return;
|
|
1351
|
+
await draftPreview.pushToolProgress(buildChannelProgressDraftLine({
|
|
1352
|
+
event: "command-output",
|
|
1353
|
+
phase: payload.phase,
|
|
1354
|
+
title: payload.title,
|
|
1355
|
+
name: payload.name,
|
|
1356
|
+
status: payload.status,
|
|
1357
|
+
exitCode: payload.exitCode
|
|
1358
|
+
}));
|
|
1359
|
+
},
|
|
1360
|
+
onPatchSummary: async (payload) => {
|
|
1361
|
+
if (payload.phase !== "end") return;
|
|
1362
|
+
await draftPreview.pushToolProgress(buildChannelProgressDraftLine({
|
|
1363
|
+
event: "patch",
|
|
1364
|
+
phase: payload.phase,
|
|
1365
|
+
title: payload.title,
|
|
1366
|
+
name: payload.name,
|
|
1367
|
+
added: payload.added,
|
|
1368
|
+
modified: payload.modified,
|
|
1369
|
+
deleted: payload.deleted,
|
|
1370
|
+
summary: payload.summary
|
|
1371
|
+
}));
|
|
1372
|
+
},
|
|
1373
|
+
onCompactionStart: async () => {
|
|
1374
|
+
if (isProcessAborted(abortSignal)) return;
|
|
1375
|
+
await statusReactions.setCompacting();
|
|
1376
|
+
},
|
|
1377
|
+
onCompactionEnd: async () => {
|
|
1378
|
+
if (isProcessAborted(abortSignal)) return;
|
|
1379
|
+
statusReactions.cancelPending();
|
|
1380
|
+
await statusReactions.setThinking();
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
})
|
|
1384
|
+
});
|
|
1385
|
+
if (!preparedResult.dispatched) return;
|
|
1386
|
+
dispatchResult = preparedResult.dispatchResult;
|
|
1387
|
+
if (isProcessAborted(abortSignal)) {
|
|
1388
|
+
dispatchAborted = true;
|
|
1389
|
+
return;
|
|
1390
|
+
}
|
|
1391
|
+
} catch (err) {
|
|
1392
|
+
if (isProcessAborted(abortSignal)) {
|
|
1393
|
+
dispatchAborted = true;
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1396
|
+
dispatchError = true;
|
|
1397
|
+
throw err;
|
|
1398
|
+
} finally {
|
|
1399
|
+
endDiscordInboundEventDeliveryCorrelation();
|
|
1400
|
+
try {
|
|
1401
|
+
await draftPreview.cleanup();
|
|
1402
|
+
} finally {
|
|
1403
|
+
if (!dispatchSettledBeforeStart) {
|
|
1404
|
+
markRunComplete();
|
|
1405
|
+
markDispatchIdle();
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
const finalDeliveryFailed = (dispatchResult?.failedCounts?.final ?? 0) > 0;
|
|
1409
|
+
if (statusReactionsActive) if (dispatchAborted) if (removeAckAfterReply) statusReactions.clear();
|
|
1410
|
+
else statusReactions.restoreInitial();
|
|
1411
|
+
else {
|
|
1412
|
+
if (dispatchError || finalDeliveryFailed) await statusReactions.setError();
|
|
1413
|
+
else await statusReactions.setDone();
|
|
1414
|
+
if (removeAckAfterReply) (async () => {
|
|
1415
|
+
await sleep(dispatchError || finalDeliveryFailed ? DEFAULT_TIMING.errorHoldMs : DEFAULT_TIMING.doneHoldMs);
|
|
1416
|
+
await statusReactions.clear();
|
|
1417
|
+
})();
|
|
1418
|
+
else statusReactions.restoreInitial();
|
|
1419
|
+
}
|
|
1420
|
+
else if (shouldSendAckReaction && ackReaction && removeAckAfterReply) removeReactionDiscord(messageChannelId, message.id, ackReaction, ackReactionContext).catch((err) => {
|
|
1421
|
+
logAckFailure({
|
|
1422
|
+
log: logVerbose,
|
|
1423
|
+
channel: "discord",
|
|
1424
|
+
target: `${messageChannelId}/${message.id}`,
|
|
1425
|
+
error: err
|
|
1426
|
+
});
|
|
1427
|
+
});
|
|
1428
|
+
}
|
|
1429
|
+
if (dispatchAborted) return;
|
|
1430
|
+
const finalDispatchResult = dispatchResult;
|
|
1431
|
+
if (!finalDispatchResult || !hasFinalInboundReplyDispatch(finalDispatchResult)) return;
|
|
1432
|
+
if (shouldLogVerbose()) {
|
|
1433
|
+
const finalCount = finalDispatchResult.counts.final;
|
|
1434
|
+
logVerbose(`discord: delivered ${finalCount} reply${finalCount === 1 ? "" : "ies"} to ${replyTarget}`);
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
//#endregion
|
|
1438
|
+
export { processDiscordMessage };
|