@openclaw/discord 2026.7.2-beta.7 → 2026.8.1-beta.2

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.
Files changed (95) hide show
  1. package/dist/{account-inspect-SXiCBCgP.js → account-inspect-CqzrH14k.js} +48 -42
  2. package/dist/account-inspect-api.js +1 -1
  3. package/dist/action-runtime-api.js +1 -1
  4. package/dist/{active-turn-thread-route-MlKWp8RD.js → active-turn-thread-route-fmHl1HNd.js} +9 -11
  5. package/dist/{activities-api-DTKsuDxf.js → activities-api-DS7vpdtp.js} +4 -4
  6. package/dist/activities-api.js +1 -1
  7. package/dist/{allow-list-DzKrtfah.js → allow-list-DnxIlPP2.js} +2 -3
  8. package/dist/{api-DnrK9gML.js → api-DNVbJyEy.js} +7 -2
  9. package/dist/api.js +15 -15
  10. package/dist/{approval-handler.runtime-HymvFTYn.js → approval-handler.runtime-B6yEV79q.js} +5 -5
  11. package/dist/{approval-message-safety-DEyKVsAw.js → approval-message-safety-AcIxvrf3.js} +1 -1
  12. package/dist/{audit-CjsrabP8.js → audit-Db-rlntl.js} +4 -4
  13. package/dist/{channel-vKa-76xY.js → channel-CnnbJSt5.js} +46 -72
  14. package/dist/{channel-actions-uXFTJX3o.js → channel-actions-5s_hjYKd.js} +4 -4
  15. package/dist/{channel-actions.runtime-CIYU90sl.js → channel-actions.runtime-CLKrx6tr.js} +53 -36
  16. package/dist/channel-config-api.js +1 -1
  17. package/dist/channel-plugin-api.js +1 -1
  18. package/dist/{channel.setup-CmcwZ_qd.js → channel.setup-BFAO-V1P.js} +15 -53
  19. package/dist/{config-schema-WHRQcY19.js → config-schema-BgEskFAs.js} +9 -4
  20. package/dist/{directory-config-D_LojtCe.js → directory-config-BVX7Cflc.js} +1 -1
  21. package/dist/directory-contract-api.js +1 -1
  22. package/dist/{directory-live-Bw5Ephzv.js → directory-live-DGIGxpIo.js} +6 -3
  23. package/dist/{doctor-Bc4S1qyd.js → doctor-BzC-TLnB.js} +4 -4
  24. package/dist/{doctor-contract-CCFMIDk9.js → doctor-contract-DLa-BTiU.js} +2 -3
  25. package/dist/doctor-contract-api.js +147 -2
  26. package/dist/{handle-action.guild-admin-D6VRRW-C.js → handle-action.guild-admin-BNVTUISF.js} +4 -7
  27. package/dist/{inbound-event-delivery-CEPlt2uz.js → inbound-event-delivery-De2qxTrP.js} +4 -25
  28. package/dist/index.js +4 -2
  29. package/dist/{message-handler-CeiSulIw.js → message-handler-Unk_f10F.js} +41 -32
  30. package/dist/{message-handler.preflight-DibgQFf2.js → message-handler.preflight-C9zecxR9.js} +50 -26
  31. package/dist/{message-handler.process-Dnc_oz9y.js → message-handler.process-DWQ6UHHu.js} +130 -166
  32. package/dist/{message-utils-BgTwT_q7.js → message-utils-BSCPVgy0.js} +4 -4
  33. package/dist/{model-picker-preference-primitives-D25pnCOu.js → model-picker-preference-primitives-BHT1LkR_.js} +3 -3
  34. package/dist/{outbound-adapter-DtE-i2OJ.js → outbound-adapter-D9nRivT1.js} +91 -32
  35. package/dist/{outbound-components-C7GVyfPh.js → outbound-components-cTzwBJ60.js} +3 -3
  36. package/dist/{outbound-session-route-H-HoyH5U.js → outbound-session-route-DxLkrgKQ.js} +68 -95
  37. package/dist/{pluralkit-CD4B11JB.js → pluralkit-DdiJ9KMK.js} +1 -1
  38. package/dist/{preflight-audio-BjnQZ5yS.js → preflight-audio-DrnqFA8U.js} +22 -28
  39. package/dist/{probe-BeWKhjqX.js → probe-Bl-k5_Ku.js} +36 -12
  40. package/dist/probe.runtime-M9fREKvp.js +2 -0
  41. package/dist/{provider-DfQJX3z5.js → provider-DvSNb-2v.js} +969 -1248
  42. package/dist/{provider-session.runtime-Bo88k0pV.js → provider-session.runtime-BUEqD0rA.js} +3 -3
  43. package/dist/provider.runtime-C9PnJZbZ.js +2 -0
  44. package/dist/realtime-session.runtime-C7wen-aF.js +1340 -0
  45. package/dist/{resolve-allowlist-common-DHERApxi.js → resolve-allowlist-common-D04lpEzb.js} +2 -2
  46. package/dist/{resolve-channels-BjepAj8b.js → resolve-channels-Dm7jEeg6.js} +4 -4
  47. package/dist/{resolve-users-Bc28htgt.js → resolve-users-bktrCUUU.js} +3 -3
  48. package/dist/{chunk-DX4jpFQP.js → retry-CLpJcXXf.js} +291 -68
  49. package/dist/{runtime-_aRC6V2s.js → runtime-DGsSyDTS.js} +30 -25
  50. package/dist/{runtime-BmW_j_hl.js → runtime-DoHjckYm.js} +4 -6
  51. package/dist/runtime-api.actions.js +2 -2
  52. package/dist/runtime-api.js +22 -23
  53. package/dist/runtime-api.lookup.js +5 -5
  54. package/dist/runtime-api.monitor-CHxrFPnW.js +5 -0
  55. package/dist/runtime-api.monitor.js +5 -5
  56. package/dist/runtime-api.send.js +6 -6
  57. package/dist/runtime-api.threads.js +5 -6
  58. package/dist/sdk-runtime-QpY8PvCh.js +370 -0
  59. package/dist/{send-CdkZ6zeW.js → send-B7Q6Oc1I.js} +175 -86
  60. package/dist/{send.components-5uqwkjgE.js → send.components-iJyFXw9L.js} +24 -28
  61. package/dist/{send.outbound-D-80Ej-t.js → send.outbound-DS5tHwfR.js} +37 -24
  62. package/dist/{send.reactions-CtlmGd_o.js → send.reactions-BAn0iiVQ.js} +14 -21
  63. package/dist/{send.receipt-CuwRrpFz.js → send.receipt-IPI1p-SP.js} +19 -1
  64. package/dist/{send.shared-jpvAG_u6.js → send.shared-BmwFjW6T.js} +16 -85
  65. package/dist/{sender-identity-mG_zsdox.js → sender-identity-QtalnCYv.js} +1 -1
  66. package/dist/setup-entry.js +0 -5
  67. package/dist/setup-plugin-api.js +1 -1
  68. package/dist/{shared-CXF1tup1.js → shared-BIXIX6uJ.js} +16 -17
  69. package/dist/{subagent-hooks-Dxy7Fmnq.js → subagent-hooks-CTt5KCJr.js} +2 -2
  70. package/dist/subagent-hooks-api.js +2 -6
  71. package/dist/subagent-progress-DhaE_h8a.js +109 -0
  72. package/dist/{system-events-DdUqf91B.js → system-events-CTqDf6_s.js} +2 -2
  73. package/dist/{target-resolver-C3s_2m0t.js → target-resolver-maOKSqO0.js} +2 -2
  74. package/dist/targets-yUFTHoAx.js +3 -0
  75. package/dist/{thread-bindings-C-M-ly7P.js → thread-bindings-3L8p82GV.js} +5 -7
  76. package/dist/{thread-bindings.manager-DYvSlrva.js → thread-bindings.manager-DCIH6WDC.js} +257 -29
  77. package/dist/{thread-bindings.session-updates-DAIlp7Px.js → thread-bindings.session-updates-bJ2KQFY6.js} +1 -1
  78. package/dist/{thread-bindings.state-Cgi5uRn-.js → thread-bindings.state-aeV8HNgV.js} +2 -15
  79. package/dist/{transcripts-source-DIKGsTGT.js → transcripts-source-CfFMexGt.js} +1 -1
  80. package/dist/transcripts-source-api.js +1 -1
  81. package/dist/{typing--Mzo2kM7.js → typing-CS-nNVoe.js} +1 -1
  82. package/dist/voice-runtime-BEdqNcST.js +2459 -0
  83. package/openclaw.plugin.json +203 -19
  84. package/package.json +15 -9
  85. package/skills/discord/SKILL.md +22 -115
  86. package/dist/legacy-state-migrations-api.js +0 -138
  87. package/dist/manager.runtime-Bjav3pfC.js +0 -3655
  88. package/dist/preflight-audio.runtime-CoCXMM8r.js +0 -7
  89. package/dist/probe.runtime-DqFGFMbZ.js +0 -2
  90. package/dist/provider.runtime-Cy7XZmyh.js +0 -2
  91. package/dist/runtime-api.monitor-CCRubd3-.js +0 -5
  92. package/dist/session-binding-contract-api.js +0 -2
  93. package/dist/subagent-progress-DI3sYLLo.js +0 -664
  94. package/dist/targets-Dv_RN7vT.js +0 -3
  95. package/dist/thread-bindings.discord-api-BoDNqamb.js +0 -246
@@ -1,38 +1,38 @@
1
- import { C as Client, D as RateLimitError, F as Message, I as User, K as Container, M as Command, N as CommandWithSubcommands, P as Guild, Q as Row, V as Modal, W as Button, Yt as __exportAll, _ as MessageReactionRemoveListener, _t as editChannel, at as BaseMessageInteractiveComponent, bt as getChannelMessage, d as discord_exports, et as Separator, f as GuildCreateListener, g as MessageReactionAddListener, h as MessageCreateListener, m as InteractionCreateListener, mt as createThread, nt as TextDisplay, p as GuildDeleteListener, t as chunkDiscordTextWithMode, tt as StringSelectMenu, v as PresenceUpdateListener, w as Plugin, x as ThreadUpdateListener, y as ReadyListener } from "./chunk-DX4jpFQP.js";
1
+ import { B as Message, C as ReadyListener, Ct as editChannel, E as ThreadUpdateListener, Et as getChannelMessage, K as Modal, L as Command, M as RateLimitError, O as Client, R as CommandWithSubcommands, S as PresenceUpdateListener, T as ThreadDeleteListener, V as User, Y as Button, Z as Container, _ as GuildDeleteListener, a as chunkDiscordTextWithMode, at as Separator, b as MessageReactionAddListener, bt as createThread, g as GuildCreateListener, h as discord_exports, k as Plugin, ot as StringSelectMenu, rt as Row, st as TextDisplay, tn as __exportAll, ut as BaseMessageInteractiveComponent, v as InteractionCreateListener, x as MessageReactionRemoveListener, y as MessageCreateListener, z as Guild } from "./retry-CLpJcXXf.js";
2
2
  import { c as resolveDiscordAccountAllowFrom, d as resolveDiscordAccountDmPolicy, f as resolveDiscordMaxLinesPerMessage, p as normalizeDiscordToken, s as resolveDiscordAccount } from "./accounts-DJTf1LtA.js";
3
- import { n as getDiscordActivitiesRuntime } from "./runtime-BmW_j_hl.js";
4
- import { C as parseDiscordActivityCustomIdForInteraction, D as parseDiscordModalCustomIdForInteraction, E as parseDiscordModalCustomId, O as decodeCustomIdComponent, T as parseDiscordComponentCustomIdForInteraction, k as encodeCustomIdComponent, l as parseExecApprovalData, s as parseDiscordQuestionData, w as parseDiscordComponentCustomId, y as buildDiscordActivityCustomId } from "./outbound-session-route-H-HoyH5U.js";
5
- import { D as canViewDiscordGuildChannel, F as createDiscordRestClient, H as registerGateway, K as validateDiscordProxyUrl, U as unregisterGateway, q as withValidatedDiscordProxy, z as DISCORD_REST_TIMEOUT_MS } from "./send.shared-jpvAG_u6.js";
6
- import { _ as resolveDiscordModalEntryWithPersistence, g as resolveDiscordComponentEntryWithPersistence, t as editDiscordComponentMessage } from "./send.components-5uqwkjgE.js";
3
+ import { n as getDiscordActivitiesRuntime } from "./runtime-DoHjckYm.js";
4
+ import { C as parseDiscordActivityCustomIdForInteraction, D as parseDiscordModalCustomIdForInteraction, E as parseDiscordModalCustomId, O as decodeCustomIdComponent, T as parseDiscordComponentCustomIdForInteraction, k as encodeCustomIdComponent, l as parseExecApprovalData, s as parseDiscordQuestionData, w as parseDiscordComponentCustomId, y as buildDiscordActivityCustomId } from "./outbound-session-route-DxLkrgKQ.js";
5
+ import { D as canViewDiscordGuildChannel, F as createDiscordRestClient, H as registerGateway, K as validateDiscordProxyUrl, U as unregisterGateway, q as withValidatedDiscordProxy, z as DISCORD_REST_TIMEOUT_MS } from "./send.shared-BmwFjW6T.js";
6
+ import { _ as resolveDiscordModalEntryWithPersistence, g as resolveDiscordComponentEntryWithPersistence, t as editDiscordComponentMessage } from "./send.components-iJyFXw9L.js";
7
7
  import { t as getDiscordRuntime } from "./runtime-DgnVQ7zW.js";
8
- import { a as isDiscordRateLimitResponseBody, o as summarizeDiscordResponseBody } from "./api-DnrK9gML.js";
9
- import { C as resolveTimestampMs, a as normalizeDiscordSlug, b as formatDiscordReactionEmoji, c as resolveDiscordChannelConfigWithFallback, d as resolveDiscordCommandOwnerAllowFrom, f as resolveDiscordGuildEntry, g as resolveDiscordOwnerAllowFrom, h as resolveDiscordOwnerAccess, i as normalizeDiscordDisplaySlug, l as resolveDiscordChannelPolicyCommandAuthorizer, m as resolveDiscordMemberAllowed, n as isDiscordGroupAllowedByPolicy, o as resolveDiscordAllowListMatch, p as resolveDiscordMemberAccessState, r as normalizeDiscordAllowList, t as allowListMatches, v as resolveGroupDmAllow, x as formatDiscordUserTag, y as shouldEmitDiscordReactionNotification } from "./allow-list-DzKrtfah.js";
10
- import { a as formatMention, t as sendMessageDiscord } from "./send.outbound-D-80Ej-t.js";
8
+ import { a as isDiscordRateLimitResponseBody, o as summarizeDiscordResponseBody } from "./api-DNVbJyEy.js";
9
+ import { C as resolveTimestampMs, a as normalizeDiscordSlug, b as formatDiscordReactionEmoji, c as resolveDiscordChannelConfigWithFallback, d as resolveDiscordCommandOwnerAllowFrom, f as resolveDiscordGuildEntry, g as resolveDiscordOwnerAllowFrom, h as resolveDiscordOwnerAccess, i as normalizeDiscordDisplaySlug, l as resolveDiscordChannelPolicyCommandAuthorizer, m as resolveDiscordMemberAllowed, n as isDiscordGroupAllowedByPolicy, o as resolveDiscordAllowListMatch, p as resolveDiscordMemberAccessState, r as normalizeDiscordAllowList, t as allowListMatches, v as resolveGroupDmAllow, x as formatDiscordUserTag, y as shouldEmitDiscordReactionNotification } from "./allow-list-DnxIlPP2.js";
10
+ import { a as formatMention, t as sendMessageDiscord } from "./send.outbound-DS5tHwfR.js";
11
11
  import { i as setPresence, t as clearPresences } from "./presence-cache-z1N8MbJ3.js";
12
- import { n as sendVoiceMessageDiscord } from "./send-CdkZ6zeW.js";
12
+ import { r as sendVoiceMessageDiscord } from "./send-B7Q6Oc1I.js";
13
13
  import { c as raceWithTimeout, u as withAbortTimeout } from "./timeouts-B9cy6RvQ.js";
14
- import { a as getDiscordExecApprovalApprovers, n as formatDiscordApprovalDisplayValue, r as resolveDiscordConversationIdentity, s as isDiscordExecApprovalClientEnabled, t as DISCORD_APPROVAL_ALLOWED_MENTIONS } from "./approval-message-safety-DEyKVsAw.js";
15
- import { t as resolveDiscordChannelAllowlist } from "./resolve-channels-BjepAj8b.js";
16
- import { t as resolveDiscordUserAllowlist } from "./resolve-users-Bc28htgt.js";
17
- import { a as isThreadArchived, d as resolveDiscordChannelIdSafe, f as resolveDiscordChannelInfoSafe, g as resolveDiscordChannelTopicSafe, h as resolveDiscordChannelParentSafe, m as resolveDiscordChannelParentIdSafe, p as resolveDiscordChannelNameSafe, y as formatThreadBindingDurationLabel } from "./thread-bindings.discord-api-BoDNqamb.js";
18
- import { r as parseApplicationIdFromToken, t as fetchDiscordApplicationId } from "./probe-BeWKhjqX.js";
19
- import { i as timestampMs, n as normalizeModelRef, r as sanitizeRecentModels, t as buildPreferenceModelKey } from "./model-picker-preference-primitives-D25pnCOu.js";
20
- import { a as formatDiscordMediaText, d as resolveDiscordChannelInfo, f as resolveDiscordMessageChannelId, n as resolveDiscordForwardedMessagesTextFromSnapshots, t as resolveDiscordEmbedText } from "./message-utils-BgTwT_q7.js";
21
- import { t as resolveDiscordSenderIdentity } from "./sender-identity-mG_zsdox.js";
14
+ import { a as getDiscordExecApprovalApprovers, n as formatDiscordApprovalDisplayValue, r as resolveDiscordConversationIdentity, s as isDiscordExecApprovalClientEnabled, t as DISCORD_APPROVAL_ALLOWED_MENTIONS } from "./approval-message-safety-AcIxvrf3.js";
15
+ import { t as resolveDiscordChannelAllowlist } from "./resolve-channels-Dm7jEeg6.js";
16
+ import { t as resolveDiscordUserAllowlist } from "./resolve-users-bktrCUUU.js";
17
+ import { a as isThreadArchived, c as resolveDiscordChannelInfoSafe, d as resolveDiscordChannelParentSafe, f as resolveDiscordChannelTopicSafe, h as formatThreadBindingDurationLabel, l as resolveDiscordChannelNameSafe, r as getThreadBindingManager, s as resolveDiscordChannelIdSafe, u as resolveDiscordChannelParentIdSafe } from "./thread-bindings.manager-DCIH6WDC.js";
18
+ import { a as probeDiscordApplicationId, r as parseApplicationIdFromToken } from "./probe-Bl-k5_Ku.js";
19
+ import { i as sanitizeRecentModels, n as normalizeModelRef, r as preferenceTimestampMs, t as buildPreferenceModelKey } from "./model-picker-preference-primitives-BHT1LkR_.js";
20
+ import { a as formatDiscordMediaText, d as resolveDiscordChannelInfo, f as resolveDiscordMessageChannelId, n as resolveDiscordForwardedMessagesTextFromSnapshots, t as resolveDiscordEmbedText } from "./message-utils-BSCPVgy0.js";
21
+ import { t as resolveDiscordSenderIdentity } from "./sender-identity-QtalnCYv.js";
22
22
  import { normalizeAccountId } from "openclaw/plugin-sdk/account-id";
23
23
  import { buildAgentSessionKey, deriveLastRoutePolicy, isAcpSessionKey, isSubagentSessionKey, parseAgentSessionKey, resolveAgentIdFromSessionKey, resolveAgentRoute } from "openclaw/plugin-sdk/routing";
