@openclaw/discord 2026.9.6 → 2026.9.8

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 (150) hide show
  1. package/assets/icon.png +0 -0
  2. package/dist/.setup/{account-inspect-BwQj24ht.mjs → account-inspect-De8hOUxG.mjs} +1 -1
  3. package/dist/.setup/{accounts-CwJQoLjM.mjs → accounts-ChaMcixG.mjs} +1 -4
  4. package/dist/.setup/{activities-api-COU4-0nR.mjs → activities-api-tvbsU2e3.mjs} +38 -66
  5. package/dist/.setup/{api-CBK9zcq5.mjs → api-DVbJwZVt.mjs} +10 -2
  6. package/dist/.setup/{approval-handler.runtime-CbBktBb4.mjs → approval-handler.runtime-DxCK2Rux.mjs} +49 -125
  7. package/dist/.setup/{approval-message-updates-jf9zMaIC.mjs → approval-message-updates-BKFqavHZ.mjs} +2 -2
  8. package/dist/.setup/{audit-vKbh-LFR.mjs → audit-CLg9I71N.mjs} +5 -5
  9. package/dist/.setup/{channel-Dv4e3Jpc.mjs → channel-DxlUVU9t.mjs} +121 -227
  10. package/dist/.setup/{channel-type-DKnjV1XW.mjs → channel-type-DRCrEMss.mjs} +22 -22
  11. package/dist/.setup/{channel.setup-0NX6FFwi.mjs → channel.setup-CRo1GjV7.mjs} +6 -9
  12. package/dist/.setup/{components-yBEb75bB.mjs → components-Cq1PvIpN.mjs} +41 -69
  13. package/dist/.setup/{config-schema-CiZz-mtA.mjs → config-schema-oxELUSDx.mjs} +19 -44
  14. package/dist/.setup/{conversation-binding-route-B7qImK_b.mjs → conversation-binding-route-Caz_XuFH.mjs} +10 -5
  15. package/dist/.setup/{directory-config-BytTftz-.mjs → directory-config-esqNn0Eo.mjs} +6 -9
  16. package/dist/.setup/{directory-live--EGTH0DC.mjs → directory-live-DtKRHuGV.mjs} +6 -12
  17. package/dist/.setup/{discord-BXpHW-cu.mjs → discord-0xJYfdDD.mjs} +82 -386
  18. package/dist/.setup/{doctor-D1R-uFIu.mjs → doctor-BCl1H5tl.mjs} +21 -36
  19. package/dist/.setup/{doctor-contract-Co5vRCZ6.mjs → doctor-contract-CvvgwrrG.mjs} +41 -50
  20. package/dist/.setup/{channel-actions.runtime-leZuw01r.mjs → handle-action-CUvQWnE7.mjs} +67 -104
  21. package/dist/.setup/{handle-action.guild-admin-DcFa8NwA.mjs → handle-action.guild-admin-Df-8gVle.mjs} +62 -120
  22. package/dist/.setup/{live-policy-config-eKQ-PLdy.mjs → live-policy-config-8Pv20CHc.mjs} +2 -3
  23. package/dist/.setup/{message-handler-X6ejmbtY.mjs → message-handler-B-Jk4oDk.mjs} +18 -94
  24. package/dist/.setup/{message-handler.preflight-pluralkit-8Xji34AF.mjs → message-handler.preflight-pluralkit-CWLUBVcx.mjs} +8 -10
  25. package/dist/.setup/{message-handler.preflight-DE_POxFc.mjs → message-handler.preflight-v-__NSDq.mjs} +69 -180
  26. package/dist/.setup/{message-handler.process-CCmIVnxy.mjs → message-handler.process-BNFFuChn.mjs} +65 -86
  27. package/dist/.setup/{outbound-adapter-ClIgmu4F.mjs → outbound-adapter-BobT-kse.mjs} +105 -19
  28. package/dist/.setup/{outbound-session-route-VulUE0yV.mjs → outbound-session-route-zVGxxceg.mjs} +140 -108
  29. package/dist/.setup/{pluralkit-CdAt1G0P.mjs → pluralkit-a6_NLfH9.mjs} +1 -1
  30. package/dist/.setup/{presence-cache-dYSHPWMx.mjs → presence-cache-Di3H3DIU.mjs} +0 -4
  31. package/dist/.setup/{probe-CupW4we4.mjs → probe-LYrIHlBF.mjs} +13 -4
  32. package/dist/.setup/{provider-iOf73HW-.mjs → provider-B_G5hASF.mjs} +721 -1416
  33. package/dist/.setup/provider-session.runtime-BYlTBFD2.mjs +6 -0
  34. package/dist/.setup/provider.runtime-CUE3UxER.mjs +2 -0
  35. package/dist/.setup/{realtime-session.runtime-Dz8uVutq.mjs → realtime-session.runtime-oUd9DOcz.mjs} +41 -55
  36. package/dist/.setup/{receive-recovery-DHst8WRx.mjs → receive-recovery-CHPOsnLc.mjs} +1 -4
  37. package/dist/.setup/{resolve-allowlist-common-CwMBzbx9.mjs → resolve-allowlist-common-DPdRggu5.mjs} +4 -7
  38. package/dist/.setup/{resolve-channels-BbvsLQOZ.mjs → resolve-channels-BFPNRmVv.mjs} +21 -40
  39. package/dist/.setup/{resolve-users-ByFlR7LQ.mjs → resolve-users-0-aBuCy3.mjs} +4 -4
  40. package/dist/.setup/{retry-BEYkDy0P.mjs → retry-pz_cSha2.mjs} +3 -3
  41. package/dist/.setup/rolldown-runtime-B4FMCO8f.mjs +28 -0
  42. package/dist/.setup/{runtime-B8lJfyrA.mjs → runtime-FN000lxA.mjs} +60 -162
  43. package/dist/.setup/runtime-doctor-migrations-CshUGl36.mjs +159 -0
  44. package/dist/.setup/{runtime-DisDbixd.mjs → runtime-sUksZ--Q.mjs} +2 -5
  45. package/dist/.setup/{secret-config-contract-DFu9hfU8.mjs → secret-config-contract-uOE2J7yM.mjs} +15 -85
  46. package/dist/.setup/{security-audit-s5w50-Ly.mjs → security-audit-Cdaegq7C.mjs} +3 -1
  47. package/dist/.setup/{send-CIBzvXjS.mjs → send-CrfEI6Bs.mjs} +53 -108
  48. package/dist/.setup/{send.components-ChY21qr-.mjs → send.components-DxQAMfES.mjs} +62 -107
  49. package/dist/.setup/{send.outbound-QTyuFupn.mjs → send.outbound-BISKnb-a.mjs} +66 -140
  50. package/dist/.setup/{send.shared-VNvWfX2T.mjs → send.shared-DGFkbkQR.mjs} +10 -25
  51. package/dist/.setup/{sender-identity-0Ymgvmaj.mjs → sender-identity-B9X5Vb9R.mjs} +2 -3
  52. package/dist/.setup/setup-plugin-api-BQUBxbhe.mjs +3 -0
  53. package/dist/.setup/{shared-DHPDaMSx.mjs → shared-B3wZ4hN-.mjs} +25 -32
  54. package/dist/.setup/{subagent-hooks-CjW7r8Pe.mjs → subagent-hooks-nlUE9ow7.mjs} +5 -6
  55. package/dist/.setup/system-events-CdeZnvPV.mjs +33 -0
  56. package/dist/.setup/{target-resolver-BXpy6VT8.mjs → target-resolver-BuLapkhy.mjs} +9 -12
  57. package/dist/.setup/targets-0KUcEIYx.mjs +3 -0
  58. package/dist/.setup/{thread-bindings-CEh2dhqP.mjs → thread-bindings-D7TfgjR_.mjs} +33 -35
  59. package/dist/.setup/{thread-bindings.manager-ve-i6oqt.mjs → thread-bindings.manager-DN-NissX.mjs} +325 -233
  60. package/dist/.setup/thread-bindings.session-updates-CshfKghV.mjs +359 -0
  61. package/dist/.setup/{thread-bindings.state-BThFQEga.mjs → thread-bindings.state-g6v9zAym.mjs} +21 -62
  62. package/dist/.setup/{transcripts-source-DVegW0WI.mjs → transcripts-source-p8EV179h.mjs} +76 -235
  63. package/dist/.setup/{typing-BKZL5fGj.mjs → typing-BiHCzBRu.mjs} +2 -1
  64. package/dist/.setup/{voice-runtime-elTtjEIG.mjs → voice-runtime-B46rcwrG.mjs} +140 -244
  65. package/dist/account-inspect-api.js +1 -1
  66. package/dist/action-runtime-api.js +1 -1
  67. package/dist/activities-api.js +1 -1
  68. package/dist/api.js +18 -18
  69. package/dist/channel-config-api.js +1 -1
  70. package/dist/channel-plugin-api.js +1 -1
  71. package/dist/directory-contract-api.js +1 -1
  72. package/dist/doctor-contract-api.js +4 -4
  73. package/dist/index.js +1 -1
  74. package/dist/runtime-api.js +20 -20
  75. package/dist/secret-contract-api.js +1 -1
  76. package/dist/security-audit-contract-api.js +1 -1
  77. package/dist/setup-entry.js +1 -1
  78. package/dist/setup-plugin-api.js +1 -1
  79. package/dist/src/voice/audio-worker.runtime.js +15 -24
  80. package/dist/subagent-hooks-api.js +3 -4
  81. package/dist/transcripts-source-api.js +1 -1
  82. package/node_modules/@types/node/README.md +2 -2
  83. package/node_modules/@types/node/child_process.d.ts +1 -1
  84. package/node_modules/@types/node/crypto.d.ts +20 -8
  85. package/node_modules/@types/node/ffi.d.ts +14 -0
  86. package/node_modules/@types/node/http2.d.ts +3 -2
  87. package/node_modules/@types/node/net.d.ts +16 -4
  88. package/node_modules/@types/node/package.json +2 -17
  89. package/node_modules/@types/node/process.d.ts +1 -0
  90. package/node_modules/@types/node/quic.d.ts +39 -1
  91. package/node_modules/@types/node/stream/iter.d.ts +9 -12
  92. package/node_modules/@types/node/test/reporters.d.ts +1 -0
  93. package/node_modules/@types/node/test.d.ts +72 -0
  94. package/node_modules/@types/node/tls.d.ts +14 -10
  95. package/node_modules/@types/node/vm.d.ts +2 -1
  96. package/node_modules/@types/node/worker_threads.d.ts +4 -1
  97. package/node_modules/typebox/build/compile/code.mjs +1 -2
  98. package/node_modules/typebox/build/compile/validator.mjs +5 -1
  99. package/node_modules/typebox/build/format/iri.mjs +16 -9
  100. package/node_modules/typebox/build/guard/emit.mjs +2 -2
  101. package/node_modules/typebox/build/guard/guard.d.mts +7 -5
  102. package/node_modules/typebox/build/guard/guard.mjs +14 -9
  103. package/node_modules/typebox/build/guard/index.mjs +6 -0
  104. package/node_modules/typebox/build/guard/unicode/unicode.d.mts +6 -0
  105. package/node_modules/typebox/build/guard/unicode/unicode.mjs +35 -0
  106. package/node_modules/typebox/build/guard/{string.mjs → unicode/unicode_segment.mjs} +16 -19
  107. package/node_modules/typebox/build/schema/build.mjs +2 -3
  108. package/node_modules/typebox/build/schema/engine/_pathing.d.mts +1 -0
  109. package/node_modules/typebox/build/schema/engine/_pathing.mjs +11 -0
  110. package/node_modules/typebox/build/schema/engine/_refine.mjs +13 -5
  111. package/node_modules/typebox/build/schema/engine/additionalProperties.mjs +2 -1
  112. package/node_modules/typebox/build/schema/engine/dependencies.mjs +2 -1
  113. package/node_modules/typebox/build/schema/engine/dependentSchemas.mjs +2 -1
  114. package/node_modules/typebox/build/schema/engine/dynamicRef.d.mts +1 -1
  115. package/node_modules/typebox/build/schema/engine/dynamicRef.mjs +2 -2
  116. package/node_modules/typebox/build/schema/engine/format.mjs +9 -2
  117. package/node_modules/typebox/build/schema/engine/patternProperties.mjs +3 -2
  118. package/node_modules/typebox/build/schema/engine/properties.mjs +3 -2
  119. package/node_modules/typebox/build/schema/engine/propertyNames.mjs +2 -1
  120. package/node_modules/typebox/build/schema/engine/recursiveRef.d.mts +1 -1
  121. package/node_modules/typebox/build/schema/engine/recursiveRef.mjs +2 -2
  122. package/node_modules/typebox/build/schema/engine/ref.d.mts +1 -1
  123. package/node_modules/typebox/build/schema/engine/ref.mjs +2 -2
  124. package/node_modules/typebox/build/schema/engine/schema.mjs +24 -24
  125. package/node_modules/typebox/build/schema/parse.mjs +3 -0
  126. package/node_modules/typebox/build/schema/pointer/pointer.d.mts +1 -6
  127. package/node_modules/typebox/build/schema/pointer/pointer.mjs +47 -64
  128. package/node_modules/typebox/build/system/settings/internal.d.mts +2 -0
  129. package/node_modules/typebox/build/system/settings/internal.mjs +20 -0
  130. package/node_modules/typebox/build/system/settings/settings.d.mts +11 -0
  131. package/node_modules/typebox/build/system/settings/settings.mjs +14 -0
  132. package/node_modules/typebox/build/type/extends/inference.d.mts +1 -1
  133. package/node_modules/typebox/build/type/extends/inference.mjs +15 -2
  134. package/node_modules/typebox/build/type/extends/tuple.d.mts +2 -1
  135. package/node_modules/typebox/build/type/extends/tuple.mjs +21 -1
  136. package/node_modules/typebox/build/value/parse/parse.mjs +10 -4
  137. package/node_modules/typebox/package.json +1 -1
  138. package/node_modules/typebox/readme.md +3 -1
  139. package/openclaw.plugin.json +21 -1
  140. package/package.json +5 -5
  141. package/dist/.setup/agent-components.runtime-CEPrf2SY.mjs +0 -4
  142. package/dist/.setup/probe.runtime-DBQ_AGPe.mjs +0 -2
  143. package/dist/.setup/provider-session.runtime-DrGAYnZv.mjs +0 -6
  144. package/dist/.setup/provider.runtime-BmjW_pIw.mjs +0 -2
  145. package/dist/.setup/security-audit.runtime-DHtYenx9.mjs +0 -2
  146. package/dist/.setup/setup-plugin-api-Dvr41dyx.mjs +0 -3
  147. package/dist/.setup/system-events-B2YEEMbS.mjs +0 -34
  148. package/dist/.setup/targets-CvemhN3i.mjs +0 -3
  149. package/dist/.setup/thread-bindings.session-updates-DCQq1WZ5.mjs +0 -55
  150. /package/node_modules/typebox/build/guard/{string.d.mts → unicode/unicode_segment.d.mts} +0 -0
@@ -1,19 +1,21 @@
1
- import { C as withDiscordRequestAuthority, Ut as __exportAll, pt as getChannel, tt as createChannelWebhook } from "./discord-BXpHW-cu.mjs";
2
- import { t as isDiscordThreadChannelType } from "./channel-type-DKnjV1XW.mjs";
3
- import { b as resolveDiscordChannelId, t as canFallbackDiscordWebhookSend } from "./retry-BEYkDy0P.mjs";
4
- import { P as createDiscordRestClient } from "./send.shared-VNvWfX2T.mjs";
5
- import { t as sendMessageDiscord } from "./send.outbound-QTyuFupn.mjs";
1
+ import { t as __exportAll } from "./rolldown-runtime-B4FMCO8f.mjs";
2
+ import { C as withDiscordRequestAuthority, mt as getChannel, nt as createChannelWebhook } from "./discord-0xJYfdDD.mjs";
3
+ import { t as isDiscordThreadChannelType } from "./channel-type-DRCrEMss.mjs";
4
+ import { t as canFallbackDiscordWebhookSend, x as resolveDiscordChannelId } from "./retry-pz_cSha2.mjs";
5
+ import { N as createDiscordRestClient } from "./send.shared-DGFkbkQR.mjs";
6
+ import { t as sendMessageDiscord } from "./send.outbound-BISKnb-a.mjs";
6
7
  import { n as resolveDiscordChannelInfoSafe, t as resolveDiscordChannelIdSafe } from "./channel-access-C12aDZ0p.mjs";