24
- import { normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeOptionalStringifiedId, normalizeStringEntries, normalizeStringEntriesLower, summarizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
24
+ import { asFiniteNumber, normalizeLowercaseStringOrEmpty, normalizeOptionalLowercaseString, normalizeOptionalString, normalizeOptionalStringifiedId, normalizeStringEntries, parseFiniteNumber, parseStrictNonNegativeInteger, summarizeStringEntries } from "openclaw/plugin-sdk/string-coerce-runtime";
25
25
  import { getRuntimeConfig, getRuntimeConfigSnapshot } from "openclaw/plugin-sdk/runtime-config-snapshot";
26
26
  import { logDebug, logError } from "openclaw/plugin-sdk/logging-core";
27
27
  import { ApplicationCommandOptionType, ButtonStyle, ChannelType, ComponentType, GatewayCloseCodes, GatewayCloseCodes as GatewayCloseCodes$1, GatewayDispatchEvents, GatewayIntentBits, GatewayOpcodes } from "discord-api-types/v10";
28
28
  import { createHash, randomUUID } from "node:crypto";
29
- import { MAX_DATE_TIMESTAMP_MS, asDateTimestampMs, parseFiniteNumber, parseStrictFiniteNumber, parseStrictInteger, parseStrictNonNegativeInteger, parseStrictPositiveInteger, resolveDateTimestampMs, resolveTimestampMsToIsoString } from "openclaw/plugin-sdk/number-runtime";
29
+ import { MAX_DATE_TIMESTAMP_MS, asDateTimestampMs, parseFiniteNumber as parseFiniteNumber$1, parseStrictInteger, parseStrictPositiveInteger, resolveDateTimestampMs, resolveTimestampMsToIsoString } from "openclaw/plugin-sdk/number-runtime";
30
30
  import { expectDefined } from "openclaw/plugin-sdk/expect-runtime";
31
31
  import { Type } from "typebox";
32
32
  import { Check, Errors } from "typebox/value";
33
33
  import { inspect } from "node:util";
34
34
  import { readResponseWithLimit } from "openclaw/plugin-sdk/response-limit-runtime";
35
- import { PlatformMessageNotDispatchedError, formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
35
+ import { PlatformMessageNotDispatchedError, formatErrorMessage, toErrorObject } from "openclaw/plugin-sdk/error-runtime";
36
36
  import { fetchWithSsrFGuard, formatErrorMessage as formatErrorMessage$1 } from "openclaw/plugin-sdk/ssrf-runtime";
37
37
  import { createHttp1EnvHttpProxyAgent, createHttp1ProxyAgent, createNodeProxyAgent, resolveEnvHttpProxyAgentOptions, wrapFetchWithAbortSignal } from "openclaw/plugin-sdk/fetch-runtime";
38
38
  import { createNonExitingRuntime, createSubsystemLogger, danger, formatDurationSeconds, isVerbose, logVerbose, shouldLogVerbose, sleepWithAbort, warn } from "openclaw/plugin-sdk/runtime-env";
@@ -40,45 +40,44 @@ import { sliceUtf16Safe, truncateUtf16Safe, withTimeout } from "openclaw/plugin-
40
40
  import { addAllowlistUserEntriesFromConfigEntry, buildAllowlistResolutionSummary, canonicalizeAllowlistWithResolvedIds, patchAllowlistUsersInConfigEntries, summarizeMapping } from "openclaw/plugin-sdk/allow-from";
41
41
  import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
42
42
  import { resolveChunkMode, resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-chunking";
43
- import { chunkItems, sanitizeAssistantVisibleText, sanitizeAssistantVisibleTextWithProfile } from "openclaw/plugin-sdk/text-chunking";
43
+ import { chunkItems, findCodeRegions, sanitizeAssistantVisibleText, sanitizeAssistantVisibleTextWithProfile } from "openclaw/plugin-sdk/text-chunking";
44
44
  import { createReplyReferencePlanner } from "openclaw/plugin-sdk/reply-reference";
45
- import { buildOutboundSessionContext, resolveChannelStreamingBlockEnabled, sendDurableMessageBatch } from "openclaw/plugin-sdk/channel-outbound";
45
+ import { buildOutboundSessionContext, listMessageReceiptPlatformIds, resolveChannelStreamingBlockEnabled, sendDurableMessageBatch } from "openclaw/plugin-sdk/channel-outbound";
46
46
  import { getAgentScopedMediaLocalRoots } from "openclaw/plugin-sdk/media-runtime";
47
47
  import { resolveSendableOutboundReplyParts, resolveTextChunksWithFallback } from "openclaw/plugin-sdk/reply-payload";
48
48
  import { loadWebMedia } from "openclaw/plugin-sdk/web-media";
49
- import { createLazyRuntimeModule, createLazyRuntimeNamedExport } from "openclaw/plugin-sdk/lazy-runtime";
49
+ import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime";
50
50
  import { resolvePinnedMainDmOwnerFromAllowlist as resolvePinnedMainDmOwnerFromAllowlist$1 } from "openclaw/plugin-sdk/security-runtime";
51
51
  import { GROUP_POLICY_BLOCKED_LABEL, resolveDefaultGroupPolicy, resolveOpenProviderRuntimeGroupPolicy, warnMissingProviderGroupPolicyFallbackOnce } from "openclaw/plugin-sdk/runtime-group-policy";
52
52
  import { createChannelPairingChallengeIssuer } from "openclaw/plugin-sdk/channel-pairing";
53
53
  import { CHANNEL_APPROVAL_NATIVE_RUNTIME_CONTEXT_CAPABILITY } from "openclaw/plugin-sdk/approval-handler-adapter-runtime";
54
54
  import { questionGatewayRuntime } from "openclaw/plugin-sdk/question-gateway-runtime";
55
55
  import { buildPairingReply, upsertChannelPairingRequest, upsertChannelPairingRequest as upsertChannelPairingRequest$1 } from "openclaw/plugin-sdk/conversation-runtime";
56
- import { clearExpiredCooldowns, ensureAuthProfileStore, formatReasoningMessage, getPreparedModelCatalogSnapshot, isProfileInCooldown, loadPreparedModelCatalog, resolveAgentAvatar, resolveAgentDir, resolveDefaultModelForAgent, resolveHumanDelayConfig, resolveProfilesUnavailableReason } from "openclaw/plugin-sdk/agent-runtime";
56
+ import { clearExpiredCooldowns, ensureAuthProfileStore, formatReasoningMessage, getPreparedModelCatalogSnapshot, isProfileInCooldown, listAgentIds, loadPreparedModelCatalog, resolveAgentAvatar, resolveAgentDir, resolveDefaultModelForAgent, resolveHumanDelayConfig, resolveProfilesUnavailableReason } from "openclaw/plugin-sdk/agent-runtime";
57
57
  import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/dangerous-name-runtime";
58
58
  import { resolveNativeCommandsEnabled, resolveNativeSkillsEnabled } from "openclaw/plugin-sdk/native-command-config-runtime";
59
- import { normalizeProviderId } from "openclaw/plugin-sdk/provider-model-shared";
60
59
  import { requestHeartbeat } from "openclaw/plugin-sdk/heartbeat-runtime";
61
- import { enqueueSystemEvent, enqueueSystemEvent as enqueueSystemEvent$1 } from "openclaw/plugin-sdk/system-event-runtime";
60
+ import { enqueueRoutedSystemEvent, enqueueRoutedSystemEvent as enqueueRoutedSystemEvent$1 } from "openclaw/plugin-sdk/system-event-runtime";
62
61
  import { createChannelIngressResolver, defineStableChannelIngressIdentity, readChannelIngressStoreAllowFromForDmPolicy } from "openclaw/plugin-sdk/channel-ingress-runtime";
63
62
  import { createChannelPartialDeliveryError, dispatchChannelInboundTurn, formatInboundEnvelope, hasVisibleInboundReplyDispatch, isChannelPartialDeliveryError, resolveEnvelopeFormatOptions, resolveInboundSupplementalSenderAllowed, runChannelInboundEvent } from "openclaw/plugin-sdk/channel-inbound";
64
- import { ModelSelectionLockedError, applyModelOverrideToSessionEntry, resolveChannelModelOverride } from "openclaw/plugin-sdk/model-session-runtime";
65
- import { completeWithPreparedSimpleCompletionModel, extractAssistantText, prepareSimpleCompletionModelForAgent } from "openclaw/plugin-sdk/simple-completion-runtime";
66
- import { deleteSessionEntry, getSessionEntry, listSessionEntries, patchSessionEntry, readSessionUpdatedAt as readSessionUpdatedAt$1, resolveStorePath, resolveStorePath as resolveStorePath$1 } from "openclaw/plugin-sdk/session-store-runtime";
63
+ import { resolveChannelModelOverride } from "openclaw/plugin-sdk/model-session-runtime";
64
+ import { finalizeInboundContext, generateConversationLabel } from "openclaw/plugin-sdk/reply-dispatch-runtime";
65
+ import { deleteSessionEntry, getSessionEntry, listSessionEntries, readSessionUpdatedAt as readSessionUpdatedAt$1, resolveStorePath, resolveStorePath as resolveStorePath$1 } from "openclaw/plugin-sdk/session-store-runtime";
67
66
  import { buildCommandTextFromArgs, findCommandByNativeName, formatCommandArgMenuTitle, listChatCommands, listNativeCommandSpecsForConfig, listSkillCommandsForAgents, resolveCommandAuthorizedFromAuthorizers, resolveEffectiveAgentRuntime, resolveNativeCommandSessionTargets, resolveStoredModelOverride, serializeCommandArgs } from "openclaw/plugin-sdk/command-auth-native";
68
- import { buildCommandTextFromArgs as buildCommandTextFromArgs$1, findCommandByNativeName as findCommandByNativeName$1, parseCommandArgs, resolveCommandArgChoices, resolveCommandArgMenu, serializeCommandArgs as serializeCommandArgs$1 } from "openclaw/plugin-sdk/native-command-registry";
67
+ import { buildCommandTextFromArgs as buildCommandTextFromArgs$1, findCommandByNativeName as findCommandByNativeName$1, mergeNativeCommandSpecs, parseCommandArgs, resolveCommandArgChoices, resolveCommandArgMenu, serializeCommandArgs as serializeCommandArgs$1 } from "openclaw/plugin-sdk/native-command-registry";
68
+ import { PLUGIN_COMMAND_DISPATCH } from "openclaw/plugin-sdk/plugin-command-runtime";
69
69
  import { resolveDirectStatusReplyForSession } from "openclaw/plugin-sdk/command-status-runtime";
70
- import * as pluginRuntime from "openclaw/plugin-sdk/plugin-runtime";
71
- import { createInteractiveConversationBindingHelpers, dispatchPluginInteractiveHandler } from "openclaw/plugin-sdk/plugin-runtime";
72
70
  import * as conversationRuntime from "openclaw/plugin-sdk/conversation-binding-runtime";
73
- import { finalizeInboundContext } from "openclaw/plugin-sdk/reply-dispatch-runtime";
74
- import { createConnectedChannelStatusPatch, createTransportActivityStatusPatch } from "openclaw/plugin-sdk/gateway-runtime";
71
+ import { normalizeProviderId } from "openclaw/plugin-sdk/provider-model-shared";
75
72
  import { EventEmitter } from "node:events";
76
73
  import * as ws from "ws";
77
74
  import { Agent } from "node:https";
78
75
  import { captureHttpExchange, captureWsEvent, resolveDebugProxySettings, resolveEffectiveDebugProxyUrl } from "openclaw/plugin-sdk/proxy-capture";
79
76
  import * as dns from "node:dns";
77
+ import { channelReadyPatch, createTransportActivityStatusPatch } from "openclaw/plugin-sdk/gateway-runtime";
80
78
  import { registerChannelRuntimeContext } from "openclaw/plugin-sdk/channel-runtime-context";
81
79
  import { stripPlainTextToolCallBlocks } from "openclaw/plugin-sdk/tool-payload";
80
+ import { createChannelInteractiveDispatcher } from "openclaw/plugin-sdk/plugin-runtime";
82
81
  import { resolveApprovalOverGateway } from "openclaw/plugin-sdk/approval-gateway-runtime";
83
82
  import { resolveRequestUrl } from "openclaw/plugin-sdk/request-url";
84
83
  import { fetchWithRuntimeDispatcher } from "openclaw/plugin-sdk/runtime-fetch";
@@ -253,8 +252,11 @@ async function resolveDiscordDmCommandAccess(params) {
253
252
  subject: createDiscordDmIngressSubject(params.sender),
254
253
  conversation: {
255
254
  kind: "direct",
256
- id: params.sender.id
255
+ id: params.conversationId ?? params.sender.id,
256
+ parentId: params.conversationParentId,
257
+ threadId: params.conversationThreadId
257
258
  },
259
+ ...params.contextBinding ? { contextBinding: params.contextBinding } : {},
258
260
  event: {
259
261
  kind: params.eventKind ?? "native-command",
260
262
  authMode: "inbound",
@@ -275,7 +277,7 @@ async function resolveDiscordTextCommandAccess(params) {
275
277
  const memberAccessGroup = "discord-member-access";
276
278
  const commandGroup = params.memberAccessConfigured ? [`accessGroup:${memberAccessGroup}`] : [];
277
279
  const accessGroupMembership = params.memberAccessConfigured ? [syntheticAccessGroupMembership(memberAccessGroup, params.memberAllowed)] : [];
278
- return (await createDiscordIngressResolver({
280
+ return await createDiscordIngressResolver({
279
281
  accountId: params.accountId,
280
282
  cfg: params.cfg,
281
283
  token: params.token,
@@ -283,9 +285,12 @@ async function resolveDiscordTextCommandAccess(params) {
283
285
  }).command({
284
286
  subject: createDiscordDmIngressSubject(params.sender),
285
287
  conversation: {
286
- kind: "group",
287
- id: "discord-command"
288
+ kind: "channel",
289
+ id: params.conversationId ?? "discord-command",
290
+ parentId: params.conversationParentId,
291
+ threadId: params.conversationThreadId
288
292
  },
293
+ ...params.contextBinding ? { contextBinding: params.contextBinding } : {},
289
294
  accessGroupMembership,
290
295
  dmPolicy: "allowlist",
291
296
  groupPolicy: "allowlist",
@@ -297,7 +302,7 @@ async function resolveDiscordTextCommandAccess(params) {
297
302
  hasControlCommand: params.hasControlCommand,
298
303
  modeWhenAccessGroupsOff: "configured"
299
304
  }
300
- })).commandAccess;
305
+ });
301
306
  }
302
307
  //#endregion
303
308
  //#region extensions/discord/src/monitor/thread-title.ts
@@ -305,23 +310,10 @@ const DEFAULT_THREAD_TITLE_TIMEOUT_MS = 6e4;
305
310
  const MAX_THREAD_TITLE_SOURCE_CHARS = 600;
306
311
  const MAX_THREAD_TITLE_CHANNEL_NAME_CHARS = 120;
307
312
  const MAX_THREAD_TITLE_CHANNEL_DESCRIPTION_CHARS = 320;
308
- const DISCORD_THREAD_TITLE_MAX_TOKENS = 4096;
309
313
  const DISCORD_THREAD_TITLE_SYSTEM_PROMPT = "Generate a concise Discord thread title (3-6 words). Return only the title. Use channel context when provided and avoid redundant channel-name words unless needed for clarity.";
310
314
  async function generateThreadTitle(params) {
311
315
  const sourceText = params.messageText.trim();
312
316
  if (!sourceText) return null;
313
- const prepared = await prepareSimpleCompletionModelForAgent({
314
- cfg: params.cfg,
315
- agentId: params.agentId,
316
- ...params.modelRef ? { modelRef: params.modelRef } : {},
317
- useUtilityModel: true,
318
- allowMissingApiKeyModes: ["aws-sdk"]
319
- });
320
- if ("error" in prepared) {
321
- const modelLabel = prepared.selection ? `${prepared.selection.provider}/${prepared.selection.modelId}` : "unknown";
322
- logVerbose(`thread-title: ${prepared.error} (agent=${params.agentId}, model=${modelLabel})`);
323
- return null;
324
- }
325
317
  try {
326
318
  const userMessage = buildThreadTitleCompletionUserMessage({
327
319
  sourceText,
@@ -329,40 +321,21 @@ async function generateThreadTitle(params) {
329
321
  channelDescription: params.channelDescription
330
322
  });
331
323
  const timeoutMs = resolveThreadTitleTimeoutMs(params.timeoutMs);
332
- return normalizeGeneratedThreadTitle(extractAssistantText(await completeThreadTitle({
333
- model: prepared.model,
334
- auth: prepared.auth,
324
+ const generated = await generateConversationLabel({
325
+ cfg: params.cfg,
326
+ agentId: params.agentId,
335
327
  userMessage,
336
- timeoutMs
337
- }))) || null;
328
+ prompt: DISCORD_THREAD_TITLE_SYSTEM_PROMPT,
329
+ ...params.modelRef ? { modelRef: params.modelRef } : {},
330
+ timeoutMs,
331
+ maxLength: MAX_THREAD_TITLE_SOURCE_CHARS
332
+ });
333
+ return generated ? normalizeGeneratedThreadTitle(generated) : null;
338
334
  } catch (err) {
339
335
  logVerbose(`thread-title: title generation failed for agent ${params.agentId}: ${String(err)}`);
340
336
  return null;
341
337
  }
342
338
  }
343
- async function completeThreadTitle(params) {
344
- const maxTokens = Math.min(DISCORD_THREAD_TITLE_MAX_TOKENS, Math.floor(params.model.maxTokens));
345
- return await withAbortTimeout({
346
- timeoutMs: params.timeoutMs,
347
- createTimeoutError: () => /* @__PURE__ */ new Error(`thread-title timed out after ${params.timeoutMs}ms`),
348
- run: async (signal) => await completeWithPreparedSimpleCompletionModel({
349
- model: params.model,
350
- auth: params.auth,
351
- context: {
352
- systemPrompt: DISCORD_THREAD_TITLE_SYSTEM_PROMPT,
353
- messages: [{
354
- role: "user",
355
- content: params.userMessage,
356
- timestamp: Date.now()
357
- }]
358
- },
359
- options: {
360
- maxTokens,
361
- signal
362
- }
363
- })
364
- });
365
- }
366
339
  function buildThreadTitleCompletionUserMessage(params) {
367
340
  const sourceText = truncateThreadTitleSourceText(params.sourceText);
368
341
  const channelName = normalizeTitleContextField(params.channelName, MAX_THREAD_TITLE_CHANNEL_NAME_CHARS);
@@ -1113,7 +1086,7 @@ async function handleDiscordReactionEvent(params) {
1113
1086
  const resolveReactionBase = () => {
1114
1087
  if (reactionBase) return reactionBase;
1115
1088
  const emojiLabel = formatDiscordReactionEmoji(data.emoji);
1116
- const actorLabel = formatDiscordUserTag(user);
1089
+ const actorLabel = formatDiscordUserTag(user) || user.id;
1117
1090
  const guildSlug = guildInfo?.slug || (data.guild?.name ? normalizeDiscordSlug(data.guild.name) : data.guild_id ?? (isGroupDm ? "group-dm" : "dm"));
1118
1091
  const channelLabel = channelSlug ? `#${channelSlug}` : channelName ? `#${normalizeDiscordSlug(channelName)}` : `#${data.channel_id}`;
1119
1092
  reactionBase = {
@@ -1124,24 +1097,21 @@ async function handleDiscordReactionEvent(params) {
1124
1097
  };
1125
1098
  const emitReaction = (text, parentPeerId) => {
1126
1099
  const { contextKey } = resolveReactionBase();
1127
- enqueueSystemEvent(text, {
1128
- sessionKey: resolveAgentRoute({
1129
- cfg: params.cfg,
1130
- channel: "discord",
1131
- accountId: params.accountId,
1132
- guildId: data.guild_id ?? void 0,
1133
- memberRoleIds,
1134
- peer: {
1135
- kind: isDirectMessage ? "direct" : isGroupDm ? "group" : "channel",
1136
- id: isDirectMessage ? user.id : data.channel_id
1137
- },
1138
- parentPeer: parentPeerId ? {
1139
- kind: "channel",
1140
- id: parentPeerId
1141
- } : void 0
1142
- }).sessionKey,
1143
- contextKey
1144
- });
1100
+ enqueueRoutedSystemEvent(text, resolveAgentRoute({
1101
+ cfg: params.cfg,
1102
+ channel: "discord",
1103
+ accountId: params.accountId,
1104
+ guildId: data.guild_id ?? void 0,
1105
+ memberRoleIds,
1106
+ peer: {
1107
+ kind: isDirectMessage ? "direct" : isGroupDm ? "group" : "channel",
1108
+ id: isDirectMessage ? user.id : data.channel_id
1109
+ },
1110
+ parentPeer: parentPeerId ? {
1111
+ kind: "channel",
1112
+ id: parentPeerId
1113
+ } : void 0
1114
+ }), { contextKey });
1145
1115
  };
1146
1116
  const shouldNotifyReaction = (options) => shouldEmitDiscordReactionNotification({
1147
1117
  mode: options.mode,
@@ -1393,24 +1363,30 @@ var DiscordPresenceBaselineCache = class {
1393
1363
  * that a later inbound message starts a fresh session in every reset mode.
1394
1364
  */
1395
1365
  async function closeDiscordThreadSessions(params) {
1396
- const { cfg, accountId, threadId } = params;
1366
+ const { cfg, threadId } = params;
1397
1367
  const normalizedThreadId = normalizeOptionalLowercaseString(threadId) ?? "";
1398
1368
  if (!normalizedThreadId) return 0;
1399
1369
  const segmentRe = new RegExp(`:${normalizedThreadId}(?::|$)`, "i");
1400
1370
  function sessionKeyContainsThreadId(key) {
1401
1371
  return segmentRe.test(key);
1402
1372
  }
1403
- const storePath = resolveStorePath(cfg.session?.store, { agentId: accountId });
1404
1373
  let resetCount = 0;
1405
- for (const { sessionKey, entry } of listSessionEntries({ storePath })) {
1406
- if (!sessionKeyContainsThreadId(sessionKey)) continue;
1407
- if (await deleteSessionEntry({
1408
- archiveTranscript: true,
1409
- expectedSessionId: entry.sessionId ?? null,
1410
- expectedUpdatedAt: entry.updatedAt,
1411
- sessionKey,
1412
- storePath
1413
- })) resetCount += 1;
1374
+ for (const agentId of listAgentIds(cfg)) {
1375
+ const storePath = resolveStorePath(cfg.session?.store, { agentId });
1376
+ for (const { sessionKey, entry } of listSessionEntries({
1377
+ agentId,
1378
+ storePath,
1379
+ readOnly: true
1380
+ })) {
1381
+ if (!sessionKeyContainsThreadId(sessionKey)) continue;
1382
+ if (await deleteSessionEntry({
1383
+ archiveTranscript: true,
1384
+ expectedSessionId: entry.sessionId ?? null,
1385
+ expectedUpdatedAt: entry.updatedAt,
1386
+ sessionKey,
1387
+ storePath
1388
+ })) resetCount += 1;
1389
+ }
1414
1390
  }
1415
1391
  return resetCount;
1416
1392
  }
@@ -1612,8 +1588,7 @@ var DiscordPresenceListener = class extends PresenceUpdateListener {
1612
1588
  (this.params.logger ?? discordEventQueueLog).warn(danger(`discord presence cooldown persistence failed: ${String(err)}`));
1613
1589
  return;
1614
1590
  }
1615
- if (!enqueueSystemEvent(presenceEvent.text, {
1616
- sessionKey: route.sessionKey,
1591
+ if (!enqueueRoutedSystemEvent(presenceEvent.text, route, {
1617
1592
  contextKey: `discord:presence-online:${this.params.accountId}:${data.guild_id}:${userId}`,
1618
1593
  deliveryContext: {
1619
1594
  channel: "discord",
@@ -1679,10 +1654,9 @@ var DiscordPresenceReadyListener = class extends ReadyListener {
1679
1654
  }
1680
1655
  };
1681
1656
  var DiscordThreadUpdateListener = class extends ThreadUpdateListener {
1682
- constructor(cfg, accountId, logger) {
1657
+ constructor(cfg, logger) {
1683
1658
  super();
1684
1659
  this.cfg = cfg;
1685
- this.accountId = accountId;
1686
1660
  this.logger = logger;
1687
1661
  }
1688
1662
  async handle(data) {
@@ -1697,7 +1671,6 @@ var DiscordThreadUpdateListener = class extends ThreadUpdateListener {
1697
1671
  const logger = this.logger ?? discordEventQueueLog;
1698
1672
  const count = await closeDiscordThreadSessions({
1699
1673
  cfg: this.cfg,
1700
- accountId: this.accountId,
1701
1674
  threadId
1702
1675
  });
1703
1676
  if (count > 0) logger.info("Discord thread archived — reset sessions", {
@@ -1711,6 +1684,40 @@ var DiscordThreadUpdateListener = class extends ThreadUpdateListener {
1711
1684
  });
1712
1685
  }
1713
1686
  };
1687
+ var DiscordThreadDeleteListener = class extends ThreadDeleteListener {
1688
+ constructor(cfg, accountId, logger) {
1689
+ super();
1690
+ this.cfg = cfg;
1691
+ this.accountId = accountId;
1692
+ this.logger = logger;
1693
+ }
1694
+ async handle(data) {
1695
+ await runDiscordListenerWithSlowLog({
1696
+ logger: this.logger,
1697
+ listener: this.constructor.name,
1698
+ event: this.type,
1699
+ run: async () => {
1700
+ const threadId = data.id;
1701
+ getThreadBindingManager(this.accountId)?.unbindThread({
1702
+ threadId,
1703
+ reason: "thread-delete",
1704
+ sendFarewell: false
1705
+ });
1706
+ const count = await closeDiscordThreadSessions({
1707
+ cfg: this.cfg,
1708
+ threadId
1709
+ });
1710
+ if (count > 0) (this.logger ?? discordEventQueueLog).info("Discord thread deleted — reset sessions", {
1711
+ threadId,
1712
+ count
1713
+ });
1714
+ },
1715
+ onError: (err) => {
1716
+ (this.logger ?? discordEventQueueLog).error(danger(`discord thread-delete handler failed: ${String(err)}`));
1717
+ }
1718
+ });
1719
+ }
1720
+ };
1714
1721
  //#endregion
1715
1722
  //#region extensions/discord/src/internal/gateway-dispatch.ts
1716
1723
  function dispatchVoiceGatewayEvent(client, type, data) {
@@ -1841,17 +1848,7 @@ async function deliverDiscordInteractionReply(params) {
1841
1848
  ...contentPayload,
1842
1849
  ...components ? { components } : {},
1843
1850
  ...params.responseEphemeral !== void 0 ? { ephemeral: params.responseEphemeral } : {},
1844
- files: files.map((file) => {
1845
- if (file.data instanceof Blob) return {
1846
- name: file.name,
1847
- data: file.data
1848
- };
1849
- const arrayBuffer = Uint8Array.from(file.data).buffer;
1850
- return {
1851
- name: file.name,
1852
- data: new Blob([arrayBuffer])
1853
- };
1854
- })
1851
+ files
1855
1852
  } : {
1856
1853
  ...contentPayload,
1857
1854
  ...components ? { components } : {},
@@ -1884,7 +1881,8 @@ async function deliverDiscordInteractionReply(params) {
1884
1881
  const loaded = await loadWebMedia(url, { localRoots: params.mediaLocalRoots });
1885
1882
  return {
1886
1883
  name: loaded.fileName ?? "upload",
1887
- data: loaded.buffer
1884
+ data: loaded.buffer,
1885
+ contentType: loaded.contentType
1888
1886
  };
1889
1887
  }));
1890
1888
  const chunks = resolveTextChunksWithFallback(reply.text, chunkDiscordTextWithMode(reply.text, {
@@ -2031,8 +2029,6 @@ async function resolveDiscordNativeInteractionRouteState(params) {
2031
2029
  //#endregion
2032
2030
  //#region extensions/discord/src/monitor/native-command.runtime.ts
2033
2031
  const nativeCommandRuntime = {
2034
- matchPluginCommand: pluginRuntime.matchPluginCommand,
2035
- executePluginCommand: pluginRuntime.executePluginCommand,
2036
2032
  dispatchChannelInboundTurn,
2037
2033
  resolveDirectStatusReplyForSession,
2038
2034
  resolveDiscordNativeInteractionRouteState,
@@ -2043,7 +2039,8 @@ const nativeCommandRuntime = {
2043
2039
  async function dispatchDiscordNativeAgentReply(params) {
2044
2040
  const blockStreamingEnabled = resolveChannelStreamingBlockEnabled(params.discordConfig);
2045
2041
  let didReply = false;
2046
- let settledWithoutVisibleReply = false;
2042
+ let finalReplyOutcome;
2043
+ let hiddenFinalReply;
2047
2044
  const turnResult = await nativeCommandRuntime.dispatchChannelInboundTurn({
2048
2045
  cfg: params.cfg,
2049
2046
  channel: "discord",
@@ -2079,14 +2076,17 @@ async function dispatchDiscordNativeAgentReply(params) {
2079
2076
  suppression: { reason: "no_visible_result" }
2080
2077
  };
2081
2078
  },
2082
- onDelivered: (_payload, _info, result) => {
2083
- if (result?.visibleReplySent === false) settledWithoutVisibleReply = true;
2079
+ onDelivered: (payload, info, result) => {
2080
+ if (params.suppressReplies && info.kind === "final" && result?.suppression?.reason === "no_visible_result" && payload.text?.trim()) hiddenFinalReply = payload;
2081
+ if (info.kind === "final" && result?.visibleReplySent !== void 0 && (result.visibleReplySent || finalReplyOutcome !== "failed")) finalReplyOutcome = result.visibleReplySent ? "accepted" : "suppressed";
2084
2082
  },
2085
2083
  onError: (err, info) => {
2086
- if (isChannelPartialDeliveryError(err)) {
2084
+ const partialDelivery = isChannelPartialDeliveryError(err);
2085
+ if (partialDelivery) {
2087
2086
  didReply = true;
2088
2087
  logVerbose("discord: interaction reply partially delivered before expiry");
2089
2088
  }
2089
+ if (info.kind === "final") finalReplyOutcome = partialDelivery ? "accepted" : "failed";
2090
2090
  const message = err instanceof Error ? err.stack ?? err.message : String(err);
2091
2091
  params.log.error(`discord slash ${info.kind} reply failed: ${message}`);
2092
2092
  }
@@ -2095,14 +2095,20 @@ async function dispatchDiscordNativeAgentReply(params) {
2095
2095
  dispatcherOptions: { humanDelay: resolveHumanDelayConfig(params.cfg, params.effectiveRoute.agentId) },
2096
2096
  replyOptions: {
2097
2097
  skillFilter: params.channelConfig?.skills,
2098
+ [PLUGIN_COMMAND_DISPATCH]: params.pluginCommandDispatch,
2098
2099
  disableBlockStreaming: typeof blockStreamingEnabled === "boolean" ? !blockStreamingEnabled : void 0
2099
2100
  }
2100
2101
  });
2101
- if (!didReply && (params.suppressReplies || settledWithoutVisibleReply)) {
2102
+ const deliberateSilentTerminalReply = turnResult.dispatched && turnResult.dispatchResult.deliberateSilentTerminalReply === true;
2103
+ const dispatchResult = {
2104
+ dispatched: turnResult.dispatched,
2105
+ ...hiddenFinalReply ? { hiddenFinalReply } : {}
2106
+ };
2107
+ if (!didReply && (params.suppressReplies || finalReplyOutcome === "suppressed" || deliberateSilentTerminalReply)) {
2102
2108
  await settleDiscordInteractionWithoutVisibleReply(params.interaction);
2103
- return;
2109
+ return dispatchResult;
2104
2110
  }
2105
- if (params.suppressReplies || didReply || settledWithoutVisibleReply || turnResult.dispatched && hasVisibleInboundReplyDispatch(turnResult.dispatchResult)) return;
2111
+ if (didReply || turnResult.dispatched && hasVisibleInboundReplyDispatch(turnResult.dispatchResult)) return dispatchResult;
2106
2112
  await safeDiscordInteractionCall("interaction empty fallback", async () => {
2107
2113
  const payload = {
2108
2114
  content: DISCORD_EMPTY_VISIBLE_REPLY_WARNING,
@@ -2114,6 +2120,7 @@ async function dispatchDiscordNativeAgentReply(params) {
2114
2120
  }
2115
2121
  await params.interaction.reply(payload);
2116
2122
  });
2123
+ return dispatchResult;
2117
2124
  }
2118
2125
  //#endregion
2119
2126
  //#region extensions/discord/src/monitor/native-interaction-channel-context.ts
@@ -2623,7 +2630,8 @@ async function handleDiscordCommandArgInteraction(params) {
2623
2630
  sessionPrefix: ctx.sessionPrefix,
2624
2631
  preferFollowUp: true,
2625
2632
  threadBindings: ctx.threadBindings,
2626
- responseEphemeral: resolveDiscordSlashCommandConfig(ctx.discordConfig?.slashCommand).ephemeral
2633
+ responseEphemeral: resolveDiscordSlashCommandConfig(ctx.discordConfig?.slashCommand).ephemeral,
2634
+ pluginCommandDispatch: { kind: "non-plugin" }
2627
2635
  });
2628
2636
  }
2629
2637
  async function runDiscordCommandArgButton(params) {
@@ -2730,10 +2738,10 @@ function timestampOrder(value) {
2730
2738
  return value !== void 0 && value >= 0 ? value : 0;
2731
2739
  }
2732
2740
  function comparePreferenceEntries(left, right) {
2733
- return timestampMs(right.value.updatedAt) - timestampMs(left.value.updatedAt) || timestampOrder(right.value.updatedOrder) - timestampOrder(left.value.updatedOrder) || left.key.localeCompare(right.key);
2741
+ return preferenceTimestampMs(right.value.updatedAt) - preferenceTimestampMs(left.value.updatedAt) || timestampOrder(right.value.updatedOrder) - timestampOrder(left.value.updatedOrder) || left.key.localeCompare(right.key);
2734
2742
  }
2735
2743
  function nextPreferenceTimestamp(existingEntries) {
2736
- const existingMaxTimestampMs = existingEntries.reduce((max, entry) => Math.max(max, timestampMs(entry.updatedAt)), 0);
2744
+ const existingMaxTimestampMs = existingEntries.reduce((max, entry) => Math.max(max, preferenceTimestampMs(entry.updatedAt)), 0);
2737
2745
  lastPreferenceTimestampMs = Math.min(Math.max(resolveDateTimestampMs(Date.now(), 0), lastPreferenceTimestampMs + 1, existingMaxTimestampMs + 1), MAX_DATE_TIMESTAMP_MS);
2738
2746
  const existingMaxOrder = existingEntries.reduce((max, entry) => Math.max(max, timestampOrder(entry.updatedOrder)), 0);
2739
2747
  lastPreferenceOrder = Math.max(lastPreferenceOrder + 1, existingMaxOrder + 1);
@@ -2782,8 +2790,6 @@ async function recordDiscordModelPickerRecentModel(params) {
2782
2790
  const DISCORD_MODEL_PICKER_CUSTOM_ID_KEY = "mdlpk";
2783
2791
  const DISCORD_CUSTOM_ID_MAX_CHARS = 100;
2784
2792
  const DISCORD_COMPONENT_MAX_SELECT_OPTIONS = 25;
2785
- const DISCORD_MODEL_PICKER_PROVIDER_PAGE_SIZE = DISCORD_COMPONENT_MAX_SELECT_OPTIONS;
2786
- const DISCORD_MODEL_PICKER_MODEL_PAGE_SIZE = DISCORD_COMPONENT_MAX_SELECT_OPTIONS;
2787
2793
  function compareBucketItems(left, right) {
2788
2794
  const normalized = left.toLowerCase().localeCompare(right.toLowerCase());
2789
2795
  return normalized === 0 ? left.localeCompare(right) : normalized;
@@ -2843,15 +2849,15 @@ function parseRawPage(value) {
2843
2849
  }
2844
2850
  return 1;
2845
2851
  }
2846
- function parseRawPositiveInt(value) {
2847
- return parseStrictPositiveInteger(value);
2848
- }
2849
2852
  function coerceString(value) {
2850
2853
  return typeof value === "string" || typeof value === "number" ? String(value) : "";
2851
2854
  }
2852
- function clampPageSize(rawPageSize, max, fallback) {
2853
- if (!Number.isFinite(rawPageSize)) return fallback;
2854
- return Math.min(max, Math.max(1, Math.floor(rawPageSize ?? fallback)));
2855
+ function clampPageSize(rawPageSize) {
2856
+ if (!Number.isFinite(rawPageSize)) return DISCORD_COMPONENT_MAX_SELECT_OPTIONS;
2857
+ return Math.min(DISCORD_COMPONENT_MAX_SELECT_OPTIONS, Math.max(1, Math.floor(rawPageSize ?? DISCORD_COMPONENT_MAX_SELECT_OPTIONS)));
2858
+ }
2859
+ function normalizeOptionalModelPickerIndex(value) {
2860
+ return typeof value === "number" && Number.isFinite(value) ? Math.max(1, Math.floor(value)) : void 0;
2855
2861
  }
2856
2862
  function paginateItems(params) {
2857
2863
  const totalItems = params.items.length;
@@ -2877,10 +2883,10 @@ function buildDiscordModelPickerCustomId(params) {
2877
2883
  const userId = params.userId.trim();
2878
2884
  if (!userId) throw new Error("Discord model picker custom_id requires userId");
2879
2885
  const page = normalizeModelPickerPage(params.page);
2880
- const providerPage = typeof params.providerPage === "number" && Number.isFinite(params.providerPage) ? Math.max(1, Math.floor(params.providerPage)) : void 0;
2886
+ const providerPage = normalizeOptionalModelPickerIndex(params.providerPage);
2881
2887
  const normalizedProvider = params.provider ? normalizeProviderId(params.provider) : void 0;
2882
- const modelIndex = typeof params.modelIndex === "number" && Number.isFinite(params.modelIndex) ? Math.max(1, Math.floor(params.modelIndex)) : void 0;
2883
- const recentSlot = typeof params.recentSlot === "number" && Number.isFinite(params.recentSlot) ? Math.max(1, Math.floor(params.recentSlot)) : void 0;
2888
+ const modelIndex = normalizeOptionalModelPickerIndex(params.modelIndex);
2889
+ const recentSlot = normalizeOptionalModelPickerIndex(params.recentSlot);
2884
2890
  const modelToken = params.modelToken?.trim();
2885
2891
  if (modelToken && !DISCORD_MODEL_PICKER_MODEL_TOKEN_PATTERN.test(modelToken)) throw new Error("Discord model picker model token is invalid");
2886
2892
  const parts = [
@@ -2893,7 +2899,7 @@ function buildDiscordModelPickerCustomId(params) {
2893
2899
  if (normalizedProvider) parts.push(`p=${encodeCustomIdComponent(normalizedProvider)}`);
2894
2900
  const runtime = params.runtime?.trim();
2895
2901
  if (runtime) parts.push(`r=${encodeCustomIdComponent(runtime)}`);
2896
- const runtimeIndex = typeof params.runtimeIndex === "number" && Number.isFinite(params.runtimeIndex) ? Math.max(1, Math.floor(params.runtimeIndex)) : void 0;
2902
+ const runtimeIndex = normalizeOptionalModelPickerIndex(params.runtimeIndex);
2897
2903
  if (runtimeIndex) parts.push(`ri=${String(runtimeIndex)}`);
2898
2904
  if (providerPage) parts.push(`pp=${String(providerPage)}`);
2899
2905
  if (modelToken) parts.push(`m=${modelToken}`);
@@ -2917,13 +2923,13 @@ function parseDiscordModelPickerData(data) {
2917
2923
  const userId = decodeCustomIdComponent(coerceString(data.u));
2918
2924
  const providerRaw = decodeCustomIdComponent(coerceString(data.p));
2919
2925
  const runtimeRaw = decodeCustomIdComponent(coerceString(data.r));
2920
- const runtimeIndex = parseRawPositiveInt(data.ri);
2926
+ const runtimeIndex = parseStrictPositiveInteger(data.ri);
2921
2927
  const page = parseRawPage(data.g ?? data.pg);
2922
- const providerPage = parseRawPositiveInt(data.pp);
2923
- const modelIndex = parseRawPositiveInt(data.mi);
2928
+ const providerPage = parseStrictPositiveInteger(data.pp);
2929
+ const modelIndex = parseStrictPositiveInteger(data.mi);
2924
2930
  const modelTokenRaw = coerceString(data.m).trim();
2925
2931
  const modelToken = DISCORD_MODEL_PICKER_MODEL_TOKEN_PATTERN.test(modelTokenRaw) ? modelTokenRaw : void 0;
2926
- const recentSlot = parseRawPositiveInt(data.rs);
2932
+ const recentSlot = parseStrictPositiveInteger(data.rs);
2927
2933
  const providerBucketRaw = decodeCustomIdComponent(coerceString(data.pb)).trim().toLowerCase();
2928
2934
  const modelBucketRaw = decodeCustomIdComponent(coerceString(data.mb)).trim().toLowerCase();
2929
2935
  if (!isValidCommandContext(command) || !isValidPickerAction(action) || !isValidPickerView(view)) return null;
@@ -3039,17 +3045,7 @@ function findProviderBucketId(data, provider) {
3039
3045
  return findProviderBucketLocation(data, provider)?.bucket;
3040
3046
  }
3041
3047
  function findProviderBucketLocation(data, provider) {
3042
- const normalized = normalizeProviderId(provider);
3043
- const sorted = [...data.providers].toSorted();
3044
- const idx = sorted.indexOf(normalized);
3045
- if (idx < 0) return;
3046
- const containing = computeAlphaBuckets(sorted).find((bucket) => idx >= bucket.start && idx < bucket.end);
3047
- if (!containing) return;
3048
- const page = Math.floor((idx - containing.start) / DISCORD_MODEL_PICKER_PROVIDER_PAGE_SIZE) + 1;
3049
- return {
3050
- ...containing.id !== "all" ? { bucket: containing.id } : {},
3051
- page
3052
- };
3048
+ return findModelPickerBucketLocation([...data.providers].toSorted(), normalizeProviderId(provider));
3053
3049
  }
3054
3050
  /**
3055
3051
  * Derive the alpha-bucket id that contains a given model id within the
@@ -3059,28 +3055,24 @@ function findProviderBucketLocation(data, provider) {
3059
3055
  */
3060
3056
  function findModelBucketId(data, provider, model) {
3061
3057
  const modelSet = data.byProvider.get(normalizeProviderId(provider));
3062
- if (!modelSet) return;
3063
- const sorted = [...modelSet].toSorted(compareBucketItems);
3064
- const idx = sorted.indexOf(model);
3065
- if (idx < 0) return;
3066
- const containing = computeAlphaBuckets(sorted).find((bucket) => idx >= bucket.start && idx < bucket.end);
3067
- return containing && containing.id !== "all" ? containing.id : void 0;
3068
- }
3069
- function buildDiscordModelPickerProviderItems(data) {
3070
- return [...data.providers].toSorted().map((provider) => ({
3071
- id: provider,
3072
- count: data.byProvider.get(provider)?.size ?? 0
3073
- }));
3074
- }
3075
- function getDiscordModelPickerProviderPage(params) {
3076
- const allItems = buildDiscordModelPickerProviderItems(params.data);
3077
- const buckets = computeAlphaBuckets(allItems.map((item) => item.id));
3058
+ return modelSet ? findModelPickerBucketLocation([...modelSet].toSorted(compareBucketItems), model)?.bucket : void 0;
3059
+ }
3060
+ function findModelPickerBucketLocation(sortedItems, item, pageSize = DISCORD_COMPONENT_MAX_SELECT_OPTIONS) {
3061
+ const index = sortedItems.indexOf(item);
3062
+ const bucket = index < 0 ? void 0 : computeAlphaBuckets(sortedItems).find((entry) => index >= entry.start && index < entry.end);
3063
+ return bucket ? {
3064
+ ...bucket.id === "all" ? {} : { bucket: bucket.id },
3065
+ page: Math.floor((index - bucket.start) / pageSize) + 1
3066
+ } : void 0;
3067
+ }
3068
+ function paginateDiscordModelPickerBucket(params) {
3069
+ const buckets = computeAlphaBuckets(params.itemLabels);
3078
3070
  const bucket = resolveBucket(buckets, params.bucket);
3079
- const bucketItems = bucket ? allItems.slice(bucket.start, bucket.end) : allItems;
3080
- const pageSize = clampPageSize(params.pageSize, DISCORD_MODEL_PICKER_PROVIDER_PAGE_SIZE, DISCORD_MODEL_PICKER_PROVIDER_PAGE_SIZE);
3071
+ const items = bucket ? params.items.slice(bucket.start, bucket.end) : params.items;
3072
+ const pageSize = clampPageSize(params.pageSize);
3081
3073
  return {
3082
3074
  ...paginateItems({
3083
- items: bucketItems,
3075
+ items,
3084
3076
  page: normalizeModelPickerPage(params.page),
3085
3077
  pageSize
3086
3078
  }),
@@ -3088,24 +3080,29 @@ function getDiscordModelPickerProviderPage(params) {
3088
3080
  buckets
3089
3081
  };
3090
3082
  }
3083
+ function getDiscordModelPickerProviderPage(params) {
3084
+ const providers = [...params.data.providers].toSorted();
3085
+ return paginateDiscordModelPickerBucket({
3086
+ ...params,
3087
+ itemLabels: providers,
3088
+ items: providers.map((provider) => ({
3089
+ id: provider,
3090
+ count: params.data.byProvider.get(provider)?.size ?? 0
3091
+ }))
3092
+ });
3093
+ }
3091
3094
  function getDiscordModelPickerModelPage(params) {
3092
3095
  const provider = normalizeProviderId(params.provider);
3093
3096
  const modelSet = params.data.byProvider.get(provider);
3094
3097
  if (!modelSet) return null;
3095
3098
  const allModels = [...modelSet].toSorted(compareBucketItems);
3096
- const buckets = computeAlphaBuckets(allModels);
3097
- const bucket = resolveBucket(buckets, params.bucket);
3098
- const bucketItems = bucket ? allModels.slice(bucket.start, bucket.end) : allModels;
3099
- const pageSize = clampPageSize(params.pageSize, DISCORD_MODEL_PICKER_MODEL_PAGE_SIZE, DISCORD_MODEL_PICKER_MODEL_PAGE_SIZE);
3100
3099
  return {
3101
- ...paginateItems({
3102
- items: bucketItems,
3103
- page: normalizeModelPickerPage(params.page),
3104
- pageSize
3100
+ ...paginateDiscordModelPickerBucket({
3101
+ ...params,
3102
+ items: allModels,
3103
+ itemLabels: allModels
3105
3104
  }),
3106
- provider,
3107
- bucket,
3108
- buckets
3105
+ provider
3109
3106
  };
3110
3107
  }
3111
3108
  function resolveDiscordModelPickerPageForModel(params) {
@@ -3113,16 +3110,8 @@ function resolveDiscordModelPickerPageForModel(params) {
3113
3110
  const modelSet = params.data.byProvider.get(provider);
3114
3111
  if (!modelSet) return { page: 1 };
3115
3112
  const sorted = [...modelSet].toSorted(compareBucketItems);
3116
- const index = sorted.indexOf(params.model);
3117
- if (index < 0) return { page: 1 };
3118
- const pageSize = clampPageSize(params.pageSize, DISCORD_MODEL_PICKER_MODEL_PAGE_SIZE, DISCORD_MODEL_PICKER_MODEL_PAGE_SIZE);
3119
- const containingBucket = computeAlphaBuckets(sorted).find((bucket) => index >= bucket.start && index < bucket.end);
3120
- if (!containingBucket) return { page: Math.floor(index / pageSize) + 1 };
3121
- const offsetInBucket = index - containingBucket.start;
3122
- return {
3123
- page: Math.floor(offsetInBucket / pageSize) + 1,
3124
- bucket: containingBucket.id === "all" ? void 0 : containingBucket.id
3125
- };
3113
+ const pageSize = clampPageSize(params.pageSize);
3114
+ return findModelPickerBucketLocation(sorted, params.model, pageSize) ?? { page: 1 };
3126
3115
  }
3127
3116
  //#endregion
3128
3117
  //#region extensions/discord/src/monitor/model-picker.view.ts
@@ -3302,53 +3291,25 @@ function buildProviderSelectRow(params) {
3302
3291
  }
3303
3292
  function buildPaginationRow(params) {
3304
3293
  if (params.totalPages <= 1) return null;
3305
- return new Row([
3306
- createModelPickerButton({
3307
- label: "◀ Prev",
3308
- style: ButtonStyle.Secondary,
3309
- disabled: !params.hasPrev,
3310
- customId: buildDiscordModelPickerCustomId({
3311
- command: params.command,
3312
- action: "nav",
3313
- view: params.view,
3314
- provider: params.provider,
3315
- runtime: params.runtime,
3316
- runtimeIndex: params.runtimeIndex,
3317
- page: Math.max(1, params.page - 1),
3318
- providerPage: params.providerPage,
3319
- modelIndex: params.modelIndex,
3320
- modelToken: params.modelToken,
3321
- providerBucket: params.providerBucket,
3322
- modelBucket: params.modelBucket,
3323
- userId: params.userId
3324
- })
3325
- }),
3326
- createModelPickerButton({
3327
- label: `Page ${params.page}/${params.totalPages}`,
3328
- style: ButtonStyle.Secondary,
3329
- disabled: true,
3330
- customId: DISCORD_MODEL_PICKER_PAGE_INDICATOR_CUSTOM_ID
3331
- }),
3332
- createModelPickerButton({
3333
- label: "Next ▶",
3334
- style: ButtonStyle.Secondary,
3335
- disabled: !params.hasNext,
3336
- customId: buildDiscordModelPickerCustomId({
3337
- command: params.command,
3338
- action: "nav",
3339
- view: params.view,
3340
- provider: params.provider,
3341
- runtime: params.runtime,
3342
- runtimeIndex: params.runtimeIndex,
3343
- page: Math.min(params.totalPages, params.page + 1),
3344
- providerPage: params.providerPage,
3345
- modelIndex: params.modelIndex,
3346
- modelToken: params.modelToken,
3347
- providerBucket: params.providerBucket,
3348
- modelBucket: params.modelBucket,
3349
- userId: params.userId
3350
- })
3294
+ const { page, totalPages, hasPrev, hasNext, ...navigationState } = params;
3295
+ const createNavigationButton = (label, targetPage, enabled) => createModelPickerButton({
3296
+ label,
3297
+ disabled: !enabled,
3298
+ customId: buildDiscordModelPickerCustomId({
3299
+ ...navigationState,
3300
+ action: "nav",
3301
+ page: targetPage
3351
3302
  })
3303
+ });
3304
+ const indicatorButton = createModelPickerButton({
3305
+ label: `Page ${page}/${totalPages}`,
3306
+ disabled: true,
3307
+ customId: DISCORD_MODEL_PICKER_PAGE_INDICATOR_CUSTOM_ID
3308
+ });
3309
+ return new Row([
3310
+ createNavigationButton("◀ Prev", Math.max(1, page - 1), hasPrev),
3311
+ indicatorButton,
3312
+ createNavigationButton("Next ▶", Math.min(totalPages, page + 1), hasNext)
3352
3313
  ]);
3353
3314
  }
3354
3315
  function buildModelRows(params) {
@@ -3461,10 +3422,17 @@ function buildModelRows(params) {
3461
3422
  const resolvedDefault = params.data.resolvedDefault;
3462
3423
  const shouldDisableReset = Boolean(parsedCurrentModel) && parsedCurrentModel?.provider === resolvedDefault.provider && parsedCurrentModel?.model === resolvedDefault.model;
3463
3424
  const hasPendingSelection = Boolean(parsedPendingModel) && parsedPendingModel?.provider === params.modelPage.provider && typeof params.pendingModelIndex === "number" && params.pendingModelIndex > 0;
3425
+ const modelActionState = {
3426
+ command: params.command,
3427
+ provider: params.modelPage.provider,
3428
+ ...compactRuntime,
3429
+ page: params.modelPage.page,
3430
+ providerPage: providerPage.page,
3431
+ userId: params.userId
3432
+ };
3464
3433
  const buttonRowItems = [
3465
3434
  createModelPickerButton({
3466
3435
  label: "Providers",
3467
- style: ButtonStyle.Secondary,
3468
3436
  customId: buildDiscordModelPickerCustomId({
3469
3437
  command: params.command,
3470
3438
  action: "back",
@@ -3476,47 +3444,29 @@ function buildModelRows(params) {
3476
3444
  }),
3477
3445
  createModelPickerButton({
3478
3446
  label: "Cancel",
3479
- style: ButtonStyle.Secondary,
3480
3447
  customId: buildDiscordModelPickerCustomId({
3481
- command: params.command,
3448
+ ...modelActionState,
3482
3449
  action: "cancel",
3483
- view: "models",
3484
- provider: params.modelPage.provider,
3485
- ...compactRuntime,
3486
- page: params.modelPage.page,
3487
- providerPage: providerPage.page,
3488
- userId: params.userId
3450
+ view: "models"
3489
3451
  })
3490
3452
  }),
3491
3453
  createModelPickerButton({
3492
3454
  label: "Reset to default",
3493
- style: ButtonStyle.Secondary,
3494
3455
  disabled: shouldDisableReset,
3495
3456
  customId: buildDiscordModelPickerCustomId({
3496
- command: params.command,
3457
+ ...modelActionState,
3497
3458
  action: "reset",
3498
- view: "models",
3499
- provider: params.modelPage.provider,
3500
- ...compactRuntime,
3501
- page: params.modelPage.page,
3502
- providerPage: providerPage.page,
3503
- userId: params.userId
3459
+ view: "models"
3504
3460
  })
3505
3461
  })
3506
3462
  ];
3507
3463
  if (hasQuickModels) buttonRowItems.push(createModelPickerButton({
3508
3464
  label: "Recents",
3509
- style: ButtonStyle.Secondary,
3510
3465
  customId: buildDiscordModelPickerCustomId({
3511
- command: params.command,
3466
+ ...modelActionState,
3512
3467
  action: "recents",
3513
3468
  view: "recents",
3514
- provider: params.modelPage.provider,
3515
- ...compactRuntime,
3516
- page: params.modelPage.page,
3517
- providerPage: providerPage.page,
3518
- modelBucket: activeModelBucket,
3519
- userId: params.userId
3469
+ modelBucket: activeModelBucket
3520
3470
  })
3521
3471
  }));
3522
3472
  buttonRowItems.push(createModelPickerButton({
@@ -3524,16 +3474,11 @@ function buildModelRows(params) {
3524
3474
  style: ButtonStyle.Primary,
3525
3475
  disabled: !hasPendingSelection,
3526
3476
  customId: buildDiscordModelPickerCustomId({
3527
- command: params.command,
3477
+ ...modelActionState,
3528
3478
  action: "submit",
3529
3479
  view: "models",
3530
- provider: params.modelPage.provider,
3531
- ...compactRuntime,
3532
- page: params.modelPage.page,
3533
- providerPage: providerPage.page,
3534
3480
  modelIndex: params.pendingModelIndex,
3535
- modelToken: pendingModelToken,
3536
- userId: params.userId
3481
+ modelToken: pendingModelToken
3537
3482
  })
3538
3483
  }));
3539
3484
  return {
@@ -3680,32 +3625,14 @@ function createModelRefToken(modelRef) {
3680
3625
  function renderDiscordModelPickerRecentsView(params) {
3681
3626
  const defaultModelRef = `${params.data.resolvedDefault.provider}/${params.data.resolvedDefault.model}`;
3682
3627
  const rows = [];
3683
- const dedupedQuickModels = params.quickModels.filter((modelRef) => modelRef !== defaultModelRef);
3684
- rows.push(new Row([createModelPickerButton({
3685
- label: formatRecentsButtonLabel(defaultModelRef, "(default)"),
3686
- style: ButtonStyle.Secondary,
3687
- customId: buildDiscordModelPickerCustomId({
3688
- command: params.command,
3689
- action: "submit",
3690
- view: "recents",
3691
- recentSlot: 1,
3692
- modelToken: createModelRefToken(defaultModelRef),
3693
- provider: params.provider,
3694
- runtime: params.runtime,
3695
- runtimeIndex: params.runtimeIndex,
3696
- page: params.page,
3697
- providerPage: params.providerPage,
3698
- userId: params.userId
3699
- })
3700
- })]));
3701
- for (const [i, modelRef] of dedupedQuickModels.entries()) rows.push(new Row([createModelPickerButton({
3702
- label: formatRecentsButtonLabel(modelRef),
3703
- style: ButtonStyle.Secondary,
3628
+ const recentModels = [defaultModelRef, ...params.quickModels.filter((modelRef) => modelRef !== defaultModelRef)];
3629
+ for (const [index, modelRef] of recentModels.entries()) rows.push(new Row([createModelPickerButton({
3630
+ label: formatRecentsButtonLabel(modelRef, index === 0 ? "(default)" : void 0),
3704
3631
  customId: buildDiscordModelPickerCustomId({
3705
3632
  command: params.command,
3706
3633
  action: "submit",
3707
3634
  view: "recents",
3708
- recentSlot: i + 2,
3635
+ recentSlot: index + 1,
3709
3636
  modelToken: createModelRefToken(modelRef),
3710
3637
  provider: params.provider,
3711
3638
  runtime: params.runtime,
@@ -3717,7 +3644,6 @@ function renderDiscordModelPickerRecentsView(params) {
3717
3644
  })]));
3718
3645
  const backRow = new Row([createModelPickerButton({
3719
3646
  label: "Back",
3720
- style: ButtonStyle.Secondary,
3721
3647
  customId: buildDiscordModelPickerCustomId({
3722
3648
  command: params.command,
3723
3649
  action: "back",
@@ -3749,43 +3675,10 @@ function toDiscordModelPickerMessagePayload(view) {
3749
3675
  }
3750
3676
  //#endregion
3751
3677
  //#region extensions/discord/src/monitor/native-command-model-picker-apply.ts
3752
- async function persistDiscordModelPickerOverride(params) {
3753
- const storePath = resolveStorePath(params.cfg.session?.store, { agentId: params.route.agentId });
3754
- let persisted = false;
3755
- await patchSessionEntry({
3756
- storePath,
3757
- sessionKey: params.route.sessionKey,
3758
- fallbackEntry: {
3759
- sessionId: randomUUID(),
3760
- updatedAt: Date.now()
3761
- },
3762
- replaceEntry: true,
3763
- update: (entry) => {
3764
- persisted = applyModelOverrideToSessionEntry({
3765
- entry,
3766
- selection: {
3767
- provider: params.provider,
3768
- model: params.model,
3769
- isDefault: params.isDefault
3770
- },
3771
- markLiveSwitchPending: true
3772
- }).updated || persisted;
3773
- const runtime = params.runtime?.trim();
3774
- if (runtime && runtime !== "auto" && runtime !== "default") {
3775
- if (entry.agentRuntimeOverride !== runtime) {
3776
- entry.agentRuntimeOverride = runtime;
3777
- delete entry.agentHarnessId;
3778
- persisted = true;
3779
- }
3780
- } else if (runtime && entry.agentRuntimeOverride) {
3781
- delete entry.agentRuntimeOverride;
3782
- delete entry.agentHarnessId;
3783
- persisted = true;
3784
- }
3785
- return entry;
3786
- }
3787
- });
3788
- return persisted;
3678
+ function normalizeExpectedRuntime(value) {
3679
+ const runtime = value?.trim();
3680
+ if (!runtime) return;
3681
+ return runtime === "auto" || runtime === "default" ? "auto" : runtime;
3789
3682
  }
3790
3683
  async function applyDiscordModelPickerSelection(params) {
3791
3684
  try {
@@ -3800,79 +3693,42 @@ async function applyDiscordModelPickerSelection(params) {
3800
3693
  sessionPrefix: params.sessionPrefix,
3801
3694
  preferFollowUp: true,
3802
3695
  threadBindings: params.threadBindings,
3803
- suppressReplies: true
3696
+ suppressReplies: true,
3697
+ pluginCommandDispatch: { kind: "non-plugin" }
3804
3698
  }), 12e3);
3805
3699
  if (!dispatchResult.accepted) return {
3806
3700
  status: "rejected",
3807
3701
  noticeMessage: `❌ Failed to apply ${params.resolvedModelRef}. Try /model ${params.resolvedModelRef} directly.`
3808
3702
  };
3809
- const fallbackRoute = dispatchResult.effectiveRoute ?? params.route;
3703
+ const hiddenFinalReply = dispatchResult.hiddenFinalReply;
3704
+ const effectiveRoute = dispatchResult.effectiveRoute ?? params.route;
3810
3705
  if (params.settleMs > 0) await new Promise((resolve) => {
3811
3706
  setTimeout(resolve, params.settleMs);
3812
3707
  });
3813
- let effectiveModelRef = params.resolveCurrentModel(fallbackRoute);
3814
- let persisted = effectiveModelRef === params.resolvedModelRef;
3815
- if (params.selectedRuntime?.trim()) {
3816
- await persistDiscordModelPickerOverride({
3817
- cfg: params.cfg,
3818
- route: fallbackRoute,
3819
- provider: params.selectedProvider,
3820
- model: params.selectedModel,
3821
- isDefault: params.selectedProvider === params.defaultProvider && params.selectedModel === params.defaultModel,
3822
- runtime: params.selectedRuntime
3823
- });
3824
- await new Promise((resolve) => {
3825
- setTimeout(resolve, 100);
3826
- });
3827
- effectiveModelRef = params.resolveCurrentModel(fallbackRoute);
3828
- persisted = effectiveModelRef === params.resolvedModelRef;
3829
- }
3830
- if (!persisted) {
3831
- logVerbose(`discord: model picker override mismatch — expected ${params.resolvedModelRef} but read ${effectiveModelRef} from session key ${fallbackRoute.sessionKey}; attempting direct session override persist`);
3832
- try {
3833
- const directlyPersisted = await persistDiscordModelPickerOverride({
3834
- cfg: params.cfg,
3835
- route: fallbackRoute,
3836
- provider: params.selectedProvider,
3837
- model: params.selectedModel,
3838
- isDefault: params.selectedProvider === params.defaultProvider && params.selectedModel === params.defaultModel,
3839
- runtime: params.selectedRuntime
3840
- });
3841
- await new Promise((resolve) => {
3842
- setTimeout(resolve, 100);
3843
- });
3844
- effectiveModelRef = params.resolveCurrentModel(fallbackRoute);
3845
- persisted = effectiveModelRef === params.resolvedModelRef;
3846
- if (!persisted) logVerbose(`discord: direct session override persist failed — expected ${params.resolvedModelRef} but read ${effectiveModelRef} from session key ${fallbackRoute.sessionKey}`);
3847
- else if (!directlyPersisted) logVerbose(`discord: direct session override persist became a no-op because ${params.resolvedModelRef} was already present on re-read for session key ${fallbackRoute.sessionKey}`);
3848
- } catch (error) {
3849
- if (error instanceof ModelSelectionLockedError) return {
3850
- status: "rejected",
3851
- noticeMessage: `❌ ${error.message}`
3852
- };
3853
- const message = error instanceof Error ? error.message : String(error);
3854
- logVerbose(`discord: direct session override persist threw for session key ${fallbackRoute.sessionKey}: ${message}`);
3855
- }
3856
- }
3857
- if (persisted) await recordDiscordModelPickerRecentModel({
3708
+ const effectiveModelRef = params.resolveCurrentModel(effectiveRoute);
3709
+ const effectiveRuntime = params.resolveCurrentRuntime(effectiveRoute);
3710
+ const currentSelection = `Current selection: ${effectiveModelRef} with runtime ${effectiveRuntime}.`;
3711
+ if (hiddenFinalReply?.isError) return {
3712
+ status: "rejected",
3713
+ noticeMessage: `${hiddenFinalReply.text?.trim()}\n${currentSelection}`
3714
+ };
3715
+ const expectedRuntime = normalizeExpectedRuntime(params.selectedRuntime);
3716
+ const verified = effectiveModelRef === params.resolvedModelRef && (expectedRuntime === void 0 || effectiveRuntime === expectedRuntime);
3717
+ if (verified) await recordDiscordModelPickerRecentModel({
3858
3718
  scope: params.preferenceScope,
3859
3719
  modelRef: params.resolvedModelRef,
3860
3720
  limit: 5
3861
3721
  }).catch(() => void 0);
3862
- return persisted ? {
3722
+ return verified ? {
3863
3723
  status: "success",
3864
3724
  effectiveModelRef,
3865
- noticeMessage: `✅ Model set to ${params.resolvedModelRef}.`
3725
+ noticeMessage: hiddenFinalReply?.text?.trim() || `✅ Model set to ${params.resolvedModelRef}.`
3866
3726
  } : {
3867
3727
  status: "mismatch",
3868
3728
  effectiveModelRef,
3869
- noticeMessage: `⚠️ Tried to set ${params.resolvedModelRef}, but current model is ${effectiveModelRef}.`
3729
+ noticeMessage: `⚠️ Tried to set ${params.resolvedModelRef}${expectedRuntime ? ` with runtime ${expectedRuntime}` : ""}, but current selection is ${effectiveModelRef} with runtime ${effectiveRuntime}.`
3870
3730
  };
3871
3731
  } catch (error) {
3872
- if (error instanceof ModelSelectionLockedError) return {
3873
- status: "rejected",
3874
- noticeMessage: `❌ ${error.message}`
3875
- };
3876
3732
  if (error instanceof Error && error.message === "timeout") return {
3877
3733
  status: "timeout",
3878
3734
  noticeMessage: `⏳ Model change to ${params.resolvedModelRef} is still processing. Check /status in a few seconds.`
@@ -3902,9 +3758,6 @@ function shouldOpenDiscordModelPickerFromCommand(params) {
3902
3758
  if (context === "model") return !resolveCommandArgStringValue(params.commandArgs, "model") && !serializedArgs ? context : null;
3903
3759
  return serializedArgs ? null : context;
3904
3760
  }
3905
- function buildDiscordModelPickerCurrentModel(defaultProvider, defaultModel) {
3906
- return `${defaultProvider}/${defaultModel}`;
3907
- }
3908
3761
  function buildDiscordModelPickerAllowedModelRefs(data) {
3909
3762
  const out = /* @__PURE__ */ new Set();
3910
3763
  for (const provider of data.providers) {
@@ -4000,7 +3853,7 @@ async function resolveDiscordNativeChoiceContext(params) {
4000
3853
  }
4001
3854
  }
4002
3855
  function resolveDiscordModelPickerCurrentModel(params) {
4003
- const fallback = buildDiscordModelPickerCurrentModel(params.data.resolvedDefault.provider, params.data.resolvedDefault.model);
3856
+ const fallback = `${params.data.resolvedDefault.provider}/${params.data.resolvedDefault.model}`;
4004
3857
  try {
4005
3858
  const storePath = resolveStorePath(params.cfg.session?.store, { agentId: params.route.agentId });
4006
3859
  const override = resolveStoredModelOverride({
@@ -4175,7 +4028,7 @@ function buildDiscordModelPickerSelectionCommand(params) {
4175
4028
  if (!commandDefinition) return null;
4176
4029
  const commandArgs = {
4177
4030
  values: { model: params.modelRef },
4178
- raw: params.modelRef
4031
+ raw: params.runtime ? `${params.modelRef} --runtime ${params.runtime}` : params.modelRef
4179
4032
  };
4180
4033
  return {
4181
4034
  command: commandDefinition,
@@ -4186,7 +4039,7 @@ function buildDiscordModelPickerSelectionCommand(params) {
4186
4039
  function listDiscordModelPickerProviderModels(data, provider) {
4187
4040
  const modelSet = data.byProvider.get(provider);
4188
4041
  if (!modelSet) return [];
4189
- return [...modelSet].toSorted();
4042
+ return [...modelSet].toSorted((left, right) => left < right ? -1 : left > right ? 1 : 0);
4190
4043
  }
4191
4044
  function resolveDiscordModelPickerModelRefByToken(data, modelToken) {
4192
4045
  const matchingRefs = [];
@@ -4224,10 +4077,6 @@ function resolveDiscordModelPickerSubmissionRuntime(params) {
4224
4077
  provider: params.provider,
4225
4078
  runtime: params.parsedRuntime,
4226
4079
  allowResetRuntime: true
4227
- }) ?? resolveDiscordModelPickerRuntimeForProvider({
4228
- data: params.data,
4229
- provider: params.provider,
4230
- runtime: params.currentRuntime
4231
4080
  });
4232
4081
  }
4233
4082
  async function handleDiscordModelPickerInteraction(params) {
@@ -4277,6 +4126,22 @@ async function handleDiscordModelPickerInteraction(params) {
4277
4126
  });
4278
4127
  const updatePicker = async (payload) => await params.safeInteractionCall("model picker update", () => deferredUpdate ? interaction.editReply(payload) : interaction.update(payload));
4279
4128
  const showNotice = async (message) => await updatePicker(buildDiscordModelPickerNoticePayload(message));
4129
+ const updateModelsView = async (provider, state = {}) => {
4130
+ const rendered = renderDiscordModelPickerModelsView({
4131
+ command: parsed.command,
4132
+ userId: parsed.userId,
4133
+ data: pickerData,
4134
+ provider,
4135
+ page: parsed.page,
4136
+ providerPage: parsed.providerPage ?? 1,
4137
+ providerBucket: parsed.providerBucket ?? findProviderBucketId(pickerData, provider),
4138
+ currentModel: currentModelRef,
4139
+ currentRuntime,
4140
+ quickModels,
4141
+ ...state
4142
+ });
4143
+ return await updatePicker(toDiscordModelPickerMessagePayload(rendered));
4144
+ };
4280
4145
  if (parsed.action === "recents") {
4281
4146
  await updatePicker(toDiscordModelPickerMessagePayload(renderDiscordModelPickerRecentsView({
4282
4147
  command: parsed.command,
@@ -4293,35 +4158,14 @@ async function handleDiscordModelPickerInteraction(params) {
4293
4158
  })));
4294
4159
  return;
4295
4160
  }
4296
- if (parsed.action === "back" && parsed.view === "providers") {
4297
- await updatePicker(toDiscordModelPickerMessagePayload(renderDiscordModelPickerProvidersView({
4298
- command: parsed.command,
4299
- userId: parsed.userId,
4300
- data: pickerData,
4301
- page: parsed.page,
4302
- providerBucket: parsed.providerBucket,
4303
- currentModel: currentModelRef
4304
- })));
4305
- return;
4306
- }
4307
- if (parsed.action === "nav" && parsed.view === "providers") {
4308
- await updatePicker(toDiscordModelPickerMessagePayload(renderDiscordModelPickerProvidersView({
4309
- command: parsed.command,
4310
- userId: parsed.userId,
4311
- data: pickerData,
4312
- page: parsed.page,
4313
- providerBucket: parsed.providerBucket,
4314
- currentModel: currentModelRef
4315
- })));
4316
- return;
4317
- }
4318
- if (parsed.action === "bucket" && parsed.view === "providers") {
4161
+ if (parsed.view === "providers" && (parsed.action === "back" || parsed.action === "nav" || parsed.action === "bucket")) {
4162
+ const selectingBucket = parsed.action === "bucket";
4319
4163
  await updatePicker(toDiscordModelPickerMessagePayload(renderDiscordModelPickerProvidersView({
4320
4164
  command: parsed.command,
4321
4165
  userId: parsed.userId,
4322
4166
  data: pickerData,
4323
- page: 1,
4324
- providerBucket: resolveSelectedBucket(interaction),
4167
+ page: selectingBucket ? 1 : parsed.page,
4168
+ providerBucket: selectingBucket ? resolveSelectedBucket(interaction) : parsed.providerBucket,
4325
4169
  currentModel: currentModelRef
4326
4170
  })));
4327
4171
  return;
@@ -4332,24 +4176,15 @@ async function handleDiscordModelPickerInteraction(params) {
4332
4176
  currentModelRef,
4333
4177
  data: pickerData
4334
4178
  });
4335
- await updatePicker(toDiscordModelPickerMessagePayload(renderDiscordModelPickerModelsView({
4336
- command: parsed.command,
4337
- userId: parsed.userId,
4338
- data: pickerData,
4339
- provider,
4179
+ await updateModelsView(provider, {
4340
4180
  page: 1,
4341
- providerPage: parsed.providerPage ?? 1,
4342
- providerBucket: parsed.providerBucket ?? findProviderBucketId(pickerData, provider),
4343
4181
  modelBucket: resolveSelectedBucket(interaction),
4344
- currentModel: currentModelRef,
4345
- currentRuntime,
4346
4182
  pendingRuntime: resolvePendingRuntime({
4347
4183
  data: pickerData,
4348
4184
  provider,
4349
4185
  parsed
4350
- }),
4351
- quickModels
4352
- })));
4186
+ })
4187
+ });
4353
4188
  return;
4354
4189
  }
4355
4190
  if (parsed.action === "nav" && parsed.view === "models") {
@@ -4374,26 +4209,16 @@ async function handleDiscordModelPickerInteraction(params) {
4374
4209
  provider,
4375
4210
  model: pendingModel
4376
4211
  }) : void 0;
4377
- await updatePicker(toDiscordModelPickerMessagePayload(renderDiscordModelPickerModelsView({
4378
- command: parsed.command,
4379
- userId: parsed.userId,
4380
- data: pickerData,
4381
- provider,
4382
- page: parsed.page,
4383
- providerPage: parsed.providerPage ?? 1,
4384
- providerBucket: parsed.providerBucket ?? findProviderBucketId(pickerData, provider),
4212
+ await updateModelsView(provider, {
4385
4213
  modelBucket: parsed.modelBucket,
4386
- currentModel: currentModelRef,
4387
- currentRuntime,
4388
4214
  ...pendingModel ? { pendingModel: `${provider}/${pendingModel}` } : {},
4389
4215
  pendingModelIndex: pendingModelIndex ?? void 0,
4390
4216
  pendingRuntime: resolvePendingRuntime({
4391
4217
  data: pickerData,
4392
4218
  provider,
4393
4219
  parsed
4394
- }),
4395
- quickModels
4396
- })));
4220
+ })
4221
+ });
4397
4222
  return;
4398
4223
  }
4399
4224
  if (parsed.action === "back" && parsed.view === "models") {
@@ -4402,24 +4227,14 @@ async function handleDiscordModelPickerInteraction(params) {
4402
4227
  currentModelRef,
4403
4228
  data: pickerData
4404
4229
  });
4405
- await updatePicker(toDiscordModelPickerMessagePayload(renderDiscordModelPickerModelsView({
4406
- command: parsed.command,
4407
- userId: parsed.userId,
4408
- data: pickerData,
4409
- provider,
4410
- page: parsed.page ?? 1,
4411
- providerPage: parsed.providerPage ?? 1,
4412
- providerBucket: parsed.providerBucket ?? findProviderBucketId(pickerData, provider),
4230
+ await updateModelsView(provider, {
4413
4231
  modelBucket: parsed.modelBucket,
4414
- currentModel: currentModelRef,
4415
- currentRuntime,
4416
4232
  pendingRuntime: resolvePendingRuntime({
4417
4233
  data: pickerData,
4418
4234
  provider,
4419
4235
  parsed
4420
- }),
4421
- quickModels
4422
- })));
4236
+ })
4237
+ });
4423
4238
  return;
4424
4239
  }
4425
4240
  if (parsed.action === "provider") {
@@ -4428,18 +4243,10 @@ async function handleDiscordModelPickerInteraction(params) {
4428
4243
  await showNotice("Sorry, that provider isn't available anymore.");
4429
4244
  return;
4430
4245
  }
4431
- await updatePicker(toDiscordModelPickerMessagePayload(renderDiscordModelPickerModelsView({
4432
- command: parsed.command,
4433
- userId: parsed.userId,
4434
- data: pickerData,
4435
- provider: selectedProvider,
4246
+ await updateModelsView(selectedProvider, {
4436
4247
  page: 1,
4437
- providerPage: parsed.providerPage ?? parsed.page,
4438
- providerBucket: parsed.providerBucket ?? findProviderBucketId(pickerData, selectedProvider),
4439
- currentModel: currentModelRef,
4440
- currentRuntime,
4441
- quickModels
4442
- })));
4248
+ providerPage: parsed.providerPage ?? parsed.page
4249
+ });
4443
4250
  return;
4444
4251
  }
4445
4252
  if (parsed.action === "model") {
@@ -4459,28 +4266,16 @@ async function handleDiscordModelPickerInteraction(params) {
4459
4266
  return;
4460
4267
  }
4461
4268
  const modelRef = `${provider}/${selectedModel}`;
4462
- const derivedProviderBucket = parsed.providerBucket ?? findProviderBucketId(pickerData, provider);
4463
- const derivedModelBucket = parsed.modelBucket ?? findModelBucketId(pickerData, provider, selectedModel);
4464
- await updatePicker(toDiscordModelPickerMessagePayload(renderDiscordModelPickerModelsView({
4465
- command: parsed.command,
4466
- userId: parsed.userId,
4467
- data: pickerData,
4468
- provider,
4469
- page: parsed.page,
4470
- providerPage: parsed.providerPage ?? 1,
4471
- providerBucket: derivedProviderBucket,
4472
- modelBucket: derivedModelBucket,
4473
- currentModel: currentModelRef,
4474
- currentRuntime,
4269
+ await updateModelsView(provider, {
4270
+ modelBucket: parsed.modelBucket ?? findModelBucketId(pickerData, provider, selectedModel),
4475
4271
  pendingModel: modelRef,
4476
4272
  pendingModelIndex: modelIndex,
4477
4273
  pendingRuntime: resolvePendingRuntime({
4478
4274
  data: pickerData,
4479
4275
  provider,
4480
4276
  parsed
4481
- }),
4482
- quickModels
4483
- })));
4277
+ })
4278
+ });
4484
4279
  return;
4485
4280
  }
4486
4281
  if (parsed.action === "runtime") {
@@ -4507,25 +4302,13 @@ async function handleDiscordModelPickerInteraction(params) {
4507
4302
  provider,
4508
4303
  model: selectedModel
4509
4304
  }) : void 0;
4510
- const derivedProviderBucket = parsed.providerBucket ?? findProviderBucketId(pickerData, provider);
4511
4305
  const currentModelOnly = splitDiscordModelRef(currentModelRef ?? "");
4512
- const derivedModelBucket = parsed.modelBucket ?? (selectedModel ? findModelBucketId(pickerData, provider, selectedModel) : currentModelOnly && currentModelOnly.provider === provider ? findModelBucketId(pickerData, provider, currentModelOnly.model) : void 0);
4513
- await updatePicker(toDiscordModelPickerMessagePayload(renderDiscordModelPickerModelsView({
4514
- command: parsed.command,
4515
- userId: parsed.userId,
4516
- data: pickerData,
4517
- provider,
4518
- page: parsed.page,
4519
- providerPage: parsed.providerPage ?? 1,
4520
- providerBucket: derivedProviderBucket,
4521
- modelBucket: derivedModelBucket,
4522
- currentModel: currentModelRef,
4523
- currentRuntime,
4306
+ await updateModelsView(provider, {
4307
+ modelBucket: parsed.modelBucket ?? (selectedModel ? findModelBucketId(pickerData, provider, selectedModel) : currentModelOnly && currentModelOnly.provider === provider ? findModelBucketId(pickerData, provider, currentModelOnly.model) : void 0),
4524
4308
  ...pendingModel ? { pendingModel } : {},
4525
4309
  pendingModelIndex: pendingModelIndex ?? void 0,
4526
- pendingRuntime: selectedRuntime,
4527
- quickModels
4528
- })));
4310
+ pendingRuntime: selectedRuntime
4311
+ });
4529
4312
  return;
4530
4313
  }
4531
4314
  if (parsed.action === "submit" || parsed.action === "reset" || parsed.action === "quick") {
@@ -4548,10 +4331,12 @@ async function handleDiscordModelPickerInteraction(params) {
4548
4331
  data: pickerData,
4549
4332
  parsed,
4550
4333
  selectedProvider: parsedModelRef.provider
4551
- }),
4552
- currentRuntime
4334
+ })
4335
+ });
4336
+ const selectionCommand = buildDiscordModelPickerSelectionCommand({
4337
+ modelRef: resolvedModelRef,
4338
+ runtime: selectedRuntime
4553
4339
  });
4554
- const selectionCommand = buildDiscordModelPickerSelectionCommand({ modelRef: resolvedModelRef });
4555
4340
  if (!selectionCommand) {
4556
4341
  await showNotice("Sorry, /model is unavailable right now.");
4557
4342
  return;
@@ -4568,17 +4353,17 @@ async function handleDiscordModelPickerInteraction(params) {
4568
4353
  threadBindings: ctx.threadBindings,
4569
4354
  route,
4570
4355
  resolvedModelRef,
4571
- selectedProvider: parsedModelRef.provider,
4572
- selectedModel: parsedModelRef.model,
4573
4356
  selectedRuntime,
4574
- defaultProvider: pickerData.resolvedDefault.provider,
4575
- defaultModel: pickerData.resolvedDefault.model,
4576
4357
  preferenceScope,
4577
4358
  settleMs: ctx.postApplySettleMs ?? 250,
4578
4359
  resolveCurrentModel: (currentRoute) => resolveDiscordModelPickerCurrentModel({
4579
4360
  cfg,
4580
4361
  route: currentRoute,
4581
4362
  data: pickerData
4363
+ }),
4364
+ resolveCurrentRuntime: (currentRoute) => resolveDiscordModelPickerCurrentRuntime({
4365
+ cfg,
4366
+ route: currentRoute
4582
4367
  })
4583
4368
  });
4584
4369
  await params.safeInteractionCall("model picker follow-up", () => interaction.followUp({
@@ -4756,12 +4541,13 @@ function buildDiscordCommandOptions(params) {
4756
4541
  //#endregion
4757
4542
  //#region extensions/discord/src/monitor/native-command.ts
4758
4543
  const log = createSubsystemLogger("discord/native-command");
4544
+ const NON_PLUGIN_COMMAND_DISPATCH = Object.freeze({ kind: "non-plugin" });
4759
4545
  function createDiscordNativeCommand(params) {
4760
4546
  const { command, cfg, discordConfig, accountId, sessionPrefix, ephemeralDefault, threadBindings } = params;
4761
4547
  const fallbackCommandDefinition = createNativeCommandDefinition(command);
4762
- const pluginCommandMatch = nativeCommandRuntime.matchPluginCommand(`/${command.name}`);
4763
- const commandDefinition = pluginCommandMatch !== null ? fallbackCommandDefinition : findCommandByNativeName$1(command.name, "discord", { includeBundledChannelFallback: false }) ?? fallbackCommandDefinition;
4764
- const argDefinitions = commandDefinition.args ?? command.args;
4548
+ const pluginCommandCandidate = "prepareDispatch" in command ? command : void 0;
4549
+ const commandDefinition = pluginCommandCandidate ? fallbackCommandDefinition : findCommandByNativeName$1(command.name, "discord", { includeBundledChannelFallback: false }) ?? fallbackCommandDefinition;
4550
+ const argDefinitions = commandDefinition.args ?? ("args" in command ? command.args : void 0);
4765
4551
  const resolveCurrentConfig = () => getRuntimeConfigSnapshot() ?? cfg;
4766
4552
  const commandOptions = buildDiscordCommandOptions({
4767
4553
  command: commandDefinition,
@@ -4772,7 +4558,7 @@ function createDiscordNativeCommand(params) {
4772
4558
  cfg: resolveCurrentConfig(),
4773
4559
  discordConfig,
4774
4560
  accountId,
4775
- skipCommandOwnerAllowFrom: pluginCommandMatch !== null
4561
+ skipCommandOwnerAllowFrom: pluginCommandCandidate !== void 0
4776
4562
  }),
4777
4563
  resolveChoiceContext: async (interaction) => resolveDiscordNativeChoiceContext({
4778
4564
  interaction,
@@ -4810,9 +4596,11 @@ function createDiscordNativeCommand(params) {
4810
4596
  ...commandArgs,
4811
4597
  raw: serializeCommandArgs$1(commandDefinition, commandArgs) ?? commandArgs.raw
4812
4598
  } : void 0;
4599
+ const prompt = buildCommandTextFromArgs$1(commandDefinition, commandArgsWithRaw);
4600
+ const preparedPluginCommand = pluginCommandCandidate?.prepareDispatch(commandArgsWithRaw?.raw);
4813
4601
  await dispatchDiscordCommandInteraction({
4814
4602
  interaction,
4815
- prompt: buildCommandTextFromArgs$1(commandDefinition, commandArgsWithRaw),
4603
+ prompt,
4816
4604
  command: commandDefinition,
4817
4605
  commandArgs: commandArgsWithRaw,
4818
4606
  cfg,
@@ -4821,7 +4609,8 @@ function createDiscordNativeCommand(params) {
4821
4609
  sessionPrefix,
4822
4610
  preferFollowUp: true,
4823
4611
  threadBindings,
4824
- responseEphemeral: ephemeralDefault
4612
+ responseEphemeral: ephemeralDefault,
4613
+ pluginCommandDispatch: preparedPluginCommand ?? NON_PLUGIN_COMMAND_DISPATCH
4825
4614
  });
4826
4615
  }
4827
4616
  }();
@@ -4884,7 +4673,8 @@ async function dispatchDiscordCommandInteraction(params) {
4884
4673
  allowNameMatching
4885
4674
  });
4886
4675
  const commandOwnerAllowAll = commandOwnerAllowFrom?.includes("*") === true;
4887
- const senderIsCommandOwner = commandOwnerOk || commandOwnerAllowAll;
4676
+ const senderIsCommandOwner = commandOwnerOk;
4677
+ const commandOwnerAccessAllowed = senderIsCommandOwner || commandOwnerAllowAll;
4888
4678
  const ownerAllowListConfigured = discordOwnerAllowList != null;
4889
4679
  const ownerOk = discordOwnerOk;
4890
4680
  const { commandsAllowFromAccess, guildInfo, channelConfig } = resolveDiscordNativeCommandChannelAccessContext({
@@ -5024,8 +4814,7 @@ async function dispatchDiscordCommandInteraction(params) {
5024
4814
  return { accepted: false };
5025
4815
  }
5026
4816
  }
5027
- const pluginMatch = nativeCommandRuntime.matchPluginCommand(prompt);
5028
- if (commandOwnerAllowFrom && !senderIsCommandOwner && !commandsAllowFromAccess.allowed && commandName !== "status" && !pluginMatch) {
4817
+ if (commandOwnerAllowFrom && !commandOwnerAccessAllowed && !commandsAllowFromAccess.allowed && commandName !== "status" && params.pluginCommandDispatch.kind !== "plugin") {
5029
4818
  await respond("You are not authorized to use this command.", { ephemeral: true });
5030
4819
  return { accepted: false };
5031
4820
  }
@@ -5085,7 +4874,7 @@ async function dispatchDiscordCommandInteraction(params) {
5085
4874
  }));
5086
4875
  return { accepted: true };
5087
4876
  }
5088
- if (pluginMatch && commandName !== "status") {
4877
+ if (params.pluginCommandDispatch.kind === "plugin" && commandName !== "status") {
5089
4878
  if (suppressReplies) {
5090
4879
  await settleDiscordInteractionWithoutVisibleReply(interaction);
5091
4880
  return { accepted: true };
@@ -5099,9 +4888,7 @@ async function dispatchDiscordCommandInteraction(params) {
5099
4888
  agentId: pluginCommandAgentId,
5100
4889
  sessionKey: effectiveRoute.sessionKey
5101
4890
  });
5102
- const pluginReply = await nativeCommandRuntime.executePluginCommand({
5103
- command: pluginMatch.command,
5104
- args: pluginMatch.args,
4891
+ const pluginReply = await params.pluginCommandDispatch.execute({
5105
4892
  senderId: sender.id,
5106
4893
  channel: "discord",
5107
4894
  channelId,
@@ -5242,7 +5029,7 @@ async function dispatchDiscordCommandInteraction(params) {
5242
5029
  respond
5243
5030
  });
5244
5031
  if (directStatusResult) return directStatusResult;
5245
- await dispatchDiscordNativeAgentReply({
5032
+ const { dispatched, hiddenFinalReply } = await dispatchDiscordNativeAgentReply({
5246
5033
  cfg,
5247
5034
  discordConfig,
5248
5035
  accountId,
@@ -5254,11 +5041,13 @@ async function dispatchDiscordCommandInteraction(params) {
5254
5041
  preferFollowUp,
5255
5042
  responseEphemeral,
5256
5043
  suppressReplies,
5257
- log
5044
+ log,
5045
+ pluginCommandDispatch: params.pluginCommandDispatch
5258
5046
  });
5259
5047
  return {
5260
- accepted: true,
5261
- effectiveRoute
5048
+ accepted: dispatched,
5049
+ effectiveRoute,
5050
+ hiddenFinalReply
5262
5051
  };
5263
5052
  }
5264
5053
  function createDiscordCommandArgFallbackButton(params) {
@@ -5308,7 +5097,7 @@ function canResumeAfterGatewayClose(code) {
5308
5097
  //#region extensions/discord/src/internal/gateway-identify-limiter.ts
5309
5098
  const IDENTIFY_WINDOW_MS = 5e3;
5310
5099
  function normalizeMaxConcurrency(value) {
5311
- const parsed = parseFiniteNumber(value);
5100
+ const parsed = parseFiniteNumber$1(value);
5312
5101
  return parsed === void 0 ? 1 : Math.max(1, Math.floor(parsed));
5313
5102
  }
5314
5103
  var GatewayIdentifyLimiter = class {
@@ -6429,8 +6218,7 @@ function readStringProperty(value, key) {
6429
6218
  return typeof property === "string" && property ? property : void 0;
6430
6219
  }
6431
6220
  function readNumberProperty(value, key) {
6432
- const property = value[key];
6433
- return typeof property === "number" && Number.isFinite(property) ? property : void 0;
6221
+ return asFiniteNumber(value[key]);
6434
6222
  }
6435
6223
  function describeDiscordGatewayTransportError(error) {
6436
6224
  const code = readStringProperty(error, "code");
@@ -6480,7 +6268,8 @@ function resolveDiscordGatewayIntents(params) {
6480
6268
  const intentsConfig = params?.intentsConfig;
6481
6269
  const voiceEnabled = params?.voiceEnabled;
6482
6270
  const voiceStatesEnabled = intentsConfig?.voiceStates ?? voiceEnabled ?? false;
6483
- let intents = GatewayIntents.Guilds | GatewayIntents.GuildMessages | GatewayIntents.MessageContent | GatewayIntents.DirectMessages | GatewayIntents.GuildMessageReactions | GatewayIntents.DirectMessageReactions;
6271
+ let intents = GatewayIntents.Guilds | GatewayIntents.GuildMessages | GatewayIntents.DirectMessages | GatewayIntents.GuildMessageReactions | GatewayIntents.DirectMessageReactions;
6272
+ if (intentsConfig?.messageContent !== false) intents |= GatewayIntents.MessageContent;
6484
6273
  if (voiceStatesEnabled) intents |= GatewayIntents.GuildVoiceStates;
6485
6274
  if (intentsConfig?.presence) intents |= GatewayIntents.GuildPresences;
6486
6275
  if (intentsConfig?.guildMembers) intents |= GatewayIntents.GuildMembers;
@@ -6860,7 +6649,7 @@ async function waitForDiscordGatewayStop(params) {
6860
6649
  gateway?.disconnect?.();
6861
6650
  } finally {
6862
6651
  cleanup();
6863
- reject(toLintErrorObject(err, "Non-Error rejection"));
6652
+ reject(toErrorObject(err, "Non-Error rejection"));
6864
6653
  }
6865
6654
  };
6866
6655
  const onAbort = () => {
@@ -6881,12 +6670,15 @@ async function waitForDiscordGatewayStop(params) {
6881
6670
  params.registerForceStop?.(onForceStop);
6882
6671
  });
6883
6672
  }
6884
- function toLintErrorObject(value, fallbackMessage) {
6885
- if (value instanceof Error) return value;
6886
- if (typeof value === "string") return new Error(value);
6887
- const error = new Error(fallbackMessage, { cause: value });
6888
- if (typeof value === "object" && value !== null || typeof value === "function") Object.assign(error, value);
6889
- return error;
6673
+ //#endregion
6674
+ //#region extensions/discord/src/monitor/status.ts
6675
+ /** READY proves a prior terminal failure was repaired, so the account is restartable again. */
6676
+ function createDiscordReadyStatusPatch(at = Date.now()) {
6677
+ return channelReadyPatch({
6678
+ lastConnectedAt: at,
6679
+ lastEventAt: at,
6680
+ lastDisconnect: null
6681
+ });
6890
6682
  }
6891
6683
  //#endregion
6892
6684
  //#region extensions/discord/src/monitor/provider.lifecycle.ts
@@ -7018,11 +6810,7 @@ function createGatewayStatusObserver(params) {
7018
6810
  queuedForceStopError = err;
7019
6811
  };
7020
6812
  const pushConnectedStatus = (at) => {
7021
- params.pushStatus({
7022
- ...createConnectedChannelStatusPatch(at),
7023
- lastDisconnect: null,
7024
- lastError: null
7025
- });
6813
+ params.pushStatus(createDiscordReadyStatusPatch(at));
7026
6814
  };
7027
6815
  const startReadyWatch = () => {
7028
6816
  clearReadyWatch();
@@ -7046,6 +6834,7 @@ function createGatewayStatusObserver(params) {
7046
6834
  const error = /* @__PURE__ */ new Error(`discord gateway opened but did not reach READY within ${params.runtimeReadyTimeoutMs}ms`);
7047
6835
  params.pushStatus({
7048
6836
  connected: false,
6837
+ lifecycle: "recovering",
7049
6838
  lastEventAt: at,
7050
6839
  lastDisconnect: {
7051
6840
  at,
@@ -7074,8 +6863,14 @@ function createGatewayStatusObserver(params) {
7074
6863
  if (message.includes("Gateway websocket closed")) {
7075
6864
  clearReadyWatch();
7076
6865
  const code = parseGatewayCloseCode(message);
6866
+ const terminalDisconnect = code !== void 0 && isFatalGatewayCloseCode(code);
7077
6867
  params.pushStatus({
7078
6868
  connected: false,
6869
+ lifecycle: terminalDisconnect ? "blocked" : "recovering",
6870
+ ...terminalDisconnect ? {
6871
+ terminalDisconnect: true,
6872
+ lastError: message
6873
+ } : {},
7079
6874
  lastEventAt: at,
7080
6875
  lastDisconnect: {
7081
6876
  at,
@@ -7088,6 +6883,7 @@ function createGatewayStatusObserver(params) {
7088
6883
  clearReadyWatch();
7089
6884
  params.pushStatus({
7090
6885
  connected: false,
6886
+ lifecycle: "recovering",
7091
6887
  lastEventAt: at,
7092
6888
  lastError: message
7093
6889
  });
@@ -7118,11 +6914,7 @@ async function waitForGatewayReady(params) {
7118
6914
  if (await params.beforePoll?.() === "stop") return "stopped";
7119
6915
  if (params.gateway?.isConnected === true) {
7120
6916
  const at = Date.now();
7121
- params.pushStatus?.({
7122
- ...createConnectedChannelStatusPatch(at),
7123
- lastDisconnect: null,
7124
- lastError: null
7125
- });
6917
+ params.pushStatus?.(createDiscordReadyStatusPatch(at));
7126
6918
  return "ready";
7127
6919
  }
7128
6920
  if (Date.now() >= deadlineAt) return "timeout";
@@ -7144,6 +6936,7 @@ async function waitForGatewayReady(params) {
7144
6936
  params.runtime.error?.(danger(`discord: gateway READY wait timed out after ${params.readyTimeoutMs}ms; reconnecting with backoff (attempt ${attempt})`));
7145
6937
  params.pushStatus?.({
7146
6938
  connected: false,
6939
+ lifecycle: "recovering",
7147
6940
  lastEventAt: restartAt,
7148
6941
  lastDisconnect: {
7149
6942
  at: restartAt,
@@ -7252,7 +7045,7 @@ async function runDiscordGatewayLifecycle(params) {
7252
7045
  gatewayEmitter?.removeListener(DISCORD_GATEWAY_TRANSPORT_ACTIVITY_EVENT, onGatewayTransportActivity);
7253
7046
  if (params.voiceManager) {
7254
7047
  await params.voiceManager.destroy();
7255
- const { setDiscordTranscriptsVoiceManager } = await import("./transcripts-source-DIKGsTGT.js").then((n) => n.n);
7048
+ const { setDiscordTranscriptsVoiceManager } = await import("./transcripts-source-CfFMexGt.js").then((n) => n.n);
7256
7049
  setDiscordTranscriptsVoiceManager({
7257
7050
  accountId: params.accountId,
7258
7051
  manager: null
@@ -7267,7 +7060,7 @@ async function runDiscordGatewayLifecycle(params) {
7267
7060
  let discordVoiceRuntimePromise;
7268
7061
  let discordProviderSessionRuntimePromise;
7269
7062
  async function loadDiscordVoiceRuntime() {
7270
- const promise = discordVoiceRuntimePromise ?? import("./manager.runtime-Bjav3pfC.js");
7063
+ const promise = discordVoiceRuntimePromise ?? import("./voice-runtime-BEdqNcST.js");
7271
7064
  discordVoiceRuntimePromise = promise;
7272
7065
  try {
7273
7066
  return await promise;
@@ -7277,7 +7070,7 @@ async function loadDiscordVoiceRuntime() {
7277
7070
  }
7278
7071
  }
7279
7072
  async function loadDiscordProviderSessionRuntime() {
7280
- const promise = discordProviderSessionRuntimePromise ?? import("./provider-session.runtime-Bo88k0pV.js");
7073
+ const promise = discordProviderSessionRuntimePromise ?? import("./provider-session.runtime-BUEqD0rA.js");
7281
7074
  discordProviderSessionRuntimePromise = promise;
7282
7075
  try {
7283
7076
  return await promise;
@@ -7287,13 +7080,12 @@ async function loadDiscordProviderSessionRuntime() {
7287
7080
  }
7288
7081
  }
7289
7082
  const discordProviderRuntime = {
7290
- fetchDiscordApplicationId,
7083
+ probeDiscordApplicationId,
7291
7084
  createDiscordNativeCommand,
7292
7085
  runDiscordGatewayLifecycle,
7293
7086
  loadDiscordVoiceRuntime,
7294
7087
  loadDiscordProviderSessionRuntime,
7295
7088
  createClient: (...args) => new Client(...args),
7296
- getPluginCommandSpecs: void 0,
7297
7089
  resolveDiscordAccount,
7298
7090
  resolveNativeCommandsEnabled,
7299
7091
  resolveNativeSkillsEnabled,
@@ -7639,112 +7431,431 @@ async function cleanupDiscordProviderStartup(params) {
7639
7431
  if (!params.lifecycleStarted) params.threadBindings.stop();
7640
7432
  }
7641
7433
  //#endregion
7642
- //#region extensions/discord/src/monitor/provider.commands.ts
7643
- const loadPluginCommandSpecs = createLazyRuntimeNamedExport(() => import("openclaw/plugin-sdk/plugin-runtime"), "getPluginCommandSpecs");
7644
- async function appendPluginCommandSpecs(params) {
7645
- const merged = [...params.commandSpecs];
7646
- const existingNames = new Set(normalizeStringEntriesLower(merged.map((spec) => spec.name)));
7647
- const getPluginCommandSpecs = params.getPluginCommandSpecs ?? await loadPluginCommandSpecs();
7648
- for (const pluginCommand of getPluginCommandSpecs("discord", { config: params.cfg })) {
7649
- const normalizedName = normalizeLowercaseStringOrEmpty(pluginCommand.name);
7650
- if (!normalizedName) continue;
7651
- if (existingNames.has(normalizedName)) {
7652
- params.runtime.error?.(danger(`discord: plugin command "/${normalizedName}" duplicates an existing native command. Skipping.`));
7653
- continue;
7654
- }
7655
- existingNames.add(normalizedName);
7656
- merged.push({
7657
- name: pluginCommand.name,
7658
- description: pluginCommand.description,
7659
- acceptsArgs: pluginCommand.acceptsArgs
7660
- });
7661
- }
7662
- return merged;
7663
- }
7664
- async function resolveDiscordProviderCommandSpecs(params) {
7665
- const listSkillCommands = params.listSkillCommandsForAgents ?? listSkillCommandsForAgents;
7666
- const listNativeCommandSpecs = params.listNativeCommandSpecsForConfig ?? listNativeCommandSpecsForConfig;
7667
- const maxDiscordCommands = params.maxDiscordCommands ?? 100;
7668
- let skillCommands = params.nativeEnabled && params.nativeSkillsEnabled ? listSkillCommands({ cfg: params.cfg }) : [];
7669
- let commandSpecs = params.nativeEnabled ? listNativeCommandSpecs(params.cfg, {
7670
- skillCommands,
7671
- provider: "discord"
7672
- }) : [];
7673
- if (params.nativeEnabled) commandSpecs = await appendPluginCommandSpecs({
7674
- commandSpecs,
7675
- runtime: params.runtime,
7676
- cfg: params.cfg,
7677
- getPluginCommandSpecs: params.getPluginCommandSpecs
7434
+ //#region extensions/discord/src/voice/access.ts
7435
+ async function authorizeDiscordVoiceIngress(params) {
7436
+ const groupPolicy = params.groupPolicy ?? resolveOpenProviderRuntimeGroupPolicy({
7437
+ providerConfigPresent: params.cfg.channels?.discord !== void 0,
7438
+ groupPolicy: params.discordConfig.groupPolicy,
7439
+ defaultGroupPolicy: params.cfg.channels?.defaults?.groupPolicy
7440
+ }).groupPolicy;
7441
+ const guildInfo = resolveDiscordGuildEntry({
7442
+ guild: params.guild ?? {
7443
+ id: params.guildId,
7444
+ ...params.guildName ? { name: params.guildName } : {}
7445
+ },
7446
+ guildId: params.guildId,
7447
+ guildEntries: params.discordConfig.guilds
7678
7448
  });
7679
- const initialCommandCount = commandSpecs.length;
7680
- if (params.nativeEnabled && params.nativeSkillsEnabled && commandSpecs.length > maxDiscordCommands) {
7681
- skillCommands = [];
7682
- commandSpecs = listNativeCommandSpecs(params.cfg, {
7683
- skillCommands: [],
7684
- provider: "discord"
7685
- });
7686
- commandSpecs = await appendPluginCommandSpecs({
7687
- commandSpecs,
7688
- runtime: params.runtime,
7689
- cfg: params.cfg,
7690
- getPluginCommandSpecs: params.getPluginCommandSpecs
7691
- });
7692
- params.runtime.log?.(warn(`${initialCommandCount} commands exceed the ${maxDiscordCommands}-command Discord limit; removing per-skill commands and keeping /skill.`));
7693
- }
7694
- if (params.nativeEnabled && commandSpecs.length > maxDiscordCommands) params.runtime.log?.(warn(`${commandSpecs.length} commands exceed the ${maxDiscordCommands}-command Discord limit; some commands may fail to deploy.`));
7695
- return {
7696
- skillCommands,
7697
- commandSpecs
7449
+ const channelConfig = params.channelId ? resolveDiscordChannelConfigWithFallback({
7450
+ guildInfo,
7451
+ channelId: params.channelId,
7452
+ channelName: params.channelName,
7453
+ channelSlug: params.channelSlug,
7454
+ parentId: params.parentId,
7455
+ parentName: params.parentName,
7456
+ parentSlug: params.parentSlug,
7457
+ scope: params.scope
7458
+ }) : null;
7459
+ if (channelConfig?.enabled === false) return {
7460
+ ok: false,
7461
+ message: "This channel is disabled."
7462
+ };
7463
+ const channelAllowlistConfigured = Boolean(guildInfo?.channels) && Object.keys(guildInfo?.channels ?? {}).length > 0;
7464
+ if (!params.channelId && groupPolicy === "allowlist" && channelAllowlistConfigured) return {
7465
+ ok: false,
7466
+ message: `${params.channelLabel ?? "This channel"} is not allowlisted for voice commands.`
7467
+ };
7468
+ const channelAllowed = channelConfig ? channelConfig.allowed : !channelAllowlistConfigured;
7469
+ if (!isDiscordGroupAllowedByPolicy({
7470
+ groupPolicy,
7471
+ guildAllowlisted: Boolean(guildInfo),
7472
+ channelAllowlistConfigured,
7473
+ channelAllowed
7474
+ }) || channelConfig?.allowed === false) return {
7475
+ ok: false,
7476
+ message: `${params.channelLabel ?? "This channel"} is not allowlisted for voice commands.`
7477
+ };
7478
+ const { hasAccessRestrictions, memberAllowed } = resolveDiscordMemberAccessState({
7479
+ channelConfig,
7480
+ guildInfo,
7481
+ memberRoleIds: params.memberRoleIds,
7482
+ sender: params.sender,
7483
+ allowNameMatching: false
7484
+ });
7485
+ const admissionAllowList = normalizeDiscordAllowList(params.admissionAllowFrom ?? params.discordConfig.allowFrom ?? params.discordConfig.allowFrom, [
7486
+ "discord:",
7487
+ "user:",
7488
+ "pk:"
7489
+ ]);
7490
+ const admissionAllowed = admissionAllowList ? allowListMatches(admissionAllowList, params.sender, { allowNameMatching: false }) : false;
7491
+ const useAccessGroups = params.useAccessGroups ?? true;
7492
+ return resolveCommandAuthorizedFromAuthorizers({
7493
+ useAccessGroups,
7494
+ authorizers: useAccessGroups ? [{
7495
+ configured: admissionAllowList != null,
7496
+ allowed: admissionAllowed
7497
+ }, {
7498
+ configured: hasAccessRestrictions,
7499
+ allowed: memberAllowed
7500
+ }] : [{
7501
+ configured: hasAccessRestrictions,
7502
+ allowed: memberAllowed
7503
+ }],
7504
+ modeWhenAccessGroupsOff: "configured"
7505
+ }) ? {
7506
+ ok: true,
7507
+ channelConfig
7508
+ } : {
7509
+ ok: false,
7510
+ message: "You are not authorized to use this command."
7698
7511
  };
7699
7512
  }
7700
7513
  //#endregion
7701
- //#region extensions/discord/src/monitor/provider.config-log.ts
7702
- function formatThreadBindingDurationForConfigLabel(durationMs) {
7703
- const label = formatThreadBindingDurationLabel(durationMs);
7704
- return label === "disabled" ? "off" : label;
7514
+ //#region extensions/discord/src/voice/owner-access.ts
7515
+ function resolveDiscordVoiceAccess(params) {
7516
+ const commandOwnerAllowFrom = resolveDiscordCommandOwnerAllowFrom(params.cfg);
7517
+ if (commandOwnerAllowFrom) return {
7518
+ admissionAllowFrom: commandOwnerAllowFrom,
7519
+ ownerAllowFrom: commandOwnerAllowFrom
7520
+ };
7521
+ return {
7522
+ admissionAllowFrom: resolveDiscordAccountAllowFrom({
7523
+ cfg: params.cfg,
7524
+ accountId: params.accountId
7525
+ }) ?? params.discordConfig.allowFrom ?? [],
7526
+ ownerAllowFrom: []
7527
+ };
7705
7528
  }
7706
- function logDiscordResolvedConfig(params) {
7707
- const allowFromSummary = summarizeStringEntries({
7708
- entries: params.allowFrom ?? [],
7709
- limit: 4,
7710
- emptyText: "any"
7529
+ //#endregion
7530
+ //#region extensions/discord/src/voice/command.ts
7531
+ const VOICE_CHANNEL_TYPES = [ChannelType.GuildVoice, ChannelType.GuildStageVoice];
7532
+ const DISCORD_VOICE_COMMAND_SPEC = {
7533
+ name: "vc",
7534
+ description: "Voice channel controls",
7535
+ acceptsArgs: false
7536
+ };
7537
+ async function authorizeVoiceCommand(interaction, params, options) {
7538
+ const channelOverride = options?.channelOverride;
7539
+ const channel = channelOverride ? void 0 : interaction.channel;
7540
+ if (!interaction.guild) return {
7541
+ ok: false,
7542
+ message: "Voice commands are only available in guilds."
7543
+ };
7544
+ const user = interaction.user;
7545
+ if (!user) return {
7546
+ ok: false,
7547
+ message: "Unable to resolve command user."
7548
+ };
7549
+ const channelId = channelOverride?.id ?? channel?.id ?? "";
7550
+ const channelContext = await resolveDiscordThreadLikeChannelContext({
7551
+ client: interaction.client,
7552
+ channel: channelOverride ?? channel,
7553
+ channelIdFallback: channelId
7711
7554
  });
7712
- const groupDmChannelSummary = summarizeStringEntries({
7713
- entries: params.groupDmChannels ?? [],
7714
- limit: 4,
7715
- emptyText: "any"
7555
+ const channelName = channelOverride?.name ?? channelContext.channelName;
7556
+ const memberRoleIds = Array.isArray(interaction.rawData.member?.roles) ? interaction.rawData.member.roles.map((roleId) => roleId) : [];
7557
+ const sender = resolveDiscordSenderIdentity({
7558
+ author: user,
7559
+ member: interaction.rawData.member
7716
7560
  });
7717
- const guildSummary = summarizeStringEntries({
7718
- entries: Object.keys(params.guildEntries ?? {}),
7719
- limit: 4,
7720
- emptyText: "any"
7561
+ const voiceAccess = resolveDiscordVoiceAccess(params);
7562
+ const access = await authorizeDiscordVoiceIngress({
7563
+ cfg: params.cfg,
7564
+ discordConfig: params.discordConfig,
7565
+ accountId: params.accountId,
7566
+ groupPolicy: params.groupPolicy,
7567
+ useAccessGroups: params.useAccessGroups,
7568
+ guild: interaction.guild,
7569
+ guildId: interaction.guild.id,
7570
+ channelId,
7571
+ channelName,
7572
+ channelSlug: channelContext.channelSlug,
7573
+ parentId: channelOverride?.parentId ?? channelContext.threadParentId,
7574
+ parentName: channelContext.threadParentName,
7575
+ parentSlug: channelContext.threadParentSlug,
7576
+ scope: channelContext.isThreadChannel ? "thread" : "channel",
7577
+ channelLabel: channelId ? formatMention({ channelId }) : "This channel",
7578
+ memberRoleIds,
7579
+ admissionAllowFrom: voiceAccess.admissionAllowFrom,
7580
+ sender: {
7581
+ id: sender.id,
7582
+ name: sender.name,
7583
+ tag: sender.tag
7584
+ }
7721
7585
  });
7722
- logVerbose(`discord: config dm=${params.dmEnabled ? "on" : "off"} dmPolicy=${params.dmPolicy} allowFrom=${allowFromSummary} groupDm=${params.groupDmEnabled ? "on" : "off"} groupDmChannels=${groupDmChannelSummary} groupPolicy=${params.groupPolicy} guilds=${guildSummary} historyLimit=${params.historyLimit} mediaMaxMb=${Math.round(params.mediaMaxBytes / (1024 * 1024))} native=${params.nativeEnabled ? "on" : "off"} nativeSkills=${params.nativeSkillsEnabled ? "on" : "off"} accessGroups=${params.useAccessGroups ? "on" : "off"} threadBindings=${params.threadBindingsEnabled ? "on" : "off"} threadIdleTimeout=${formatThreadBindingDurationForConfigLabel(params.threadBindingIdleTimeoutMs)} threadMaxAge=${formatThreadBindingDurationForConfigLabel(params.threadBindingMaxAgeMs)}`);
7723
- }
7724
- //#endregion
7725
- //#region extensions/discord/src/monitor/provider.deploy-errors.ts
7726
- const DISCORD_DEPLOY_REJECTED_ENTRY_LIMIT = 3;
7727
- function attachDiscordDeployRequestBody(err, body) {
7728
- if (!err || typeof err !== "object" || body === void 0) return;
7729
- const deployErr = err;
7730
- if (deployErr.deployRequestBody === void 0) deployErr.deployRequestBody = body;
7731
- }
7732
- function attachDiscordDeployRestContext(err, context) {
7733
- if (!err || typeof err !== "object") return;
7734
- const deployErr = err;
7735
- deployErr.deployRestMethod = context.method;
7736
- deployErr.deployRestPath = context.path;
7737
- deployErr.deployRequestMs = context.requestMs;
7738
- if (typeof context.timeoutMs === "number" && Number.isFinite(context.timeoutMs)) deployErr.deployTimeoutMs = context.timeoutMs;
7586
+ if (!access.ok) return {
7587
+ ok: false,
7588
+ message: access.message
7589
+ };
7590
+ return {
7591
+ ok: true,
7592
+ guildId: interaction.guild.id
7593
+ };
7739
7594
  }
7740
- function stringifyDiscordDeployField(value) {
7741
- if (typeof value === "string") return JSON.stringify(value);
7742
- try {
7743
- return JSON.stringify(value);
7744
- } catch {
7745
- return inspect(value, {
7746
- depth: 2,
7747
- breakLength: 120
7595
+ async function resolveVoiceCommandRuntimeContext(interaction, params) {
7596
+ const guildId = interaction.guild?.id;
7597
+ if (!guildId) {
7598
+ await interaction.reply({
7599
+ content: "Unable to resolve guild for this command.",
7600
+ ephemeral: true
7601
+ });
7602
+ return null;
7603
+ }
7604
+ const manager = params.getManager();
7605
+ if (!manager) {
7606
+ await interaction.reply({
7607
+ content: "Voice manager is not available yet.",
7608
+ ephemeral: true
7609
+ });
7610
+ return null;
7611
+ }
7612
+ return {
7613
+ guildId,
7614
+ manager
7615
+ };
7616
+ }
7617
+ async function ensureVoiceCommandAccess(params) {
7618
+ const access = await authorizeVoiceCommand(params.interaction, params.context, { channelOverride: params.channelOverride });
7619
+ if (access.ok) return true;
7620
+ await params.interaction.reply({
7621
+ content: access.message ?? "Not authorized.",
7622
+ ephemeral: true
7623
+ });
7624
+ return false;
7625
+ }
7626
+ function createDiscordVoiceCommand(params) {
7627
+ const resolveSessionChannelId = (manager, guildId) => manager.status().find((entry) => entry.guildId === guildId)?.channelId;
7628
+ class JoinCommand extends Command {
7629
+ constructor(..._args) {
7630
+ super(..._args);
7631
+ this.name = "join";
7632
+ this.description = "Join a voice channel";
7633
+ this.defer = true;
7634
+ this.ephemeral = params.ephemeralDefault;
7635
+ this.options = [{
7636
+ name: "channel",
7637
+ description: "Voice channel to join",
7638
+ type: ApplicationCommandOptionType.Channel,
7639
+ required: true,
7640
+ channel_types: VOICE_CHANNEL_TYPES
7641
+ }];
7642
+ }
7643
+ async run(interaction) {
7644
+ const channel = await interaction.options.getChannel("channel", true);
7645
+ if (!channel || !("id" in channel)) {
7646
+ await interaction.reply({
7647
+ content: "Voice channel not found.",
7648
+ ephemeral: true
7649
+ });
7650
+ return;
7651
+ }
7652
+ const access = await authorizeVoiceCommand(interaction, params, { channelOverride: {
7653
+ id: channel.id,
7654
+ name: resolveDiscordChannelNameSafe(channel)
7655
+ } });
7656
+ if (!access.ok) {
7657
+ await interaction.reply({
7658
+ content: access.message ?? "Not authorized.",
7659
+ ephemeral: true
7660
+ });
7661
+ return;
7662
+ }
7663
+ if (!isVoiceChannelType(channel.type)) {
7664
+ await interaction.reply({
7665
+ content: "That is not a voice channel.",
7666
+ ephemeral: true
7667
+ });
7668
+ return;
7669
+ }
7670
+ const guildId = access.guildId ?? ("guildId" in channel ? channel.guildId : void 0);
7671
+ if (!guildId) {
7672
+ await interaction.reply({
7673
+ content: "Unable to resolve guild for this voice channel.",
7674
+ ephemeral: true
7675
+ });
7676
+ return;
7677
+ }
7678
+ const manager = params.getManager();
7679
+ if (!manager) {
7680
+ await interaction.reply({
7681
+ content: "Voice manager is not available yet.",
7682
+ ephemeral: true
7683
+ });
7684
+ return;
7685
+ }
7686
+ const result = await manager.join({
7687
+ guildId,
7688
+ channelId: channel.id
7689
+ });
7690
+ await interaction.reply({
7691
+ content: result.message,
7692
+ ephemeral: true
7693
+ });
7694
+ }
7695
+ }
7696
+ class LeaveCommand extends Command {
7697
+ constructor(..._args2) {
7698
+ super(..._args2);
7699
+ this.name = "leave";
7700
+ this.description = "Leave the current voice channel";
7701
+ this.defer = true;
7702
+ this.ephemeral = params.ephemeralDefault;
7703
+ }
7704
+ async run(interaction) {
7705
+ const runtimeContext = await resolveVoiceCommandRuntimeContext(interaction, params);
7706
+ if (!runtimeContext) return;
7707
+ const sessionChannelId = resolveSessionChannelId(runtimeContext.manager, runtimeContext.guildId);
7708
+ if (!await ensureVoiceCommandAccess({
7709
+ interaction,
7710
+ context: params,
7711
+ channelOverride: sessionChannelId ? { id: sessionChannelId } : void 0
7712
+ })) return;
7713
+ const result = await runtimeContext.manager.leave({ guildId: runtimeContext.guildId });
7714
+ await interaction.reply({
7715
+ content: result.message,
7716
+ ephemeral: true
7717
+ });
7718
+ }
7719
+ }
7720
+ class StatusCommand extends Command {
7721
+ constructor(..._args3) {
7722
+ super(..._args3);
7723
+ this.name = "status";
7724
+ this.description = "Show active voice sessions";
7725
+ this.defer = true;
7726
+ this.ephemeral = params.ephemeralDefault;
7727
+ }
7728
+ async run(interaction) {
7729
+ const runtimeContext = await resolveVoiceCommandRuntimeContext(interaction, params);
7730
+ if (!runtimeContext) return;
7731
+ const sessions = runtimeContext.manager.status().filter((entry) => entry.guildId === runtimeContext.guildId);
7732
+ const sessionChannelId = sessions[0]?.channelId;
7733
+ if (!await ensureVoiceCommandAccess({
7734
+ interaction,
7735
+ context: params,
7736
+ channelOverride: sessionChannelId ? { id: sessionChannelId } : void 0
7737
+ })) return;
7738
+ if (sessions.length === 0) {
7739
+ await interaction.reply({
7740
+ content: "No active voice sessions.",
7741
+ ephemeral: true
7742
+ });
7743
+ return;
7744
+ }
7745
+ const lines = sessions.map((entry) => `• ${formatMention({ channelId: entry.channelId })} (guild ${entry.guildId})`);
7746
+ await interaction.reply({
7747
+ content: lines.join("\n"),
7748
+ ephemeral: true
7749
+ });
7750
+ }
7751
+ }
7752
+ return new class extends CommandWithSubcommands {
7753
+ constructor(..._args4) {
7754
+ super(..._args4);
7755
+ this.name = DISCORD_VOICE_COMMAND_SPEC.name;
7756
+ this.description = DISCORD_VOICE_COMMAND_SPEC.description;
7757
+ this.subcommands = [
7758
+ new JoinCommand(),
7759
+ new LeaveCommand(),
7760
+ new StatusCommand()
7761
+ ];
7762
+ }
7763
+ }();
7764
+ }
7765
+ function isVoiceChannelType(type) {
7766
+ return type === ChannelType.GuildVoice || type === ChannelType.GuildStageVoice;
7767
+ }
7768
+ //#endregion
7769
+ //#region extensions/discord/src/monitor/provider.commands.ts
7770
+ const loadPluginCommandRuntime = createLazyRuntimeModule(() => import("openclaw/plugin-sdk/plugin-command-runtime"));
7771
+ async function resolveDiscordProviderCommandSpecs(params) {
7772
+ const listSkillCommands = params.listSkillCommandsForAgents ?? listSkillCommandsForAgents;
7773
+ const listNativeCommandSpecs = params.listNativeCommandSpecsForConfig ?? listNativeCommandSpecsForConfig;
7774
+ const maxDiscordCommands = params.maxDiscordCommands ?? 100;
7775
+ let pluginCommandRuntime;
7776
+ if (params.nativeEnabled) pluginCommandRuntime = (await loadPluginCommandRuntime()).createPluginCommandRuntime();
7777
+ const pluginCommandSpecs = pluginCommandRuntime?.listNativeCandidates("discord") ?? [];
7778
+ const onCollision = (normalizedName) => {
7779
+ params.runtime.error?.(danger(`discord: plugin command "/${normalizedName}" duplicates an existing native command. Skipping.`));
7780
+ };
7781
+ const mergePluginCommandSpecs = (primary, collisionHandler) => mergeNativeCommandSpecs({
7782
+ primary,
7783
+ secondary: pluginCommandSpecs,
7784
+ onCollision: collisionHandler
7785
+ });
7786
+ const listPrimaryCommandSpecs = (skillCommands) => {
7787
+ const standardSpecs = listNativeCommandSpecs(params.cfg, {
7788
+ skillCommands,
7789
+ provider: "discord"
7790
+ });
7791
+ if (!params.voiceEnabled) return standardSpecs;
7792
+ const voiceName = DISCORD_VOICE_COMMAND_SPEC.name;
7793
+ return [...standardSpecs.filter((spec) => normalizeLowercaseStringOrEmpty(spec.name) !== voiceName), DISCORD_VOICE_COMMAND_SPEC];
7794
+ };
7795
+ const provisionalCollisions = [];
7796
+ let skillCommands = params.nativeEnabled && params.nativeSkillsEnabled ? listSkillCommands({ cfg: params.cfg }) : [];
7797
+ let commandSpecs = params.nativeEnabled ? mergePluginCommandSpecs(listPrimaryCommandSpecs(skillCommands), (normalizedName) => provisionalCollisions.push(normalizedName)) : [];
7798
+ const initialCommandCount = commandSpecs.length;
7799
+ if (params.nativeEnabled && params.nativeSkillsEnabled && commandSpecs.length > maxDiscordCommands) {
7800
+ skillCommands = [];
7801
+ commandSpecs = mergePluginCommandSpecs(listPrimaryCommandSpecs([]), onCollision);
7802
+ params.runtime.log?.(warn(`${initialCommandCount} commands exceed the ${maxDiscordCommands}-command Discord limit; removing per-skill commands and keeping /skill.`));
7803
+ } else for (const normalizedName of provisionalCollisions) onCollision(normalizedName);
7804
+ if (params.nativeEnabled && commandSpecs.length > maxDiscordCommands) params.runtime.log?.(warn(`${commandSpecs.length} commands exceed the ${maxDiscordCommands}-command Discord limit; some commands may fail to deploy.`));
7805
+ if (commandSpecs.some((command) => "prepareDispatch" in command)) pluginCommandRuntime?.retainNativeCatalog("discord");
7806
+ return {
7807
+ skillCommands,
7808
+ commandSpecs
7809
+ };
7810
+ }
7811
+ //#endregion
7812
+ //#region extensions/discord/src/monitor/provider.config-log.ts
7813
+ function formatThreadBindingDurationForConfigLabel(durationMs) {
7814
+ const label = formatThreadBindingDurationLabel(durationMs);
7815
+ return label === "disabled" ? "off" : label;
7816
+ }
7817
+ function logDiscordResolvedConfig(params) {
7818
+ const allowFromSummary = summarizeStringEntries({
7819
+ entries: params.allowFrom ?? [],
7820
+ limit: 4,
7821
+ emptyText: "any"
7822
+ });
7823
+ const groupDmChannelSummary = summarizeStringEntries({
7824
+ entries: params.groupDmChannels ?? [],
7825
+ limit: 4,
7826
+ emptyText: "any"
7827
+ });
7828
+ const guildSummary = summarizeStringEntries({
7829
+ entries: Object.keys(params.guildEntries ?? {}),
7830
+ limit: 4,
7831
+ emptyText: "any"
7832
+ });
7833
+ logVerbose(`discord: config dm=${params.dmEnabled ? "on" : "off"} dmPolicy=${params.dmPolicy} allowFrom=${allowFromSummary} groupDm=${params.groupDmEnabled ? "on" : "off"} groupDmChannels=${groupDmChannelSummary} groupPolicy=${params.groupPolicy} guilds=${guildSummary} historyLimit=${params.historyLimit} mediaMaxMb=${Math.round(params.mediaMaxBytes / (1024 * 1024))} native=${params.nativeEnabled ? "on" : "off"} nativeSkills=${params.nativeSkillsEnabled ? "on" : "off"} accessGroups=${params.useAccessGroups ? "on" : "off"} threadBindings=${params.threadBindingsEnabled ? "on" : "off"} threadIdleTimeout=${formatThreadBindingDurationForConfigLabel(params.threadBindingIdleTimeoutMs)} threadMaxAge=${formatThreadBindingDurationForConfigLabel(params.threadBindingMaxAgeMs)}`);
7834
+ }
7835
+ //#endregion
7836
+ //#region extensions/discord/src/monitor/provider.deploy-errors.ts
7837
+ const DISCORD_DEPLOY_REJECTED_ENTRY_LIMIT = 3;
7838
+ function attachDiscordDeployRequestBody(err, body) {
7839
+ if (!err || typeof err !== "object" || body === void 0) return;
7840
+ const deployErr = err;
7841
+ if (deployErr.deployRequestBody === void 0) deployErr.deployRequestBody = body;
7842
+ }
7843
+ function attachDiscordDeployRestContext(err, context) {
7844
+ if (!err || typeof err !== "object") return;
7845
+ const deployErr = err;
7846
+ deployErr.deployRestMethod = context.method;
7847
+ deployErr.deployRestPath = context.path;
7848
+ deployErr.deployRequestMs = context.requestMs;
7849
+ if (typeof context.timeoutMs === "number" && Number.isFinite(context.timeoutMs)) deployErr.deployTimeoutMs = context.timeoutMs;
7850
+ }
7851
+ function stringifyDiscordDeployField(value) {
7852
+ if (typeof value === "string") return JSON.stringify(value);
7853
+ try {
7854
+ return JSON.stringify(value);
7855
+ } catch {
7856
+ return inspect(value, {
7857
+ depth: 2,
7858
+ breakLength: 120
7748
7859
  });
7749
7860
  }
7750
7861
  }
@@ -7762,13 +7873,6 @@ function resolveDiscordRejectedDeployEntriesSource(rawBody) {
7762
7873
  function readDiscordDeployObjectField(value, field) {
7763
7874
  return value && typeof value === "object" && field in value ? value[field] : void 0;
7764
7875
  }
7765
- function readFiniteNumber(value) {
7766
- if (typeof value === "number" && Number.isFinite(value)) return value;
7767
- if (typeof value === "string" && value.trim().length > 0) return parseStrictFiniteNumber(value);
7768
- }
7769
- function readNonNegativeInteger(value) {
7770
- return parseStrictNonNegativeInteger(value);
7771
- }
7772
7876
  function isAbortLikeError(err) {
7773
7877
  if (!err || typeof err !== "object") return false;
7774
7878
  const name = "name" in err && typeof err.name === "string" ? err.name : void 0;
@@ -7786,8 +7890,8 @@ function formatDiscordDeployRestOperation(err) {
7786
7890
  function formatDiscordDeployErrorMessage(err) {
7787
7891
  if (!isAbortLikeError(err)) return formatErrorMessage$1(err);
7788
7892
  const deployErr = err && typeof err === "object" ? err : {};
7789
- const requestMs = readFiniteNumber(deployErr.deployRequestMs);
7790
- const timeoutMs = readFiniteNumber(deployErr.deployTimeoutMs);
7893
+ const requestMs = parseFiniteNumber(deployErr.deployRequestMs);
7894
+ const timeoutMs = parseFiniteNumber(deployErr.deployTimeoutMs);
7791
7895
  const operation = formatDiscordDeployRestOperation(deployErr);
7792
7896
  if (!(requestMs !== void 0 || timeoutMs !== void 0 || deployErr.deployRestMethod !== void 0 || deployErr.deployRestPath !== void 0)) return "Discord REST request was aborted";
7793
7897
  const timing = [];
@@ -7800,8 +7904,8 @@ function formatDiscordDeployErrorMessage(err) {
7800
7904
  function resolveDiscordDeployRateLimitDetails(err) {
7801
7905
  if (!err || typeof err !== "object") return;
7802
7906
  const deployErr = err;
7803
- const status = readNonNegativeInteger(deployErr.status) ?? readNonNegativeInteger(deployErr.statusCode);
7804
- const retryAfterSeconds = readFiniteNumber(deployErr.retryAfter) ?? readFiniteNumber(readDiscordDeployObjectField(deployErr.rawBody, "retry_after"));
7907
+ const status = parseStrictNonNegativeInteger(deployErr.status) ?? parseStrictNonNegativeInteger(deployErr.statusCode);
7908
+ const retryAfterSeconds = parseFiniteNumber(deployErr.retryAfter) ?? parseFiniteNumber(readDiscordDeployObjectField(deployErr.rawBody, "retry_after"));
7805
7909
  if (!(err instanceof RateLimitError || status === 429 || retryAfterSeconds !== void 0)) return;
7806
7910
  const rawGlobal = readDiscordDeployObjectField(deployErr.rawBody, "global");
7807
7911
  const scope = typeof deployErr.scope === "string" && deployErr.scope.trim().length > 0 ? deployErr.scope : rawGlobal === true ? "global" : rawGlobal === false ? "route" : void 0;
@@ -7891,8 +7995,8 @@ function formatDiscordDeployErrorDetails(err) {
7891
7995
  function isDiscordDeployDailyCreateLimit(err) {
7892
7996
  if (!err || typeof err !== "object") return false;
7893
7997
  const deployErr = err;
7894
- const discordCode = readNonNegativeInteger(deployErr.discordCode);
7895
- const rawCode = readNonNegativeInteger(readDiscordDeployObjectField(deployErr.rawBody, "code"));
7998
+ const discordCode = parseStrictNonNegativeInteger(deployErr.discordCode);
7999
+ const rawCode = parseStrictNonNegativeInteger(readDiscordDeployObjectField(deployErr.rawBody, "code"));
7896
8000
  return (discordCode === 30034 || rawCode === 30034) && /daily application command creates/i.test(formatErrorMessage$1(err));
7897
8001
  }
7898
8002
  //#endregion
@@ -8011,430 +8115,98 @@ async function deployDiscordCommands(params) {
8011
8115
  function runDiscordCommandDeployInBackground(params) {
8012
8116
  if (!params.enabled) return;
8013
8117
  logDiscordStartupPhase$1({
8014
- runtime: params.runtime,
8015
- accountId: params.accountId,
8016
- phase: "deploy-commands:scheduled",
8017
- startAt: params.startupStartedAt,
8018
- details: "mode=reconcile background=true",
8019
- isVerbose: params.isVerbose
8020
- });
8021
- deployDiscordCommands(params).then(() => {
8022
- logDiscordStartupPhase$1({
8023
- runtime: params.runtime,
8024
- accountId: params.accountId,
8025
- phase: "deploy-commands:done",
8026
- startAt: params.startupStartedAt,
8027
- details: "background=true",
8028
- isVerbose: params.isVerbose
8029
- });
8030
- }).catch((err) => {
8031
- params.runtime.log?.(warn(`[${params.accountId}] slash command deploy failed in background (message send/receive unaffected): ${formatErrorMessage$1(err)}`));
8032
- });
8033
- }
8034
- //#endregion
8035
- //#region extensions/discord/src/monitor/agent-components-reply.ts
8036
- async function replySilently(interaction, params) {
8037
- try {
8038
- await interaction.reply(params);
8039
- } catch {}
8040
- }
8041
- //#endregion
8042
- //#region extensions/discord/src/activities/interaction.ts
8043
- const REGISTRATION_WIDGET_ID = "AAAAAAAAAAAAAAAAAAAAAA";
8044
- const PENDING_LAUNCH_WRITE_BUDGET_MS = 250;
8045
- var DiscordActivityButton = class extends Button {
8046
- constructor(ctx, deps) {
8047
- super();
8048
- this.ctx = ctx;
8049
- this.deps = deps;
8050
- this.label = "Open widget";
8051
- this.customId = buildDiscordActivityCustomId(REGISTRATION_WIDGET_ID);
8052
- this.customIdParser = parseDiscordActivityCustomIdForInteraction;
8053
- this.pendingLaunchFailureLogged = false;
8054
- }
8055
- logPendingLaunchFailure(error) {
8056
- if (this.pendingLaunchFailureLogged) return;
8057
- this.pendingLaunchFailureLogged = true;
8058
- this.deps.logError(`discord activity: failed to record pending launch: ${String(error)}`);
8059
- }
8060
- async run(interaction, data) {
8061
- if (typeof data.widgetId !== "string") {
8062
- await this.deps.reply(interaction, {
8063
- content: "This widget is no longer valid.",
8064
- ephemeral: true
8065
- });
8066
- return;
8067
- }
8068
- const runtime = getDiscordActivitiesRuntime();
8069
- const channelId = interaction.rawData.channel_id;
8070
- const discordUserId = interaction.userId;
8071
- if (!runtime || !channelId || !discordUserId) this.logPendingLaunchFailure(/* @__PURE__ */ new Error("missing activity runtime or interaction identity"));
8072
- else {
8073
- const write = runtime.store.recordPendingLaunch({
8074
- accountId: this.ctx.accountId,
8075
- channelId,
8076
- discordUserId,
8077
- widgetId: data.widgetId,
8078
- createdAt: Date.now()
8079
- }).then(() => "written").catch((error) => {
8080
- this.logPendingLaunchFailure(error);
8081
- return "failed";
8082
- });
8083
- let timer;
8084
- const timeout = new Promise((resolve) => {
8085
- timer = setTimeout(() => resolve("timeout"), PENDING_LAUNCH_WRITE_BUDGET_MS);
8086
- timer.unref?.();
8087
- });
8088
- try {
8089
- if (await Promise.race([write, timeout]) === "timeout") this.logPendingLaunchFailure(/* @__PURE__ */ new Error(`pending launch write exceeded ${PENDING_LAUNCH_WRITE_BUDGET_MS}ms`));
8090
- } finally {
8091
- if (timer) clearTimeout(timer);
8092
- }
8093
- }
8094
- await interaction.launchActivity();
8095
- }
8096
- };
8097
- function createDiscordActivityButton(ctx, applicationId, deps = {}) {
8098
- const runtime = getDiscordActivitiesRuntime();
8099
- if (!runtime || !runtime.isAccountEnabled(ctx.accountId, ctx.cfg)) return null;
8100
- if (applicationId) runtime.registerApplicationId(ctx.accountId, applicationId);
8101
- if (!runtime.resolveAccount(ctx.accountId, ctx.cfg)) return null;
8102
- return new DiscordActivityButton(ctx, {
8103
- reply: deps.reply ?? replySilently,
8104
- logError: deps.logError ?? logError
8105
- });
8106
- }
8107
- //#endregion
8108
- //#region extensions/discord/src/voice/access.ts
8109
- async function authorizeDiscordVoiceIngress(params) {
8110
- const groupPolicy = params.groupPolicy ?? resolveOpenProviderRuntimeGroupPolicy({
8111
- providerConfigPresent: params.cfg.channels?.discord !== void 0,
8112
- groupPolicy: params.discordConfig.groupPolicy,
8113
- defaultGroupPolicy: params.cfg.channels?.defaults?.groupPolicy
8114
- }).groupPolicy;
8115
- const guildInfo = resolveDiscordGuildEntry({
8116
- guild: params.guild ?? {
8117
- id: params.guildId,
8118
- ...params.guildName ? { name: params.guildName } : {}
8119
- },
8120
- guildId: params.guildId,
8121
- guildEntries: params.discordConfig.guilds
8122
- });
8123
- const channelConfig = params.channelId ? resolveDiscordChannelConfigWithFallback({
8124
- guildInfo,
8125
- channelId: params.channelId,
8126
- channelName: params.channelName,
8127
- channelSlug: params.channelSlug,
8128
- parentId: params.parentId,
8129
- parentName: params.parentName,
8130
- parentSlug: params.parentSlug,
8131
- scope: params.scope
8132
- }) : null;
8133
- if (channelConfig?.enabled === false) return {
8134
- ok: false,
8135
- message: "This channel is disabled."
8136
- };
8137
- const channelAllowlistConfigured = Boolean(guildInfo?.channels) && Object.keys(guildInfo?.channels ?? {}).length > 0;
8138
- if (!params.channelId && groupPolicy === "allowlist" && channelAllowlistConfigured) return {
8139
- ok: false,
8140
- message: `${params.channelLabel ?? "This channel"} is not allowlisted for voice commands.`
8141
- };
8142
- const channelAllowed = channelConfig ? channelConfig.allowed : !channelAllowlistConfigured;
8143
- if (!isDiscordGroupAllowedByPolicy({
8144
- groupPolicy,
8145
- guildAllowlisted: Boolean(guildInfo),
8146
- channelAllowlistConfigured,
8147
- channelAllowed
8148
- }) || channelConfig?.allowed === false) return {
8149
- ok: false,
8150
- message: `${params.channelLabel ?? "This channel"} is not allowlisted for voice commands.`
8151
- };
8152
- const { hasAccessRestrictions, memberAllowed } = resolveDiscordMemberAccessState({
8153
- channelConfig,
8154
- guildInfo,
8155
- memberRoleIds: params.memberRoleIds,
8156
- sender: params.sender,
8157
- allowNameMatching: false
8158
- });
8159
- const admissionAllowList = normalizeDiscordAllowList(params.admissionAllowFrom ?? params.discordConfig.allowFrom ?? params.discordConfig.allowFrom, [
8160
- "discord:",
8161
- "user:",
8162
- "pk:"
8163
- ]);
8164
- const admissionAllowed = admissionAllowList ? allowListMatches(admissionAllowList, params.sender, { allowNameMatching: false }) : false;
8165
- const useAccessGroups = params.useAccessGroups ?? true;
8166
- return resolveCommandAuthorizedFromAuthorizers({
8167
- useAccessGroups,
8168
- authorizers: useAccessGroups ? [{
8169
- configured: admissionAllowList != null,
8170
- allowed: admissionAllowed
8171
- }, {
8172
- configured: hasAccessRestrictions,
8173
- allowed: memberAllowed
8174
- }] : [{
8175
- configured: hasAccessRestrictions,
8176
- allowed: memberAllowed
8177
- }],
8178
- modeWhenAccessGroupsOff: "configured"
8179
- }) ? {
8180
- ok: true,
8181
- channelConfig
8182
- } : {
8183
- ok: false,
8184
- message: "You are not authorized to use this command."
8185
- };
8186
- }
8187
- //#endregion
8188
- //#region extensions/discord/src/voice/owner-access.ts
8189
- function resolveDiscordVoiceAccess(params) {
8190
- const commandOwnerAllowFrom = resolveDiscordCommandOwnerAllowFrom(params.cfg);
8191
- if (commandOwnerAllowFrom) return {
8192
- admissionAllowFrom: commandOwnerAllowFrom,
8193
- ownerAllowFrom: commandOwnerAllowFrom,
8194
- ownerAllowAll: commandOwnerAllowFrom.includes("*")
8195
- };
8196
- return {
8197
- admissionAllowFrom: resolveDiscordAccountAllowFrom({
8198
- cfg: params.cfg,
8199
- accountId: params.accountId
8200
- }) ?? params.discordConfig.allowFrom ?? params.discordConfig.allowFrom ?? [],
8201
- ownerAllowFrom: [],
8202
- ownerAllowAll: false
8203
- };
8204
- }
8205
- //#endregion
8206
- //#region extensions/discord/src/voice/command.ts
8207
- const VOICE_CHANNEL_TYPES = [ChannelType.GuildVoice, ChannelType.GuildStageVoice];
8208
- async function authorizeVoiceCommand(interaction, params, options) {
8209
- const channelOverride = options?.channelOverride;
8210
- const channel = channelOverride ? void 0 : interaction.channel;
8211
- if (!interaction.guild) return {
8212
- ok: false,
8213
- message: "Voice commands are only available in guilds."
8214
- };
8215
- const user = interaction.user;
8216
- if (!user) return {
8217
- ok: false,
8218
- message: "Unable to resolve command user."
8219
- };
8220
- const channelId = channelOverride?.id ?? channel?.id ?? "";
8221
- const channelContext = await resolveDiscordThreadLikeChannelContext({
8222
- client: interaction.client,
8223
- channel: channelOverride ?? channel,
8224
- channelIdFallback: channelId
8225
- });
8226
- const channelName = channelOverride?.name ?? channelContext.channelName;
8227
- const memberRoleIds = Array.isArray(interaction.rawData.member?.roles) ? interaction.rawData.member.roles.map((roleId) => roleId) : [];
8228
- const sender = resolveDiscordSenderIdentity({
8229
- author: user,
8230
- member: interaction.rawData.member
8231
- });
8232
- const voiceAccess = resolveDiscordVoiceAccess(params);
8233
- const access = await authorizeDiscordVoiceIngress({
8234
- cfg: params.cfg,
8235
- discordConfig: params.discordConfig,
8236
- accountId: params.accountId,
8237
- groupPolicy: params.groupPolicy,
8238
- useAccessGroups: params.useAccessGroups,
8239
- guild: interaction.guild,
8240
- guildId: interaction.guild.id,
8241
- channelId,
8242
- channelName,
8243
- channelSlug: channelContext.channelSlug,
8244
- parentId: channelOverride?.parentId ?? channelContext.threadParentId,
8245
- parentName: channelContext.threadParentName,
8246
- parentSlug: channelContext.threadParentSlug,
8247
- scope: channelContext.isThreadChannel ? "thread" : "channel",
8248
- channelLabel: channelId ? formatMention({ channelId }) : "This channel",
8249
- memberRoleIds,
8250
- admissionAllowFrom: voiceAccess.admissionAllowFrom,
8251
- sender: {
8252
- id: sender.id,
8253
- name: sender.name,
8254
- tag: sender.tag
8255
- }
8256
- });
8257
- if (!access.ok) return {
8258
- ok: false,
8259
- message: access.message
8260
- };
8261
- return {
8262
- ok: true,
8263
- guildId: interaction.guild.id
8264
- };
8265
- }
8266
- async function resolveVoiceCommandRuntimeContext(interaction, params) {
8267
- const guildId = interaction.guild?.id;
8268
- if (!guildId) {
8269
- await interaction.reply({
8270
- content: "Unable to resolve guild for this command.",
8271
- ephemeral: true
8272
- });
8273
- return null;
8274
- }
8275
- const manager = params.getManager();
8276
- if (!manager) {
8277
- await interaction.reply({
8278
- content: "Voice manager is not available yet.",
8279
- ephemeral: true
8280
- });
8281
- return null;
8282
- }
8283
- return {
8284
- guildId,
8285
- manager
8286
- };
8287
- }
8288
- async function ensureVoiceCommandAccess(params) {
8289
- const access = await authorizeVoiceCommand(params.interaction, params.context, { channelOverride: params.channelOverride });
8290
- if (access.ok) return true;
8291
- await params.interaction.reply({
8292
- content: access.message ?? "Not authorized.",
8293
- ephemeral: true
8294
- });
8295
- return false;
8296
- }
8297
- function createDiscordVoiceCommand(params) {
8298
- const resolveSessionChannelId = (manager, guildId) => manager.status().find((entry) => entry.guildId === guildId)?.channelId;
8299
- class JoinCommand extends Command {
8300
- constructor(..._args) {
8301
- super(..._args);
8302
- this.name = "join";
8303
- this.description = "Join a voice channel";
8304
- this.defer = true;
8305
- this.ephemeral = params.ephemeralDefault;
8306
- this.options = [{
8307
- name: "channel",
8308
- description: "Voice channel to join",
8309
- type: ApplicationCommandOptionType.Channel,
8310
- required: true,
8311
- channel_types: VOICE_CHANNEL_TYPES
8312
- }];
8313
- }
8314
- async run(interaction) {
8315
- const channel = await interaction.options.getChannel("channel", true);
8316
- if (!channel || !("id" in channel)) {
8317
- await interaction.reply({
8318
- content: "Voice channel not found.",
8319
- ephemeral: true
8320
- });
8321
- return;
8322
- }
8323
- const access = await authorizeVoiceCommand(interaction, params, { channelOverride: {
8324
- id: channel.id,
8325
- name: resolveDiscordChannelNameSafe(channel)
8326
- } });
8327
- if (!access.ok) {
8328
- await interaction.reply({
8329
- content: access.message ?? "Not authorized.",
8330
- ephemeral: true
8331
- });
8332
- return;
8333
- }
8334
- if (!isVoiceChannelType(channel.type)) {
8335
- await interaction.reply({
8336
- content: "That is not a voice channel.",
8337
- ephemeral: true
8338
- });
8339
- return;
8340
- }
8341
- const guildId = access.guildId ?? ("guildId" in channel ? channel.guildId : void 0);
8342
- if (!guildId) {
8343
- await interaction.reply({
8344
- content: "Unable to resolve guild for this voice channel.",
8345
- ephemeral: true
8346
- });
8347
- return;
8348
- }
8349
- const manager = params.getManager();
8350
- if (!manager) {
8351
- await interaction.reply({
8352
- content: "Voice manager is not available yet.",
8353
- ephemeral: true
8354
- });
8355
- return;
8356
- }
8357
- const result = await manager.join({
8358
- guildId,
8359
- channelId: channel.id
8360
- });
8361
- await interaction.reply({
8362
- content: result.message,
8363
- ephemeral: true
8364
- });
8365
- }
8118
+ runtime: params.runtime,
8119
+ accountId: params.accountId,
8120
+ phase: "deploy-commands:scheduled",
8121
+ startAt: params.startupStartedAt,
8122
+ details: "mode=reconcile background=true",
8123
+ isVerbose: params.isVerbose
8124
+ });
8125
+ deployDiscordCommands(params).then(() => {
8126
+ logDiscordStartupPhase$1({
8127
+ runtime: params.runtime,
8128
+ accountId: params.accountId,
8129
+ phase: "deploy-commands:done",
8130
+ startAt: params.startupStartedAt,
8131
+ details: "background=true",
8132
+ isVerbose: params.isVerbose
8133
+ });
8134
+ }).catch((err) => {
8135
+ params.runtime.log?.(warn(`[${params.accountId}] slash command deploy failed in background (message send/receive unaffected): ${formatErrorMessage$1(err)}`));
8136
+ });
8137
+ }
8138
+ //#endregion
8139
+ //#region extensions/discord/src/monitor/agent-components-reply.ts
8140
+ async function replySilently(interaction, params) {
8141
+ try {
8142
+ await interaction.reply(params);
8143
+ } catch {}
8144
+ }
8145
+ //#endregion
8146
+ //#region extensions/discord/src/activities/interaction.ts
8147
+ const REGISTRATION_WIDGET_ID = "AAAAAAAAAAAAAAAAAAAAAA";
8148
+ const PENDING_LAUNCH_WRITE_BUDGET_MS = 250;
8149
+ var DiscordActivityButton = class extends Button {
8150
+ constructor(ctx, deps) {
8151
+ super();
8152
+ this.ctx = ctx;
8153
+ this.deps = deps;
8154
+ this.label = "Open widget";
8155
+ this.customId = buildDiscordActivityCustomId(REGISTRATION_WIDGET_ID);
8156
+ this.customIdParser = parseDiscordActivityCustomIdForInteraction;
8157
+ this.pendingLaunchFailureLogged = false;
8366
8158
  }
8367
- class LeaveCommand extends Command {
8368
- constructor(..._args2) {
8369
- super(..._args2);
8370
- this.name = "leave";
8371
- this.description = "Leave the current voice channel";
8372
- this.defer = true;
8373
- this.ephemeral = params.ephemeralDefault;
8374
- }
8375
- async run(interaction) {
8376
- const runtimeContext = await resolveVoiceCommandRuntimeContext(interaction, params);
8377
- if (!runtimeContext) return;
8378
- const sessionChannelId = resolveSessionChannelId(runtimeContext.manager, runtimeContext.guildId);
8379
- if (!await ensureVoiceCommandAccess({
8380
- interaction,
8381
- context: params,
8382
- channelOverride: sessionChannelId ? { id: sessionChannelId } : void 0
8383
- })) return;
8384
- const result = await runtimeContext.manager.leave({ guildId: runtimeContext.guildId });
8385
- await interaction.reply({
8386
- content: result.message,
8159
+ logPendingLaunchFailure(error) {
8160
+ if (this.pendingLaunchFailureLogged) return;
8161
+ this.pendingLaunchFailureLogged = true;
8162
+ this.deps.logError(`discord activity: failed to record pending launch: ${String(error)}`);
8163
+ }
8164
+ async run(interaction, data) {
8165
+ if (typeof data.widgetId !== "string") {
8166
+ await this.deps.reply(interaction, {
8167
+ content: "This widget is no longer valid.",
8387
8168
  ephemeral: true
8388
8169
  });
8170
+ return;
8389
8171
  }
8390
- }
8391
- class StatusCommand extends Command {
8392
- constructor(..._args3) {
8393
- super(..._args3);
8394
- this.name = "status";
8395
- this.description = "Show active voice sessions";
8396
- this.defer = true;
8397
- this.ephemeral = params.ephemeralDefault;
8398
- }
8399
- async run(interaction) {
8400
- const runtimeContext = await resolveVoiceCommandRuntimeContext(interaction, params);
8401
- if (!runtimeContext) return;
8402
- const sessions = runtimeContext.manager.status().filter((entry) => entry.guildId === runtimeContext.guildId);
8403
- const sessionChannelId = sessions[0]?.channelId;
8404
- if (!await ensureVoiceCommandAccess({
8405
- interaction,
8406
- context: params,
8407
- channelOverride: sessionChannelId ? { id: sessionChannelId } : void 0
8408
- })) return;
8409
- if (sessions.length === 0) {
8410
- await interaction.reply({
8411
- content: "No active voice sessions.",
8412
- ephemeral: true
8413
- });
8414
- return;
8415
- }
8416
- const lines = sessions.map((entry) => `• ${formatMention({ channelId: entry.channelId })} (guild ${entry.guildId})`);
8417
- await interaction.reply({
8418
- content: lines.join("\n"),
8419
- ephemeral: true
8172
+ const runtime = getDiscordActivitiesRuntime();
8173
+ const channelId = interaction.rawData.channel_id;
8174
+ const discordUserId = interaction.userId;
8175
+ if (!runtime || !channelId || !discordUserId) this.logPendingLaunchFailure(/* @__PURE__ */ new Error("missing activity runtime or interaction identity"));
8176
+ else {
8177
+ const write = runtime.store.recordPendingLaunch({
8178
+ accountId: this.ctx.accountId,
8179
+ channelId,
8180
+ discordUserId,
8181
+ widgetId: data.widgetId,
8182
+ createdAt: Date.now()
8183
+ }).then(() => "written").catch((error) => {
8184
+ this.logPendingLaunchFailure(error);
8185
+ return "failed";
8186
+ });
8187
+ let timer;
8188
+ const timeout = new Promise((resolve) => {
8189
+ timer = setTimeout(() => resolve("timeout"), PENDING_LAUNCH_WRITE_BUDGET_MS);
8190
+ timer.unref?.();
8420
8191
  });
8192
+ try {
8193
+ if (await Promise.race([write, timeout]) === "timeout") this.logPendingLaunchFailure(/* @__PURE__ */ new Error(`pending launch write exceeded ${PENDING_LAUNCH_WRITE_BUDGET_MS}ms`));
8194
+ } finally {
8195
+ if (timer) clearTimeout(timer);
8196
+ }
8421
8197
  }
8198
+ await interaction.launchActivity();
8422
8199
  }
8423
- return new class extends CommandWithSubcommands {
8424
- constructor(..._args4) {
8425
- super(..._args4);
8426
- this.name = "vc";
8427
- this.description = "Voice channel controls";
8428
- this.subcommands = [
8429
- new JoinCommand(),
8430
- new LeaveCommand(),
8431
- new StatusCommand()
8432
- ];
8433
- }
8434
- }();
8435
- }
8436
- function isVoiceChannelType(type) {
8437
- return type === ChannelType.GuildVoice || type === ChannelType.GuildStageVoice;
8200
+ };
8201
+ function createDiscordActivityButton(ctx, applicationId, deps = {}) {
8202
+ const runtime = getDiscordActivitiesRuntime();
8203
+ if (!runtime || !runtime.isAccountEnabled(ctx.accountId, ctx.cfg)) return null;
8204
+ if (applicationId) runtime.registerApplicationId(ctx.accountId, applicationId);
8205
+ if (!runtime.resolveAccount(ctx.accountId, ctx.cfg)) return null;
8206
+ return new DiscordActivityButton(ctx, {
8207
+ reply: deps.reply ?? replySilently,
8208
+ logError: deps.logError ?? logError
8209
+ });
8438
8210
  }
8439
8211
  //#endregion
8440
8212
  //#region extensions/discord/src/monitor/agent-components-context.ts
@@ -8472,6 +8244,14 @@ async function ackComponentInteraction(params) {
8472
8244
  logError(`${params.label}: failed to acknowledge interaction: ${String(err)}`);
8473
8245
  }
8474
8246
  }
8247
+ async function replyUnavailableComponentInteraction(interaction, content) {
8248
+ try {
8249
+ await interaction.reply({
8250
+ content,
8251
+ ephemeral: true
8252
+ });
8253
+ } catch {}
8254
+ }
8475
8255
  function resolveDiscordChannelContext(interaction) {
8476
8256
  const channel = interaction.channel;
8477
8257
  const channelInfo = resolveDiscordChannelInfoSafe(channel);
@@ -8967,12 +8747,6 @@ function formatModalSubmissionText(entry, interaction) {
8967
8747
  if (lines.length === 1) lines.push("- (no values)");
8968
8748
  return lines.join("\n");
8969
8749
  }
8970
- function resolveDiscordInteractionId(interaction) {
8971
- const rawId = interaction.rawData && typeof interaction.rawData === "object" && "id" in interaction.rawData ? interaction.rawData.id : void 0;
8972
- if (typeof rawId === "string" && rawId.trim()) return rawId.trim();
8973
- if (typeof rawId === "number" && Number.isFinite(rawId)) return String(rawId);
8974
- return `discord-interaction:${Date.now()}`;
8975
- }
8976
8750
  //#endregion
8977
8751
  //#region extensions/discord/src/monitor/agent-components-helpers.ts
8978
8752
  const AGENT_BUTTON_KEY = "agent";
@@ -9046,7 +8820,7 @@ function stripDiscordInternalChannelLines(text) {
9046
8820
  return kept.join("\n");
9047
8821
  }
9048
8822
  function sanitizeDiscordFrontChannelText(text) {
9049
- return collapseExcessBlankLines(stripDiscordInternalChannelLines(stripPlainTextToolCallBlocks(sanitizeAssistantVisibleText(stripPlainTextToolCallBlocks(text))))).trim();
8823
+ return collapseExcessBlankLines(stripDiscordInternalChannelLines(stripPlainTextToolCallBlocks(sanitizeAssistantVisibleText(stripPlainTextToolCallBlocks(text, { resolveProtectedRanges: findCodeRegions })), { resolveProtectedRanges: findCodeRegions }))).trim();
9050
8824
  }
9051
8825
  function sanitizeDiscordFrontChannelReplyPayloads(payloads, options = {}) {
9052
8826
  const preserveVerboseToolProgress = options.kind === "tool";
@@ -9143,7 +8917,7 @@ function formatDiscordReasoningPayload(payload) {
9143
8917
  async function deliverDiscordReply(params) {
9144
8918
  params.runtime;
9145
8919
  const delivery = resolveDiscordDeliveryOptions(params);
9146
- const payloads = sanitizeDiscordFrontChannelReplyPayloads(params.replies, { kind: params.kind }).map(formatDiscordReasoningPayload);
8920
+ const payloads = sanitizeDiscordFrontChannelReplyPayloads(params.replies, { kind: params.kind }).map(formatDiscordReasoningPayload).map((payload) => params.bindPendingFinalDelivery?.(payload) ?? payload);
9147
8921
  if (payloads.length === 0) return {
9148
8922
  visibleReplySent: false,
9149
8923
  suppression: { reason: "no_visible_result" }
@@ -9173,7 +8947,7 @@ async function deliverDiscordReply(params) {
9173
8947
  requesterAccountId: params.accountId
9174
8948
  })
9175
8949
  });
9176
- if (send.status === "failed" || send.status === "partial_failed") throw send.error;
8950
+ if (send.status === "failed") throw send.error;
9177
8951
  if (send.status === "suppressed") {
9178
8952
  const hookEffect = send.payloadOutcomes?.find((outcome) => outcome.status === "suppressed")?.hookEffect;
9179
8953
  return {
@@ -9185,17 +8959,19 @@ async function deliverDiscordReply(params) {
9185
8959
  }
9186
8960
  };
9187
8961
  }
9188
- const results = send.results;
9189
- if (results.length === 0) throw new Error(`discord final reply produced no delivered message for ${delivery.to}`);
9190
- return {
9191
- messageIds: results.flatMap((result) => result.messageId ? [result.messageId] : []),
8962
+ if (send.results.length === 0) throw new Error(`discord final reply produced no delivered message for ${delivery.to}`);
8963
+ const deliveryResult = {
8964
+ messageIds: listMessageReceiptPlatformIds(send.receipt),
8965
+ receipt: send.receipt,
9192
8966
  visibleReplySent: true
9193
8967
  };
8968
+ if (send.status === "partial_failed") throw createChannelPartialDeliveryError(send.error, deliveryResult);
8969
+ return deliveryResult;
9194
8970
  }
9195
8971
  //#endregion
9196
8972
  //#region extensions/discord/src/monitor/agent-components.dispatch.ts
9197
8973
  const loadConversationRuntime$1 = createLazyRuntimeModule(() => import("./agent-components.runtime-CEPrf2SY.js"));
9198
- const loadTypingRuntime = createLazyRuntimeModule(() => import("./typing--Mzo2kM7.js").then((n) => n.n));
8974
+ const loadTypingRuntime = createLazyRuntimeModule(() => import("./typing-CS-nNVoe.js").then((n) => n.n));
9199
8975
  function buildDiscordComponentConversationLabel(params) {
9200
8976
  if (params.interactionCtx.isDirectMessage) return buildDirectLabel(params.interactionCtx.user);
9201
8977
  if (params.interactionCtx.isGroupDm) return `Group DM #${params.channelCtx.channelName ?? params.interactionCtx.channelId} channel id:${params.interactionCtx.channelId}`;
@@ -9425,7 +9201,8 @@ async function dispatchDiscordComponentEvent(params) {
9425
9201
  tableMode,
9426
9202
  chunkMode: resolveChunkMode(ctx.cfg, "discord", accountId),
9427
9203
  mediaLocalRoots,
9428
- kind: info.kind
9204
+ kind: info.kind,
9205
+ bindPendingFinalDelivery: info.bindPendingFinalDelivery
9429
9206
  });
9430
9207
  if (result.visibleReplySent) replyReference.markSent();
9431
9208
  return result;
@@ -9455,33 +9232,14 @@ async function dispatchDiscordComponentEvent(params) {
9455
9232
  }
9456
9233
  //#endregion
9457
9234
  //#region extensions/discord/src/interactive-dispatch.ts
9235
+ const dispatchDiscordInteractive = createChannelInteractiveDispatcher({
9236
+ channel: "discord",
9237
+ interactiveKey: "interaction"
9238
+ });
9458
9239
  async function dispatchDiscordPluginInteractiveHandler(params) {
9459
- return await dispatchPluginInteractiveHandler({
9460
- channel: "discord",
9461
- data: params.data,
9462
- dedupeId: params.interactionId,
9463
- onMatched: params.onMatched,
9464
- invoke: ({ registration, namespace, payload }) => registration.handler({
9465
- ...params.ctx,
9466
- channel: "discord",
9467
- interaction: {
9468
- ...params.ctx.interaction,
9469
- data: params.data,
9470
- namespace,
9471
- payload
9472
- },
9473
- respond: params.respond,
9474
- ...createInteractiveConversationBindingHelpers({
9475
- registration,
9476
- senderId: params.ctx.senderId,
9477
- conversation: {
9478
- channel: "discord",
9479
- accountId: params.ctx.accountId,
9480
- conversationId: params.ctx.conversationId,
9481
- parentConversationId: params.ctx.parentConversationId
9482
- }
9483
- })
9484
- })
9240
+ return await dispatchDiscordInteractive({
9241
+ ...params,
9242
+ dedupeId: params.interactionId
9485
9243
  });
9486
9244
  }
9487
9245
  //#endregion
@@ -9512,10 +9270,11 @@ async function dispatchPluginDiscordInteractiveEvent(params) {
9512
9270
  },
9513
9271
  reply: async ({ text, ephemeral = true }) => {
9514
9272
  responded = true;
9515
- await params.interaction.reply({
9273
+ const payload = {
9516
9274
  content: text,
9517
9275
  ephemeral
9518
- });
9276
+ };
9277
+ await (acknowledged ? params.interaction.followUp(payload) : params.interaction.reply(payload));
9519
9278
  },
9520
9279
  followUp: async ({ text, ephemeral = true }) => {
9521
9280
  responded = true;
@@ -9573,10 +9332,10 @@ async function dispatchPluginDiscordInteractiveEvent(params) {
9573
9332
  }
9574
9333
  const dispatched = await dispatchDiscordPluginInteractiveHandler({
9575
9334
  data: params.data,
9576
- interactionId: resolveDiscordInteractionId(params.interaction),
9335
+ interactionId: params.interaction.id,
9577
9336
  ctx: {
9578
9337
  accountId: params.ctx.accountId,
9579
- interactionId: resolveDiscordInteractionId(params.interaction),
9338
+ interactionId: params.interaction.id,
9580
9339
  conversationId: normalizedConversationId,
9581
9340
  parentConversationId: params.channelCtx.parentId,
9582
9341
  guildId: params.interactionCtx.rawGuildId,
@@ -9608,17 +9367,12 @@ async function dispatchPluginDiscordInteractiveEvent(params) {
9608
9367
  }
9609
9368
  //#endregion
9610
9369
  //#region extensions/discord/src/monitor/agent-components.handlers.ts
9611
- const loadComponentsRuntime = createLazyRuntimeModule(() => import("./outbound-session-route-H-HoyH5U.js").then((n) => n.n));
9370
+ const loadComponentsRuntime = createLazyRuntimeModule(() => import("./outbound-session-route-DxLkrgKQ.js").then((n) => n.n));
9612
9371
  async function handleDiscordComponentEvent(params) {
9613
9372
  const parsed = parseDiscordComponentData(params.data, resolveInteractionCustomId(params.interaction));
9614
9373
  if (!parsed) {
9615
9374
  logError(`${params.label}: failed to parse component data`);
9616
- try {
9617
- await params.interaction.reply({
9618
- content: "This component is no longer valid.",
9619
- ephemeral: true
9620
- });
9621
- } catch {}
9375
+ await replyUnavailableComponentInteraction(params.interaction, "This component is no longer valid.");
9622
9376
  return;
9623
9377
  }
9624
9378
  const entry = await resolveDiscordComponentEntryWithPersistence({
@@ -9626,12 +9380,7 @@ async function handleDiscordComponentEvent(params) {
9626
9380
  consume: false
9627
9381
  });
9628
9382
  if (!entry) {
9629
- try {
9630
- await params.interaction.reply({
9631
- content: "This component has expired.",
9632
- ephemeral: true
9633
- });
9634
- } catch {}
9383
+ await replyUnavailableComponentInteraction(params.interaction, "This component has expired.");
9635
9384
  return;
9636
9385
  }
9637
9386
  const unauthorizedReply = `You are not authorized to use this ${params.componentLabel}.`;
@@ -9659,21 +9408,11 @@ async function handleDiscordComponentEvent(params) {
9659
9408
  consume: !entry.reusable
9660
9409
  });
9661
9410
  if (!consumed) {
9662
- try {
9663
- await params.interaction.reply({
9664
- content: "This component has expired.",
9665
- ephemeral: true
9666
- });
9667
- } catch {}
9411
+ await replyUnavailableComponentInteraction(params.interaction, "This component has expired.");
9668
9412
  return;
9669
9413
  }
9670
9414
  if (consumed.kind === "modal-trigger") {
9671
- try {
9672
- await params.interaction.reply({
9673
- content: "This form is no longer available.",
9674
- ephemeral: true
9675
- });
9676
- } catch {}
9415
+ await replyUnavailableComponentInteraction(params.interaction, "This form is no longer available.");
9677
9416
  return;
9678
9417
  }
9679
9418
  const values = params.values ? mapSelectValues(consumed, params.values) : void 0;
@@ -9726,12 +9465,7 @@ async function handleDiscordModalTrigger(params) {
9726
9465
  const parsed = parseDiscordComponentData(params.data, resolveInteractionCustomId(params.interaction));
9727
9466
  if (!parsed) {
9728
9467
  logError(`${params.label}: failed to parse modal trigger data`);
9729
- try {
9730
- await params.interaction.reply({
9731
- content: "This button is no longer valid.",
9732
- ephemeral: true
9733
- });
9734
- } catch {}
9468
+ await replyUnavailableComponentInteraction(params.interaction, "This button is no longer valid.");
9735
9469
  return;
9736
9470
  }
9737
9471
  const entry = await resolveDiscordComponentEntryWithPersistence({
@@ -9739,22 +9473,12 @@ async function handleDiscordModalTrigger(params) {
9739
9473
  consume: false
9740
9474
  });
9741
9475
  if (!entry || entry.kind !== "modal-trigger") {
9742
- try {
9743
- await params.interaction.reply({
9744
- content: "This button has expired.",
9745
- ephemeral: true
9746
- });
9747
- } catch {}
9476
+ await replyUnavailableComponentInteraction(params.interaction, "This button has expired.");
9748
9477
  return;
9749
9478
  }
9750
9479
  const modalId = entry.modalId ?? parsed.modalId;
9751
9480
  if (!modalId) {
9752
- try {
9753
- await params.interaction.reply({
9754
- content: "This form is no longer available.",
9755
- ephemeral: true
9756
- });
9757
- } catch {}
9481
+ await replyUnavailableComponentInteraction(params.interaction, "This form is no longer available.");
9758
9482
  return;
9759
9483
  }
9760
9484
  const unauthorizedReply = "You are not authorized to use this form.";
@@ -9782,12 +9506,7 @@ async function handleDiscordModalTrigger(params) {
9782
9506
  consume: !entry.reusable
9783
9507
  });
9784
9508
  if (!consumed) {
9785
- try {
9786
- await params.interaction.reply({
9787
- content: "This form has expired.",
9788
- ephemeral: true
9789
- });
9790
- } catch {}
9509
+ await replyUnavailableComponentInteraction(params.interaction, "This form has expired.");
9791
9510
  return;
9792
9511
  }
9793
9512
  const modalEntry = await resolveDiscordModalEntryWithPersistence({
@@ -9795,18 +9514,14 @@ async function handleDiscordModalTrigger(params) {
9795
9514
  consume: false
9796
9515
  });
9797
9516
  if (!modalEntry) {
9798
- try {
9799
- await params.interaction.reply({
9800
- content: "This form has expired.",
9801
- ephemeral: true
9802
- });
9803
- } catch {}
9517
+ await replyUnavailableComponentInteraction(params.interaction, "This form has expired.");
9804
9518
  return;
9805
9519
  }
9806
9520
  try {
9807
9521
  await params.interaction.showModal((await loadComponentsRuntime()).createDiscordFormModal(modalEntry));
9808
9522
  } catch (err) {
9809
9523
  logError(`${params.label}: failed to show modal: ${String(err)}`);
9524
+ await replyUnavailableComponentInteraction(params.interaction, "Could not open this form. Request a new form and try again.");
9810
9525
  }
9811
9526
  }
9812
9527
  const discordComponentControlHandlers = {
@@ -9828,12 +9543,7 @@ var DiscordComponentModal = class extends Modal {
9828
9543
  const modalId = parseDiscordModalId(data, resolveInteractionCustomId(interaction));
9829
9544
  if (!modalId) {
9830
9545
  logError("discord component modal: missing modal id");
9831
- try {
9832
- await interaction.reply({
9833
- content: "This form is no longer valid.",
9834
- ephemeral: true
9835
- });
9836
- } catch {}
9546
+ await replyUnavailableComponentInteraction(interaction, "This form is no longer valid.");
9837
9547
  return;
9838
9548
  }
9839
9549
  const modalEntry = await resolveDiscordModalEntryWithPersistence({
@@ -9841,12 +9551,7 @@ var DiscordComponentModal = class extends Modal {
9841
9551
  consume: false
9842
9552
  });
9843
9553
  if (!modalEntry) {
9844
- try {
9845
- await interaction.reply({
9846
- content: "This form has expired.",
9847
- ephemeral: true
9848
- });
9849
- } catch {}
9554
+ await replyUnavailableComponentInteraction(interaction, "This form has expired.");
9850
9555
  return;
9851
9556
  }
9852
9557
  const unauthorizedReply = "You are not authorized to use this form.";
@@ -9879,12 +9584,7 @@ var DiscordComponentModal = class extends Modal {
9879
9584
  consume: !modalEntry.reusable
9880
9585
  });
9881
9586
  if (!consumed) {
9882
- try {
9883
- await interaction.reply({
9884
- content: "This form has expired.",
9885
- ephemeral: true
9886
- });
9887
- } catch {}
9587
+ await replyUnavailableComponentInteraction(interaction, "This form has expired.");
9888
9588
  return;
9889
9589
  }
9890
9590
  if (consumed.callbackData) {
@@ -9933,12 +9633,7 @@ async function runAgentSystemControlInteraction(params) {
9933
9633
  const parsed = parseAgentComponentData(params.data);
9934
9634
  if (!parsed) {
9935
9635
  logError(`${params.label}: failed to parse component data`);
9936
- try {
9937
- await params.interaction.reply({
9938
- content: params.invalidReply,
9939
- ephemeral: true
9940
- });
9941
- } catch {}
9636
+ await replyUnavailableComponentInteraction(params.interaction, params.invalidReply);
9942
9637
  return;
9943
9638
  }
9944
9639
  const { componentId } = parsed;
@@ -9979,10 +9674,7 @@ async function runAgentSystemControlInteraction(params) {
9979
9674
  userId
9980
9675
  });
9981
9676
  logDebug(`${params.label}: enqueuing event for channel ${channelId}: ${eventText}`);
9982
- enqueueSystemEvent$1(eventText, {
9983
- sessionKey: route.sessionKey,
9984
- contextKey: `${params.contextKeyPrefix}:${channelId}:${componentId}:${userId}`
9985
- });
9677
+ enqueueRoutedSystemEvent$1(eventText, route, { contextKey: `${params.contextKeyPrefix}:${channelId}:${componentId}:${userId}:${params.interaction.id}` });
9986
9678
  await ackComponentInteraction({
9987
9679
  interaction: params.interaction,
9988
9680
  replyOpts,
@@ -10237,7 +9929,7 @@ var ExecApprovalButton = class extends Button {
10237
9929
  try {
10238
9930
  await interaction.acknowledge();
10239
9931
  } catch {}
10240
- const result = await this.ctx.resolveApproval(parsed.approvalId, parsed.approvalKind, parsed.action);
9932
+ const result = await this.ctx.resolveApproval(parsed.approvalId, parsed.approvalKind, parsed.action, userId);
10241
9933
  if (!result.ok) {
10242
9934
  try {
10243
9935
  await interaction.followUp({
@@ -10274,7 +9966,7 @@ function createDiscordExecApprovalButtonContext(params) {
10274
9966
  accountId: params.accountId,
10275
9967
  configOverride: params.config
10276
9968
  }),
10277
- resolveApproval: async (approvalId, approvalKind, decision) => {
9969
+ resolveApproval: async (approvalId, approvalKind, decision, senderId) => {
10278
9970
  try {
10279
9971
  return {
10280
9972
  ok: true,
@@ -10283,8 +9975,10 @@ function createDiscordExecApprovalButtonContext(params) {
10283
9975
  approvalId,
10284
9976
  approvalKind,
10285
9977
  decision,
10286
- gatewayUrl: params.gatewayUrl,
10287
- clientDisplayName: `Discord approval (${params.accountId})`
9978
+ channel: "discord",
9979
+ accountId: params.accountId,
9980
+ senderId,
9981
+ gatewayUrl: params.gatewayUrl
10288
9982
  })
10289
9983
  };
10290
9984
  } catch (err) {
@@ -10368,24 +10062,26 @@ function createDiscordQuestionButton(params) {
10368
10062
  //#region extensions/discord/src/monitor/provider.interactions.ts
10369
10063
  function createDiscordProviderInteractionSurface(params) {
10370
10064
  const createNativeCommand = params.createNativeCommand ?? createDiscordNativeCommand;
10371
- const commands = params.commandSpecs.map((spec) => createNativeCommand({
10372
- command: spec,
10373
- cfg: params.cfg,
10374
- discordConfig: params.discordConfig,
10375
- accountId: params.accountId,
10376
- sessionPrefix: params.sessionPrefix,
10377
- ephemeralDefault: params.ephemeralDefault,
10378
- threadBindings: params.threadBindings
10379
- }));
10380
- if (params.nativeEnabled && params.voiceEnabled) commands.push(createDiscordVoiceCommand({
10381
- cfg: params.cfg,
10382
- discordConfig: params.discordConfig,
10383
- accountId: params.accountId,
10384
- groupPolicy: params.groupPolicy,
10385
- useAccessGroups: params.useAccessGroups,
10386
- getManager: () => params.voiceManagerRef.current,
10387
- ephemeralDefault: params.ephemeralDefault
10388
- }));
10065
+ const commands = params.commandSpecs.map((spec) => {
10066
+ if (params.nativeEnabled && params.voiceEnabled && spec.name === DISCORD_VOICE_COMMAND_SPEC.name) return createDiscordVoiceCommand({
10067
+ cfg: params.cfg,
10068
+ discordConfig: params.discordConfig,
10069
+ accountId: params.accountId,
10070
+ groupPolicy: params.groupPolicy,
10071
+ useAccessGroups: params.useAccessGroups,
10072
+ getManager: () => params.voiceManagerRef.current,
10073
+ ephemeralDefault: params.ephemeralDefault
10074
+ });
10075
+ return createNativeCommand({
10076
+ command: spec,
10077
+ cfg: params.cfg,
10078
+ discordConfig: params.discordConfig,
10079
+ accountId: params.accountId,
10080
+ sessionPrefix: params.sessionPrefix,
10081
+ ephemeralDefault: params.ephemeralDefault,
10082
+ threadBindings: params.threadBindings
10083
+ });
10084
+ });
10389
10085
  const execApprovalsConfig = params.discordConfig.execApprovals ?? {};
10390
10086
  const execApprovalsEnabled = isDiscordExecApprovalClientEnabled({
10391
10087
  cfg: params.cfg,
@@ -10665,7 +10361,8 @@ function registerDiscordMonitorListeners(params) {
10665
10361
  registerDiscordListener(params.client.listeners, new DiscordReactionListener(reactionListenerOptions));
10666
10362
  registerDiscordListener(params.client.listeners, new DiscordReactionRemoveListener(reactionListenerOptions));
10667
10363
  }
10668
- registerDiscordListener(params.client.listeners, new DiscordThreadUpdateListener(params.cfg, params.accountId, params.logger));
10364
+ registerDiscordListener(params.client.listeners, new DiscordThreadUpdateListener(params.cfg, params.logger));
10365
+ registerDiscordListener(params.client.listeners, new DiscordThreadDeleteListener(params.cfg, params.accountId, params.logger));
10669
10366
  if (params.discordConfig.intents?.presence) {
10670
10367
  const presenceListener = new DiscordPresenceListener({
10671
10368
  cfg: params.cfg,
@@ -10866,8 +10563,31 @@ async function monitorDiscordProvider(opts = {}) {
10866
10563
  phase: "fetch-application-id:start",
10867
10564
  startAt: startupStartedAt
10868
10565
  });
10869
- const applicationId = (typeof discordCfg.applicationId === "string" && discordCfg.applicationId.trim() ? discordCfg.applicationId.trim() : void 0) ?? parseApplicationIdFromToken(token) ?? await discordProviderRuntime.fetchDiscordApplicationId(token, 4e3, discordRestFetch);
10870
- if (!applicationId) throw new Error("Failed to resolve Discord application id");
10566
+ const parsedApplicationId = (typeof discordCfg.applicationId === "string" && discordCfg.applicationId.trim() ? discordCfg.applicationId.trim() : void 0) ?? parseApplicationIdFromToken(token);
10567
+ const applicationIdProbe = parsedApplicationId ? {
10568
+ kind: "resolved",
10569
+ applicationId: parsedApplicationId
10570
+ } : await discordProviderRuntime.probeDiscordApplicationId(token, 4e3, discordRestFetch);
10571
+ if (applicationIdProbe.kind !== "resolved") {
10572
+ const at = Date.now();
10573
+ const terminal = applicationIdProbe.kind === "rejected";
10574
+ const probeError = formatErrorMessage$1(applicationIdProbe.error);
10575
+ const message = `Failed to resolve Discord application id: ${probeError}`;
10576
+ opts.setStatus?.({
10577
+ connected: false,
10578
+ lifecycle: terminal ? "blocked" : "recovering",
10579
+ terminalDisconnect: terminal ? true : void 0,
10580
+ lastEventAt: at,
10581
+ lastDisconnect: {
10582
+ at,
10583
+ ...applicationIdProbe.status !== null ? { status: applicationIdProbe.status } : {},
10584
+ error: probeError
10585
+ },
10586
+ lastError: message
10587
+ });
10588
+ throw new Error(message, { cause: applicationIdProbe.error });
10589
+ }
10590
+ const applicationId = applicationIdProbe.applicationId;
10871
10591
  logDiscordStartupPhase({
10872
10592
  runtime,
10873
10593
  accountId: account.accountId,
@@ -10880,9 +10600,9 @@ async function monitorDiscordProvider(opts = {}) {
10880
10600
  runtime,
10881
10601
  nativeEnabled,
10882
10602
  nativeSkillsEnabled,
10603
+ voiceEnabled,
10883
10604
  listSkillCommandsForAgents: discordProviderRuntime.listSkillCommandsForAgents,
10884
- listNativeCommandSpecsForConfig: discordProviderRuntime.listNativeCommandSpecsForConfig,
10885
- getPluginCommandSpecs: discordProviderRuntime.getPluginCommandSpecs
10605
+ listNativeCommandSpecsForConfig: discordProviderRuntime.listNativeCommandSpecsForConfig
10886
10606
  });
10887
10607
  const voiceManagerRef = { current: null };
10888
10608
  const threadBindings = threadBindingsEnabled ? discordProviderSessionRuntime.createThreadBindingManager({
@@ -11014,7 +10734,7 @@ async function monitorDiscordProvider(opts = {}) {
11014
10734
  runtime,
11015
10735
  botUserId
11016
10736
  });
11017
- const { setDiscordTranscriptsVoiceManager } = await import("./transcripts-source-DIKGsTGT.js").then((n) => n.n);
10737
+ const { setDiscordTranscriptsVoiceManager } = await import("./transcripts-source-CfFMexGt.js").then((n) => n.n);
11018
10738
  setDiscordTranscriptsVoiceManager({
11019
10739
  accountId: account.accountId,
11020
10740
  manager: voiceManager
@@ -11032,6 +10752,7 @@ async function monitorDiscordProvider(opts = {}) {
11032
10752
  accountId: account.accountId,
11033
10753
  token,
11034
10754
  runtime,
10755
+ buildContext: opts.channelRuntime?.inbound.buildContext,
11035
10756
  setStatus: opts.setStatus,
11036
10757
  abortSignal: opts.abortSignal,
11037
10758
  botUserId,
@@ -11087,7 +10808,7 @@ async function monitorDiscordProvider(opts = {}) {
11087
10808
  gatewayReady: lifecycleGateway?.isConnected === true,
11088
10809
  botIdentity: botIdentity || void 0
11089
10810
  }));
11090
- if (lifecycleGateway?.isConnected) opts.setStatus?.(createConnectedChannelStatusPatch());
10811
+ if (lifecycleGateway?.isConnected) opts.setStatus?.(createDiscordReadyStatusPatch());
11091
10812
  lifecycleStarted = true;
11092
10813
  earlyGatewayEmitter?.removeListener("debug", onEarlyGatewayDebug);
11093
10814
  onEarlyGatewayDebug = void 0;