7
- import { c as sendWebhookMessageDiscord, u as createThreadDiscord } from "./send-CIBzvXjS.mjs";
8
- import { C as resolveThreadBindingMaxAgeMs$1, E as shouldDefaultPersist, T as setBindingRecord, _ as resolveBindingIdsForSession, a as THREAD_BINDING_TOUCH_PERSIST_MIN_INTERVAL_MS, b as resolveThreadBindingIdleTimeoutMs$1, c as forgetThreadBindingToken, d as normalizeThreadBindingDurationMs, f as normalizeThreadId, g as removeBindingRecord, h as rememberThreadBindingToken, i as REUSABLE_WEBHOOKS_BY_ACCOUNT_CHANNEL, k as toReusableWebhookKey, l as getThreadBindingToken, m as rememberReusableWebhook, n as MANAGERS_BY_ACCOUNT_ID, o as ensureBindingsLoaded, p as refreshUnboundThreadWebhookIdentity, r as PERSIST_BY_ACCOUNT_ID, s as ensureBindingsLoadedAsync, t as BINDINGS_BY_THREAD_ID, u as normalizeTargetKind, v as resolveBindingRecordKey, w as saveBindingsToDisk, y as resolvePreparedThreadBindingLifecycle } from "./thread-bindings.state-BThFQEga.mjs";
8
+ import { c as sendWebhookMessageDiscord, u as createThreadDiscord } from "./send-CrfEI6Bs.mjs";
9
+ import { C as resolvePreparedThreadBindingLifecycle, D as resolveThreadBindingMaxAgeMs$1, M as toReusableWebhookKey, S as resolveBindingRecordKey, _ as refreshUnboundThreadWebhookIdentity, c as THREAD_BINDING_TOUCH_PERSIST_MIN_INTERVAL_MS, d as forgetThreadBindingToken, f as getThreadBindingToken, h as normalizeThreadBindingDurationMs, i as REUSABLE_WEBHOOKS_BY_ACCOUNT_CHANNEL, j as toBindingRecordKey, k as shouldDefaultPersist, m as normalizeTargetKind, n as MANAGERS_BY_ACCOUNT_ID, r as PERSIST_BY_ACCOUNT_ID, t as BINDINGS_BY_THREAD_ID, u as ensureBindingsLoadedAsync, v as rememberReusableWebhook, w as resolveThreadBindingIdleTimeoutMs$1, x as resolveBindingIdsForSession, y as rememberThreadBindingToken } from "./thread-bindings.state-g6v9zAym.mjs";
10
+ import { c as drainThreadBindingAccountOperations, d as runThreadBindingAccountOperation, f as runThreadBindingMutation, h as updateBindingRecordSync, l as drainThreadBindingMutations, m as snapshotThreadBindingJson, p as shouldPersistAnyBindingState, s as commitBindingRecord } from "./thread-bindings.session-updates-CshfKghV.mjs";
9
11
  import { parseStrictNonNegativeInteger } from "openclaw/plugin-sdk/number-runtime";
10
12
  import { normalizeAccountId } from "openclaw/plugin-sdk/routing";
11
- import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
13
+ import { asOptionalObjectRecord, normalizeOptionalString, normalizeOptionalStringifiedId } from "openclaw/plugin-sdk/string-coerce-runtime";
12
14
  import { getRuntimeConfigSnapshot } from "openclaw/plugin-sdk/runtime-config-snapshot";
13
15
  import { createSubsystemLogger, logVerbose } from "openclaw/plugin-sdk/runtime-env";
14
16
  import { truncateUtf16Safe } from "openclaw/plugin-sdk/text-utility-runtime";
15
17
  import { SYSTEM_MARK } from "openclaw/plugin-sdk/text-chunking";
16
- import { formatThreadBindingDurationLabel, registerSessionBindingAdapter, resolveThreadBindingConversationIdFromBindingId, resolveThreadBindingFarewellText, resolveThreadBindingIntroText, resolveThreadBindingThreadName, unregisterSessionBindingAdapter } from "openclaw/plugin-sdk/conversation-runtime";
18
+ import { registerSessionBindingAdapter, resolveThreadBindingConversationIdFromBindingId, resolveThreadBindingFarewellText, resolveThreadBindingThreadName, unregisterSessionBindingAdapter } from "openclaw/plugin-sdk/conversation-runtime";
17
19
  import { resolveSessionAgentIdStrict } from "openclaw/plugin-sdk/agent-scope-runtime";
18
20
  //#region extensions/discord/src/monitor/thread-bindings.persona.ts
19
21
  const THREAD_BINDING_PERSONA_MAX_CHARS = 80;
@@ -26,10 +28,7 @@ function resolveThreadBindingPersona(params) {
26
28
  return truncateUtf16Safe(`${SYSTEM_MARK} ${base}`, THREAD_BINDING_PERSONA_MAX_CHARS);
27
29
  }
28
30
  function resolveThreadBindingPersonaFromRecord(record) {
29
- return resolveThreadBindingPersona({
30
- label: record.label,
31
- agentId: record.agentId
32
- });
31
+ return resolveThreadBindingPersona(record);
33
32
  }
34
33
  //#endregion
35
34
  //#region extensions/discord/src/monitor/thread-bindings.types.ts
@@ -44,10 +43,7 @@ function buildThreadTarget(threadId) {
44
43
  function isThreadArchived(raw) {
45
44
  if (!raw || typeof raw !== "object") return false;
46
45
  const asRecord = raw;
47
- if (asRecord.archived === true) return true;
48
- if (asRecord.thread_metadata?.archived === true) return true;
49
- if (asRecord.threadMetadata?.archived === true) return true;
50
- return false;
46
+ return asRecord.archived === true || asRecord.thread_metadata?.archived === true || asRecord.threadMetadata?.archived === true;
51
47
  }
52
48
  function normalizeDiscordBindingChannelId(raw) {
53
49
  const trimmed = normalizeOptionalString(raw) ?? "";
@@ -64,18 +60,15 @@ function summarizeDiscordError(err) {
64
60
  if (typeof err === "number" || typeof err === "boolean" || typeof err === "bigint" || typeof err === "symbol") return String(err);
65
61
  return "error";
66
62
  }
67
- function extractNumericDiscordErrorValue(value) {
68
- return parseStrictNonNegativeInteger(value);
69
- }
70
63
  function extractDiscordErrorStatus(err) {
71
64
  if (!err || typeof err !== "object") return;
72
65
  const candidate = err;
73
- return extractNumericDiscordErrorValue(candidate.status) ?? extractNumericDiscordErrorValue(candidate.statusCode) ?? extractNumericDiscordErrorValue(candidate.response?.status);
66
+ return parseStrictNonNegativeInteger(candidate.status) ?? parseStrictNonNegativeInteger(candidate.statusCode) ?? parseStrictNonNegativeInteger(candidate.response?.status);
74
67
  }
75
68
  function extractDiscordErrorCode(err) {
76
69
  if (!err || typeof err !== "object") return;
77
70
  const candidate = err;
78
- return extractNumericDiscordErrorValue(candidate.code) ?? extractNumericDiscordErrorValue(candidate.rawError?.code) ?? extractNumericDiscordErrorValue(candidate.body?.code) ?? extractNumericDiscordErrorValue(candidate.response?.body?.code) ?? extractNumericDiscordErrorValue(candidate.response?.data?.code);
71
+ return parseStrictNonNegativeInteger(candidate.code) ?? parseStrictNonNegativeInteger(candidate.rawError?.code) ?? parseStrictNonNegativeInteger(candidate.body?.code) ?? parseStrictNonNegativeInteger(candidate.response?.body?.code) ?? parseStrictNonNegativeInteger(candidate.response?.data?.code);
79
72
  }
80
73
  function isDiscordThreadGoneError(err) {
81
74
  if (extractDiscordErrorCode(err) === 10003) return true;
@@ -207,21 +200,6 @@ async function createThreadForBinding(params) {
207
200
  }
208
201
  //#endregion
209
202
  //#region extensions/discord/src/monitor/thread-bindings.session-adapter.ts
210
- function normalizeChildBindingParentChannelId(raw) {
211
- const trimmed = normalizeOptionalString(raw) ?? "";
212
- if (!trimmed) return;
213
- try {
214
- return resolveDiscordChannelId(trimmed);
215
- } catch {
216
- return;
217
- }
218
- }
219
- function toSessionBindingTargetKind(raw) {
220
- return raw === "subagent" ? "subagent" : "session";
221
- }
222
- function toThreadBindingTargetKind(raw) {
223
- return raw === "subagent" ? "subagent" : "acp";
224
- }
225
203
  function toSessionBindingRecord(record, defaults) {
226
204
  const bindingId = resolveBindingRecordKey({
227
205
  accountId: record.accountId,
@@ -234,7 +212,7 @@ function toSessionBindingRecord(record, defaults) {
234
212
  return {
235
213
  bindingId,
236
214
  targetSessionKey: record.targetSessionKey,
237
- targetKind: toSessionBindingTargetKind(record.targetKind),
215
+ targetKind: record.targetKind === "subagent" ? "subagent" : "session",
238
216
  conversation: {
239
217
  channel: "discord",
240
218
  accountId: record.accountId,
@@ -270,18 +248,19 @@ function createThreadBindingSessionAdapter(params) {
270
248
  if (!targetSessionKey) return null;
271
249
  const conversationId = normalizeOptionalString(input.conversation.conversationId) ?? "";
272
250
  const placement = input.placement === "child" ? "child" : "current";
273
- const metadata = input.metadata ?? {};
251
+ const metadata = asOptionalObjectRecord(snapshotThreadBindingJson(input.metadata ? { ...input.metadata } : void 0)) ?? {};
252
+ const targetKind = input.targetKind === "subagent" ? "subagent" : "acp";
274
253
  const label = normalizeOptionalString(metadata.label);
275
- const threadName = typeof metadata.threadName === "string" ? normalizeOptionalString(metadata.threadName) : void 0;
276
- const introText = typeof metadata.introText === "string" ? normalizeOptionalString(metadata.introText) : void 0;
277
- const boundBy = typeof metadata.boundBy === "string" ? normalizeOptionalString(metadata.boundBy) : void 0;
278
- const agentId = typeof metadata.agentId === "string" ? normalizeOptionalString(metadata.agentId) : void 0;
254
+ const threadName = normalizeOptionalString(metadata.threadName);
255
+ const introText = normalizeOptionalString(metadata.introText);
256
+ const boundBy = normalizeOptionalString(metadata.boundBy);
257
+ const agentId = normalizeOptionalString(metadata.agentId);
279
258
  let threadId;
280
259
  let channelId;
281
260
  let createThread = false;
282
261
  if (placement === "child") {
283
262
  createThread = true;
284
- channelId = normalizeChildBindingParentChannelId(input.conversation.parentConversationId);
263
+ channelId = normalizeDiscordBindingChannelId(input.conversation.parentConversationId) ?? void 0;
285
264
  if (!channelId && conversationId) channelId = await resolveChannelIdForBinding({
286
265
  cfg: params.resolveCurrentCfg(),
287
266
  accountId: params.accountId,
@@ -294,7 +273,7 @@ function createThreadBindingSessionAdapter(params) {
294
273
  channelId,
295
274
  createThread,
296
275
  threadName,
297
- targetKind: toThreadBindingTargetKind(input.targetKind),
276
+ targetKind,
298
277
  targetSessionKey,
299
278
  agentId,
300
279
  label,
@@ -312,28 +291,39 @@ function createThreadBindingSessionAdapter(params) {
312
291
  return binding ? serializeBinding(binding) : null;
313
292
  },
314
293
  touch: (bindingId, at) => {
294
+ const threadId = resolveThreadBindingConversationIdFromBindingId({
295
+ accountId: params.accountId,
296
+ bindingId
297
+ });
298
+ if (threadId) params.manager.touchThreadSync({
299
+ threadId,
300
+ at,
301
+ persist: true
302
+ });
303
+ },
304
+ touchAsync: async (bindingId, at) => {
315
305
  const threadId = resolveThreadBindingConversationIdFromBindingId({
316
306
  accountId: params.accountId,
317
307
  bindingId
318
308
  });
319
309
  if (!threadId) return;
320
- params.manager.touchThread({
310
+ await params.manager.touchThread({
321
311
  threadId,
322
312
  at,
323
313
  persist: true
324
314
  });
325
315
  },
326
316
  unbind: async (input) => {
327
- if (input.targetSessionKey?.trim()) return params.manager.unbindBySessionKey({
317
+ if (input.targetSessionKey?.trim()) return (await params.manager.unbindBySessionKey({
328
318
  targetSessionKey: input.targetSessionKey,
329
319
  reason: input.reason
330
- }).map(serializeBinding);
320
+ })).map(serializeBinding);
331
321
  const threadId = resolveThreadBindingConversationIdFromBindingId({
332
322
  accountId: params.accountId,
333
323
  bindingId: input.bindingId
334
324
  });
335
325
  if (!threadId) return [];
336
- const removed = params.manager.unbindThread({
326
+ const removed = await params.manager.unbindThread({
337
327
  threadId,
338
328
  reason: input.reason
339
329
  });
@@ -358,17 +348,20 @@ function createNoopThreadBindingManager(accountIdRaw) {
358
348
  registerSessionBindingAdapter(adapter);
359
349
  return {
360
350
  accountId,
351
+ isStopping: () => false,
361
352
  getIdleTimeoutMs: () => DEFAULT_THREAD_BINDING_IDLE_TIMEOUT_MS,
362
353
  getMaxAgeMs: () => 0,
363
354
  getByThreadId: () => void 0,
364
355
  getBySessionKey: () => void 0,
365
356
  listBySessionKey: () => [],
366
357
  listBindings: () => [],
367
- touchThread: () => null,
358
+ touchThread: async () => null,
359
+ touchThreadSync: () => null,
368
360
  bindTarget: async () => null,
369
- unbindThread: () => null,
370
- unbindBySessionKey: () => [],
371
- stop: () => unregisterSessionBindingAdapter({
361
+ unbindThread: async () => null,
362
+ unbindBySessionKey: async () => [],
363
+ notifyUnbound: () => {},
364
+ stop: async () => unregisterSessionBindingAdapter({
372
365
  channel: "discord",
373
366
  accountId,
374
367
  adapter
@@ -379,15 +372,23 @@ function createNoopThreadBindingManager(accountIdRaw) {
379
372
  //#region extensions/discord/src/monitor/thread-bindings.manager.ts
380
373
  var thread_bindings_manager_exports = /* @__PURE__ */ __exportAll({
381
374
  createThreadBindingManager: () => createThreadBindingManager,
382
- createThreadBindingManagerAsync: () => createThreadBindingManagerAsync,
383
375
  getThreadBindingManager: () => getThreadBindingManager
384
376
  });
385
377
  function isDirectConversationBindingId(value) {
386
378
  const trimmed = normalizeOptionalString(value);
387
379
  return Boolean(trimmed && /^(user:|channel:)/i.test(trimmed));
388
380
  }
389
- function createThreadBindingManager(params) {
390
- ensureBindingsLoaded();
381
+ async function createThreadBindingManager(input) {
382
+ const params = { ...input };
383
+ await ensureBindingsLoadedAsync();
384
+ const manager = await runThreadBindingMutation(async () => createLoadedThreadBindingManager(params));
385
+ if (manager.isStopping()) {
386
+ await manager.stop();
387
+ return await createThreadBindingManager(params);
388
+ }
389
+ return manager;
390
+ }
391
+ function createLoadedThreadBindingManager(params) {
391
392
  const accountId = normalizeAccountId(params.accountId);
392
393
  const existing = MANAGERS_BY_ACCOUNT_ID.get(accountId);
393
394
  if (existing) {
@@ -408,6 +409,23 @@ function createThreadBindingManager(params) {
408
409
  const resolveCurrentCfg = () => getRuntimeConfigSnapshot() ?? params.cfg;
409
410
  const resolveCurrentToken = () => getThreadBindingToken(accountId) ?? params.token;
410
411
  const getCurrentBinding = (threadId) => MANAGERS_BY_ACCOUNT_ID.get(accountId) === manager ? manager.getByThreadId(threadId) : void 0;
412
+ let stopping = false;
413
+ let stopPromise;
414
+ let sweepPromise;
415
+ const assertManagerCurrent = () => {
416
+ if (MANAGERS_BY_ACCOUNT_ID.get(accountId) !== manager) throw new Error("Discord thread binding manager was retired");
417
+ };
418
+ const runOwnedMutation = (operation) => {
419
+ if (stopping) return Promise.reject(/* @__PURE__ */ new Error("Discord thread binding manager is stopping"));
420
+ return runThreadBindingAccountOperation([manager], async () => {
421
+ assertManagerCurrent();
422
+ return await operation();
423
+ });
424
+ };
425
+ const mutate = (operation) => runOwnedMutation(() => runThreadBindingMutation(async () => {
426
+ assertManagerCurrent();
427
+ return await operation();
428
+ }));
411
429
  let sweepTimer = null;
412
430
  const runSweepOnce = async () => {
413
431
  const bindings = manager.listBindings();
@@ -415,7 +433,7 @@ function createThreadBindingManager(params) {
415
433
  let rest = null;
416
434
  for (const snapshotBinding of bindings) {
417
435
  const binding = getCurrentBinding(snapshotBinding.threadId);
418
- if (!binding) continue;
436
+ if (!binding || stopping) continue;
419
437
  const now = Date.now();
420
438
  const lifecycle = resolvePreparedThreadBindingLifecycle({
421
439
  record: binding,
@@ -424,8 +442,9 @@ function createThreadBindingManager(params) {
424
442
  });
425
443
  const { expiresAt, reason } = lifecycle;
426
444
  if (expiresAt != null && reason && now >= expiresAt) {
427
- manager.unbindThread({
445
+ await manager.unbindThread({
428
446
  threadId: binding.threadId,
447
+ expected: binding,
429
448
  reason,
430
449
  sendFarewell: true,
431
450
  farewellText: resolveThreadBindingFarewellText({
@@ -454,8 +473,9 @@ function createThreadBindingManager(params) {
454
473
  logVerbose(`discord thread binding sweep probe returned invalid payload for ${binding.threadId}`);
455
474
  continue;
456
475
  }
457
- if (isThreadArchived(channel)) manager.unbindThread({
476
+ if (isThreadArchived(channel)) await manager.unbindThread({
458
477
  threadId: binding.threadId,
478
+ expected: binding,
459
479
  reason: "thread-archived",
460
480
  sendFarewell: true
461
481
  });
@@ -463,8 +483,9 @@ function createThreadBindingManager(params) {
463
483
  if (getCurrentBinding(binding.threadId) !== binding) continue;
464
484
  if (isDiscordThreadGoneError(err)) {
465
485
  logVerbose(`discord thread binding sweep removing stale binding ${binding.threadId}: ${summarizeDiscordError(err)}`);
466
- manager.unbindThread({
486
+ await manager.unbindThread({
467
487
  threadId: binding.threadId,
488
+ expected: binding,
468
489
  reason: "thread-delete",
469
490
  sendFarewell: false
470
491
  });
@@ -474,8 +495,52 @@ function createThreadBindingManager(params) {
474
495
  }
475
496
  }
476
497
  };
498
+ const unbindThread = async (unbindParams) => {
499
+ const bindingKey = resolveBindingRecordKey({
500
+ accountId,
501
+ threadId: unbindParams.threadId
502
+ });
503
+ if (!bindingKey) return null;
504
+ const existingLocal = BINDINGS_BY_THREAD_ID.get(bindingKey);
505
+ if (!existingLocal || existingLocal.accountId !== accountId || unbindParams.expected && existingLocal !== unbindParams.expected) return null;
506
+ await commitBindingRecord({
507
+ bindingKey,
508
+ previous: existingLocal,
509
+ next: null,
510
+ persist: unbindParams.persist ?? persist,
511
+ assertCurrent: assertManagerCurrent
512
+ });
513
+ const removed = existingLocal;
514
+ manager.notifyUnbound(removed, unbindParams);
515
+ return removed;
516
+ };
477
517
  const manager = {
478
518
  accountId,
519
+ isStopping: () => stopping,
520
+ notifyUnbound: (removed, notification) => {
521
+ refreshUnboundThreadWebhookIdentity(removed);
522
+ if (notification.sendFarewell !== false) {
523
+ const cfg = resolveCurrentCfg();
524
+ const farewell = resolveThreadBindingFarewellText({
525
+ reason: notification.reason,
526
+ farewellText: notification.farewellText,
527
+ idleTimeoutMs: resolveThreadBindingIdleTimeoutMs$1({
528
+ record: removed,
529
+ defaultIdleTimeoutMs: idleTimeoutMs
530
+ }),
531
+ maxAgeMs: resolveThreadBindingMaxAgeMs$1({
532
+ record: removed,
533
+ defaultMaxAgeMs: maxAgeMs
534
+ })
535
+ });
536
+ if (cfg) maybeSendBindingMessage({
537
+ cfg,
538
+ record: removed,
539
+ text: farewell,
540
+ preferWebhook: false
541
+ });
542
+ }
543
+ },
479
544
  getIdleTimeoutMs: () => idleTimeoutMs,
480
545
  getMaxAgeMs: () => maxAgeMs,
481
546
  getByThreadId: (threadId) => {
@@ -498,206 +563,237 @@ function createThreadBindingManager(params) {
498
563
  }).map((bindingKey) => BINDINGS_BY_THREAD_ID.get(bindingKey)).filter((entry) => Boolean(entry));
499
564
  },
500
565
  listBindings: () => [...BINDINGS_BY_THREAD_ID.values()].filter((entry) => entry.accountId === accountId),
501
- touchThread: (touchParams) => {
566
+ touchThreadSync: (input) => {
567
+ assertManagerCurrent();
568
+ if (stopping) throw new Error("Discord thread binding manager is stopping");
502
569
  const key = resolveBindingRecordKey({
503
570
  accountId,
504
- threadId: touchParams.threadId
571
+ threadId: input.threadId
505
572
  });
506
573
  if (!key) return null;
507
- const existingResult = BINDINGS_BY_THREAD_ID.get(key);
508
- if (!existingResult || existingResult.accountId !== accountId) return null;
509
- const now = Date.now();
510
- const at = typeof touchParams.at === "number" && Number.isFinite(touchParams.at) ? Math.max(0, Math.floor(touchParams.at)) : now;
511
- const nextRecord = {
512
- ...existingResult,
513
- lastActivityAt: Math.max(existingResult.lastActivityAt || 0, at)
574
+ const at = typeof input.at === "number" && Number.isFinite(input.at) ? Math.max(0, Math.floor(input.at)) : Date.now();
575
+ return updateBindingRecordSync({
576
+ bindingKey: key,
577
+ transform: (record) => ({
578
+ ...record,
579
+ lastActivityAt: Math.max(record.lastActivityAt || 0, at)
580
+ }),
581
+ persist: (input.persist ?? persist) && shouldPersistAnyBindingState(),
582
+ minIntervalMs: THREAD_BINDING_TOUCH_PERSIST_MIN_INTERVAL_MS
583
+ });
584
+ },
585
+ touchThread: (input) => {
586
+ const touchParams = {
587
+ ...input,
588
+ at: typeof input.at === "number" && Number.isFinite(input.at) ? Math.max(0, Math.floor(input.at)) : Date.now()
514
589
  };
515
- setBindingRecord(nextRecord);
516
- if (touchParams.persist ?? persist) saveBindingsToDisk({ minIntervalMs: THREAD_BINDING_TOUCH_PERSIST_MIN_INTERVAL_MS });
517
- return nextRecord;
590
+ return mutate(async () => {
591
+ const key = resolveBindingRecordKey({
592
+ accountId,
593
+ threadId: touchParams.threadId
594
+ });
595
+ if (!key) return null;
596
+ const existingResult = BINDINGS_BY_THREAD_ID.get(key);
597
+ if (!existingResult || existingResult.accountId !== accountId) return null;
598
+ const nextRecord = {
599
+ ...existingResult,
600
+ lastActivityAt: Math.max(existingResult.lastActivityAt || 0, touchParams.at)
601
+ };
602
+ await commitBindingRecord({
603
+ bindingKey: key,
604
+ previous: existingResult,
605
+ next: nextRecord,
606
+ persist: (touchParams.persist ?? persist) && shouldPersistAnyBindingState(),
607
+ minIntervalMs: THREAD_BINDING_TOUCH_PERSIST_MIN_INTERVAL_MS,
608
+ assertCurrent: assertManagerCurrent
609
+ });
610
+ return nextRecord;
611
+ });
518
612
  },
519
613
  bindTarget: async (input) => {
520
614
  const bindParams = {
521
615
  ...input,
522
- metadata: input.metadata ? { ...input.metadata } : void 0
616
+ metadata: asOptionalObjectRecord(snapshotThreadBindingJson(input.metadata ? { ...input.metadata } : void 0))
523
617
  };
524
- const assertCurrent = bindParams.assertCurrent;
525
- assertCurrent?.();
526
- const cfg = resolveCurrentCfg();
527
- let threadId = normalizeThreadId(bindParams.threadId);
528
- let channelId = normalizeOptionalString(bindParams.channelId) ?? "";
529
- const directConversationBinding = isDirectConversationBindingId(threadId) || isDirectConversationBindingId(channelId);
530
- let nativeBindingCreated = false;
531
- const targetSessionKey = normalizeOptionalString(bindParams.targetSessionKey) ?? "";
532
- if (!targetSessionKey) return null;
533
- const targetKind = normalizeTargetKind(bindParams.targetKind, targetSessionKey);
534
- let agentId = normalizeOptionalString(bindParams.agentId);
535
- if (!threadId && bindParams.createThread) {
618
+ return runOwnedMutation(async () => {
619
+ const assertCurrent = bindParams.assertCurrent;
620
+ assertCurrent?.();
621
+ const cfg = resolveCurrentCfg();
622
+ let threadId = normalizeOptionalStringifiedId(bindParams.threadId);
623
+ let channelId = normalizeOptionalString(bindParams.channelId) ?? "";
624
+ const directConversationBinding = isDirectConversationBindingId(threadId) || isDirectConversationBindingId(channelId);
625
+ let nativeBindingCreated = false;
626
+ const targetSessionKey = normalizeOptionalString(bindParams.targetSessionKey) ?? "";
627
+ if (!targetSessionKey) return null;
628
+ const targetKind = normalizeTargetKind(bindParams.targetKind, targetSessionKey);
629
+ let agentId = normalizeOptionalString(bindParams.agentId);
630
+ if (!threadId && bindParams.createThread) {
631
+ if (!channelId) return null;
632
+ agentId ??= resolveSessionAgentIdStrict({
633
+ config: cfg,
634
+ sessionKey: targetSessionKey
635
+ });
636
+ const threadName = resolveThreadBindingThreadName({
637
+ agentId: bindParams.agentId,
638
+ label: bindParams.label
639
+ });
640
+ threadId = await createThreadForBinding({
641
+ cfg,
642
+ accountId,
643
+ token: resolveCurrentToken(),
644
+ channelId,
645
+ threadName: normalizeOptionalString(bindParams.threadName) ?? threadName,
646
+ ...assertCurrent ? { assertCreateAllowed: assertCurrent } : {}
647
+ }) ?? void 0;
648
+ nativeBindingCreated = Boolean(threadId);
649
+ }
650
+ if (!threadId) return null;
651
+ if (!channelId && directConversationBinding) channelId = threadId;
652
+ if (!channelId) channelId = await resolveChannelIdForBinding({
653
+ cfg,
654
+ accountId,
655
+ token: resolveCurrentToken(),
656
+ threadId,
657
+ channelId: bindParams.channelId
658
+ }) ?? "";
536
659
  if (!channelId) return null;
537
- agentId ??= resolveSessionAgentIdStrict({
660
+ const existingValue = manager.getByThreadId(threadId);
661
+ const previous = existingValue?.targetSessionKey === targetSessionKey && existingValue.targetKind === targetKind ? existingValue : void 0;
662
+ agentId ??= normalizeOptionalString(previous?.agentId) ?? resolveSessionAgentIdStrict({
538
663
  config: cfg,
539
664
  sessionKey: targetSessionKey
540
665
  });
541
- const threadName = resolveThreadBindingThreadName({
542
- agentId: bindParams.agentId,
543
- label: bindParams.label
544
- });
545
- threadId = await createThreadForBinding({
546
- cfg,
666
+ let webhookId = normalizeOptionalString(bindParams.webhookId) ?? normalizeOptionalString(existingValue?.webhookId) ?? "";
667
+ let webhookToken = normalizeOptionalString(bindParams.webhookToken) ?? normalizeOptionalString(existingValue?.webhookToken) ?? "";
668
+ if (!directConversationBinding && (!webhookId || !webhookToken)) {
669
+ const cachedWebhook = findReusableWebhook({
670
+ accountId,
671
+ channelId
672
+ });
673
+ webhookId = cachedWebhook.webhookId ?? "";
674
+ webhookToken = cachedWebhook.webhookToken ?? "";
675
+ }
676
+ if (!directConversationBinding && (!webhookId || !webhookToken)) {
677
+ const createdWebhook = await createWebhookForChannel({
678
+ cfg,
679
+ accountId,
680
+ token: resolveCurrentToken(),
681
+ channelId,
682
+ ...assertCurrent ? { assertCreateAllowed: assertCurrent } : {}
683
+ });
684
+ webhookId = createdWebhook.webhookId ?? "";
685
+ webhookToken = createdWebhook.webhookToken ?? "";
686
+ nativeBindingCreated ||= Boolean(webhookId && webhookToken);
687
+ }
688
+ const now = Date.now();
689
+ const record = {
547
690
  accountId,
548
- token: resolveCurrentToken(),
549
691
  channelId,
550
- threadName: normalizeOptionalString(bindParams.threadName) ?? threadName,
551
- ...assertCurrent ? { assertCreateAllowed: assertCurrent } : {}
552
- }) ?? void 0;
553
- nativeBindingCreated = Boolean(threadId);
554
- }
555
- if (!threadId) return null;
556
- if (!channelId && directConversationBinding) channelId = threadId;
557
- if (!channelId) channelId = await resolveChannelIdForBinding({
558
- cfg,
559
- accountId,
560
- token: resolveCurrentToken(),
561
- threadId,
562
- channelId: bindParams.channelId
563
- }) ?? "";
564
- if (!channelId) return null;
565
- const existingValue = manager.getByThreadId(threadId);
566
- const previous = existingValue?.targetSessionKey === targetSessionKey && existingValue.targetKind === targetKind ? existingValue : void 0;
567
- agentId ??= normalizeOptionalString(previous?.agentId) ?? resolveSessionAgentIdStrict({
568
- config: cfg,
569
- sessionKey: targetSessionKey
570
- });
571
- let webhookId = normalizeOptionalString(bindParams.webhookId) ?? normalizeOptionalString(existingValue?.webhookId) ?? "";
572
- let webhookToken = normalizeOptionalString(bindParams.webhookToken) ?? normalizeOptionalString(existingValue?.webhookToken) ?? "";
573
- if (!directConversationBinding && (!webhookId || !webhookToken)) {
574
- const cachedWebhook = findReusableWebhook({
575
- accountId,
576
- channelId
577
- });
578
- webhookId = cachedWebhook.webhookId ?? "";
579
- webhookToken = cachedWebhook.webhookToken ?? "";
580
- }
581
- if (!directConversationBinding && (!webhookId || !webhookToken)) {
582
- const createdWebhook = await createWebhookForChannel({
692
+ threadId,
693
+ targetKind,
694
+ targetSessionKey,
695
+ agentId,
696
+ label: normalizeOptionalString(bindParams.label) ?? normalizeOptionalString(previous?.label),
697
+ webhookId: webhookId || void 0,
698
+ webhookToken: webhookToken || void 0,
699
+ boundBy: normalizeOptionalString(bindParams.boundBy) ?? normalizeOptionalString(previous?.boundBy) ?? "system",
700
+ boundAt: now,
701
+ lastActivityAt: now,
702
+ idleTimeoutMs: typeof existingValue?.idleTimeoutMs === "number" ? existingValue.idleTimeoutMs : idleTimeoutMs,
703
+ maxAgeMs: typeof existingValue?.maxAgeMs === "number" ? existingValue.maxAgeMs : maxAgeMs,
704
+ metadata: {
705
+ ...previous?.metadata,
706
+ ...bindParams.metadata
707
+ }
708
+ };
709
+ if (!nativeBindingCreated) assertCurrent?.();
710
+ await runThreadBindingMutation(() => commitBindingRecord({
711
+ bindingKey: toBindingRecordKey({
712
+ accountId,
713
+ threadId
714
+ }),
715
+ previous: existingValue,
716
+ next: record,
717
+ persist,
718
+ assertCurrent: () => {
719
+ assertManagerCurrent();
720
+ if (!nativeBindingCreated) assertCurrent?.();
721
+ }
722
+ }));
723
+ const introText = bindParams.introText?.trim();
724
+ if (introText && cfg) maybeSendBindingMessage({
583
725
  cfg,
584
- accountId,
585
- token: resolveCurrentToken(),
586
- channelId,
587
- ...assertCurrent ? { assertCreateAllowed: assertCurrent } : {}
726
+ record,
727
+ text: introText,
728
+ assertCurrent: () => {
729
+ assertCurrent?.();
730
+ const current = getCurrentBinding(record.threadId);
731
+ if (!current || current.targetSessionKey !== record.targetSessionKey || current.targetKind !== record.targetKind) throw new Error("Discord thread binding changed before its intro");
732
+ }
588
733
  });
589
- webhookId = createdWebhook.webhookId ?? "";
590
- webhookToken = createdWebhook.webhookToken ?? "";
591
- nativeBindingCreated ||= Boolean(webhookId && webhookToken);
592
- }
593
- const now = Date.now();
594
- const record = {
595
- accountId,
596
- channelId,
597
- threadId,
598
- targetKind,
599
- targetSessionKey,
600
- agentId,
601
- label: normalizeOptionalString(bindParams.label) ?? normalizeOptionalString(previous?.label),
602
- webhookId: webhookId || void 0,
603
- webhookToken: webhookToken || void 0,
604
- boundBy: normalizeOptionalString(bindParams.boundBy) ?? normalizeOptionalString(previous?.boundBy) ?? "system",
605
- boundAt: now,
606
- lastActivityAt: now,
607
- idleTimeoutMs: typeof existingValue?.idleTimeoutMs === "number" ? existingValue.idleTimeoutMs : idleTimeoutMs,
608
- maxAgeMs: typeof existingValue?.maxAgeMs === "number" ? existingValue.maxAgeMs : maxAgeMs,
609
- metadata: {
610
- ...previous?.metadata,
611
- ...bindParams.metadata
612
- }
613
- };
614
- if (!nativeBindingCreated) assertCurrent?.();
615
- setBindingRecord(record);
616
- if (persist) saveBindingsToDisk();
617
- const introText = bindParams.introText?.trim();
618
- if (introText && cfg) maybeSendBindingMessage({
619
- cfg,
620
- record,
621
- text: introText,
622
- ...assertCurrent ? { assertCurrent } : {}
734
+ return record;
623
735
  });
624
- return record;
625
736
  },
626
- unbindThread: (unbindParams) => {
627
- const bindingKey = resolveBindingRecordKey({
628
- accountId,
629
- threadId: unbindParams.threadId
630
- });
631
- if (!bindingKey) return null;
632
- const existingLocal = BINDINGS_BY_THREAD_ID.get(bindingKey);
633
- if (!existingLocal || existingLocal.accountId !== accountId) return null;
634
- const removed = removeBindingRecord(bindingKey);
635
- if (!removed) return null;
636
- refreshUnboundThreadWebhookIdentity(removed);
637
- if (persist) saveBindingsToDisk();
638
- if (unbindParams.sendFarewell !== false) {
639
- const cfg = resolveCurrentCfg();
640
- const farewell = resolveThreadBindingFarewellText({
641
- reason: unbindParams.reason,
642
- farewellText: unbindParams.farewellText,
643
- idleTimeoutMs: resolveThreadBindingIdleTimeoutMs$1({
644
- record: removed,
645
- defaultIdleTimeoutMs: idleTimeoutMs
646
- }),
647
- maxAgeMs: resolveThreadBindingMaxAgeMs$1({
648
- record: removed,
649
- defaultMaxAgeMs: maxAgeMs
650
- })
651
- });
652
- if (cfg) maybeSendBindingMessage({
653
- cfg,
654
- record: removed,
655
- text: farewell,
656
- preferWebhook: false
657
- });
658
- }
659
- return removed;
737
+ unbindThread: (input) => {
738
+ const unbindParams = { ...input };
739
+ return mutate(() => unbindThread(unbindParams));
660
740
  },
661
- unbindBySessionKey: (unbindParams) => {
662
- const ids = resolveBindingIdsForSession({
663
- targetSessionKey: unbindParams.targetSessionKey,
664
- accountId,
665
- targetKind: unbindParams.targetKind
666
- });
667
- if (ids.length === 0) return [];
668
- const removed = [];
669
- for (const bindingKey of ids) {
670
- const binding = BINDINGS_BY_THREAD_ID.get(bindingKey);
671
- if (!binding) continue;
672
- const entry = manager.unbindThread({
673
- threadId: binding.threadId,
674
- reason: unbindParams.reason,
675
- sendFarewell: unbindParams.sendFarewell,
676
- farewellText: unbindParams.farewellText
741
+ unbindBySessionKey: (input) => {
742
+ const unbindParams = { ...input };
743
+ return mutate(async () => {
744
+ const ids = resolveBindingIdsForSession({
745
+ targetSessionKey: unbindParams.targetSessionKey,
746
+ accountId,
747
+ targetKind: unbindParams.targetKind
677
748
  });
678
- if (entry) removed.push(entry);
679
- }
680
- return removed;
749
+ if (ids.length === 0) return [];
750
+ const removed = [];
751
+ for (const bindingKey of ids) {
752
+ const binding = BINDINGS_BY_THREAD_ID.get(bindingKey);
753
+ if (!binding) continue;
754
+ const entry = await unbindThread({
755
+ threadId: binding.threadId,
756
+ reason: unbindParams.reason,
757
+ sendFarewell: unbindParams.sendFarewell,
758
+ farewellText: unbindParams.farewellText
759
+ });
760
+ if (entry) removed.push(entry);
761
+ }
762
+ return removed;
763
+ });
681
764
  },
682
765
  stop: () => {
766
+ if (stopPromise) return stopPromise;
767
+ stopping = true;
683
768
  if (sweepTimer) {
684
769
  clearInterval(sweepTimer);
685
770
  sweepTimer = null;
686
771
  }
687
- if (MANAGERS_BY_ACCOUNT_ID.get(accountId) === manager) {
688
- MANAGERS_BY_ACCOUNT_ID.delete(accountId);
689
- forgetThreadBindingToken(accountId);
690
- }
691
- unregisterSessionBindingAdapter({
692
- channel: "discord",
693
- accountId,
694
- adapter: sessionBindingAdapter
695
- });
772
+ stopPromise = (async () => {
773
+ await sweepPromise;
774
+ await drainThreadBindingAccountOperations(manager);
775
+ await drainThreadBindingMutations();
776
+ if (MANAGERS_BY_ACCOUNT_ID.get(accountId) === manager) {
777
+ MANAGERS_BY_ACCOUNT_ID.delete(accountId);
778
+ forgetThreadBindingToken(accountId);
779
+ }
780
+ unregisterSessionBindingAdapter({
781
+ channel: "discord",
782
+ accountId,
783
+ adapter: sessionBindingAdapter
784
+ });
785
+ })();
786
+ return stopPromise;
696
787
  }
697
788
  };
698
789
  if (params.enableSweeper !== false) {
699
790
  sweepTimer = setInterval(() => {
700
- runSweepOnce();
791
+ if (stopping || sweepPromise) return;
792
+ sweepPromise = runSweepOnce().catch((error) => {
793
+ logVerbose(`discord thread binding sweep failed: ${String(error)}`);
794
+ }).finally(() => {
795
+ sweepPromise = void 0;
796
+ });
701
797
  }, THREAD_BINDINGS_SWEEP_INTERVAL_MS);
702
798
  if (!(process.env.VITEST || false)) sweepTimer.unref?.();
703
799
  }
@@ -715,13 +811,9 @@ function createThreadBindingManager(params) {
715
811
  MANAGERS_BY_ACCOUNT_ID.set(accountId, manager);
716
812
  return manager;
717
813
  }
718
- async function createThreadBindingManagerAsync(params) {
719
- await ensureBindingsLoadedAsync();
720
- return createThreadBindingManager(params);
721
- }
722
814
  function getThreadBindingManager(accountId) {
723
815
  const normalized = normalizeAccountId(accountId);
724
816
  return MANAGERS_BY_ACCOUNT_ID.get(normalized) ?? null;
725
817
  }
726
818
  //#endregion
727
- export { createNoopThreadBindingManager as a, resolveThreadBindingPersona as c, resolveThreadBindingIntroText as d, resolveThreadBindingThreadName as f, thread_bindings_manager_exports as i, resolveThreadBindingPersonaFromRecord as l, createThreadBindingManagerAsync as n, isThreadArchived as o, getThreadBindingManager as r, resolveChannelIdForBinding as s, createThreadBindingManager as t, formatThreadBindingDurationLabel as u };
819
+ export { isThreadArchived as a, resolveThreadBindingPersonaFromRecord as c, createNoopThreadBindingManager as i, getThreadBindingManager as n, resolveChannelIdForBinding as o, thread_bindings_manager_exports as r, resolveThreadBindingPersona as s, createThreadBindingManager as t };