shardwire 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -6,6 +6,7 @@ var BOT_EVENT_NAMES = [
6
6
  "messageCreate",
7
7
  "messageUpdate",
8
8
  "messageDelete",
9
+ "messageBulkDelete",
9
10
  "messageReactionAdd",
10
11
  "messageReactionRemove",
11
12
  "guildCreate",
@@ -15,7 +16,10 @@ var BOT_EVENT_NAMES = [
15
16
  "guildMemberUpdate",
16
17
  "threadCreate",
17
18
  "threadUpdate",
18
- "threadDelete"
19
+ "threadDelete",
20
+ "channelCreate",
21
+ "channelUpdate",
22
+ "channelDelete"
19
23
  ];
20
24
  var BOT_ACTION_NAMES = [
21
25
  "sendMessage",
@@ -36,7 +40,14 @@ var BOT_ACTION_NAMES = [
36
40
  "addMemberRole",
37
41
  "removeMemberRole",
38
42
  "addMessageReaction",
39
- "removeOwnMessageReaction"
43
+ "removeOwnMessageReaction",
44
+ "timeoutMember",
45
+ "removeMemberTimeout",
46
+ "createChannel",
47
+ "editChannel",
48
+ "deleteChannel",
49
+ "createThread",
50
+ "archiveThread"
40
51
  ];
41
52
  var DISCORD_GATEWAY_INTENT_ENTRIES = Object.entries(GatewayIntentBits).filter(
42
53
  (entry) => typeof entry[1] === "number"
@@ -50,6 +61,7 @@ var EVENT_REQUIRED_INTENTS = {
50
61
  messageCreate: ["GuildMessages"],
51
62
  messageUpdate: ["GuildMessages"],
52
63
  messageDelete: ["GuildMessages"],
64
+ messageBulkDelete: ["GuildMessages"],
53
65
  messageReactionAdd: ["GuildMessageReactions"],
54
66
  messageReactionRemove: ["GuildMessageReactions"],
55
67
  guildCreate: ["Guilds"],
@@ -59,11 +71,16 @@ var EVENT_REQUIRED_INTENTS = {
59
71
  guildMemberUpdate: ["GuildMembers"],
60
72
  threadCreate: ["Guilds"],
61
73
  threadUpdate: ["Guilds"],
62
- threadDelete: ["Guilds"]
74
+ threadDelete: ["Guilds"],
75
+ channelCreate: ["Guilds"],
76
+ channelUpdate: ["Guilds"],
77
+ channelDelete: ["Guilds"]
63
78
  };
64
79
  function getAvailableEvents(intents) {
65
80
  const enabled = new Set(intents);
66
- return BOT_EVENT_NAMES.filter((eventName) => EVENT_REQUIRED_INTENTS[eventName].every((intent) => enabled.has(intent)));
81
+ return BOT_EVENT_NAMES.filter(
82
+ (eventName) => EVENT_REQUIRED_INTENTS[eventName].every((intent) => enabled.has(intent))
83
+ );
67
84
  }
68
85
 
69
86
  // src/discord/runtime/adapter.ts
@@ -80,6 +97,7 @@ var ActionExecutionError = class extends Error {
80
97
 
81
98
  // src/discord/runtime/discordjs-adapter.ts
82
99
  import {
100
+ ChannelType,
83
101
  Client,
84
102
  DiscordAPIError,
85
103
  Events,
@@ -155,6 +173,20 @@ function serializeMessage(message) {
155
173
  ...message.reference.guildId ? { guildId: message.reference.guildId } : {}
156
174
  } : void 0;
157
175
  const components = "components" in message && message.components && message.components.length > 0 ? message.components.map((row) => row.toJSON()) : void 0;
176
+ const channelMeta = (() => {
177
+ if (!("channel" in message) || !message.channel) {
178
+ return {};
179
+ }
180
+ const ch = message.channel;
181
+ const meta = {};
182
+ if (typeof ch.type === "number") {
183
+ meta.channelType = ch.type;
184
+ }
185
+ if ("parentId" in ch && typeof ch.parentId === "string") {
186
+ meta.parentChannelId = ch.parentId;
187
+ }
188
+ return meta;
189
+ })();
158
190
  return {
159
191
  id: message.id,
160
192
  channelId: message.channelId,
@@ -173,7 +205,8 @@ function serializeMessage(message) {
173
205
  })) : [],
174
206
  embeds: serializeEmbeds(message),
175
207
  ...components ? { components } : {},
176
- ...reference ? { reference } : {}
208
+ ...reference ? { reference } : {},
209
+ ...channelMeta
177
210
  };
178
211
  }
179
212
  function serializeGuild(guild) {
@@ -195,12 +228,43 @@ function serializeThread(thread) {
195
228
  ...typeof thread.locked === "boolean" ? { locked: thread.locked } : {}
196
229
  };
197
230
  }
231
+ function serializeChannel(channel) {
232
+ const base = {
233
+ id: channel.id,
234
+ type: channel.type
235
+ };
236
+ if ("name" in channel) {
237
+ base.name = channel.name ?? null;
238
+ }
239
+ if ("guildId" in channel && typeof channel.guildId === "string") {
240
+ base.guildId = channel.guildId;
241
+ }
242
+ if ("parentId" in channel) {
243
+ base.parentId = channel.parentId;
244
+ }
245
+ return base;
246
+ }
198
247
  function serializeDeletedMessage(message) {
248
+ const channelMeta = (() => {
249
+ if (!("channel" in message) || !message.channel) {
250
+ return {};
251
+ }
252
+ const ch = message.channel;
253
+ const meta = {};
254
+ if (typeof ch.type === "number") {
255
+ meta.channelType = ch.type;
256
+ }
257
+ if ("parentId" in ch && typeof ch.parentId === "string") {
258
+ meta.parentChannelId = ch.parentId;
259
+ }
260
+ return meta;
261
+ })();
199
262
  return {
200
263
  id: message.id,
201
264
  channelId: message.channelId,
202
265
  ...message.guildId ? { guildId: message.guildId } : {},
203
- deletedAt: (/* @__PURE__ */ new Date()).toISOString()
266
+ deletedAt: (/* @__PURE__ */ new Date()).toISOString(),
267
+ ...channelMeta
204
268
  };
205
269
  }
206
270
  function serializeReactionEmoji(reaction) {
@@ -266,83 +330,124 @@ function serializeInteraction(interaction) {
266
330
  ...interaction.member && "guild" in interaction.member ? { member: serializeGuildMember(interaction.member) } : {}
267
331
  };
268
332
  if (interaction.isChatInputCommand()) {
269
- return {
270
- ...base,
271
- kind: "chatInput",
272
- commandName: interaction.commandName,
273
- options: serializeChatInputOptions(interaction)
274
- };
333
+ return enrichInteractionChannel(
334
+ {
335
+ ...base,
336
+ kind: "chatInput",
337
+ commandName: interaction.commandName,
338
+ options: serializeChatInputOptions(interaction)
339
+ },
340
+ interaction
341
+ );
275
342
  }
276
343
  if (interaction.isContextMenuCommand()) {
277
- return {
278
- ...base,
279
- kind: "contextMenu",
280
- commandName: interaction.commandName
281
- };
344
+ return enrichInteractionChannel(
345
+ {
346
+ ...base,
347
+ kind: "contextMenu",
348
+ commandName: interaction.commandName
349
+ },
350
+ interaction
351
+ );
282
352
  }
283
353
  if (interaction.isButton()) {
284
354
  const message = serializeInteractionMessage(interaction);
285
- return {
286
- ...base,
287
- kind: "button",
288
- customId: interaction.customId,
289
- ...message ? { message } : {}
290
- };
355
+ return enrichInteractionChannel(
356
+ {
357
+ ...base,
358
+ kind: "button",
359
+ customId: interaction.customId,
360
+ ...message ? { message } : {}
361
+ },
362
+ interaction
363
+ );
291
364
  }
292
365
  if (interaction.isStringSelectMenu()) {
293
366
  const message = serializeInteractionMessage(interaction);
294
- return {
295
- ...base,
296
- kind: "stringSelect",
297
- ...serializeSelectMenu(interaction),
298
- ...message ? { message } : {}
299
- };
367
+ return enrichInteractionChannel(
368
+ {
369
+ ...base,
370
+ kind: "stringSelect",
371
+ ...serializeSelectMenu(interaction),
372
+ ...message ? { message } : {}
373
+ },
374
+ interaction
375
+ );
300
376
  }
301
377
  if (interaction.isUserSelectMenu()) {
302
378
  const message = serializeInteractionMessage(interaction);
303
- return {
304
- ...base,
305
- kind: "userSelect",
306
- ...serializeSelectMenu(interaction),
307
- ...message ? { message } : {}
308
- };
379
+ return enrichInteractionChannel(
380
+ {
381
+ ...base,
382
+ kind: "userSelect",
383
+ ...serializeSelectMenu(interaction),
384
+ ...message ? { message } : {}
385
+ },
386
+ interaction
387
+ );
309
388
  }
310
389
  if (interaction.isRoleSelectMenu()) {
311
390
  const message = serializeInteractionMessage(interaction);
312
- return {
313
- ...base,
314
- kind: "roleSelect",
315
- ...serializeSelectMenu(interaction),
316
- ...message ? { message } : {}
317
- };
391
+ return enrichInteractionChannel(
392
+ {
393
+ ...base,
394
+ kind: "roleSelect",
395
+ ...serializeSelectMenu(interaction),
396
+ ...message ? { message } : {}
397
+ },
398
+ interaction
399
+ );
318
400
  }
319
401
  if (interaction.isMentionableSelectMenu()) {
320
402
  const message = serializeInteractionMessage(interaction);
321
- return {
322
- ...base,
323
- kind: "mentionableSelect",
324
- ...serializeSelectMenu(interaction),
325
- ...message ? { message } : {}
326
- };
403
+ return enrichInteractionChannel(
404
+ {
405
+ ...base,
406
+ kind: "mentionableSelect",
407
+ ...serializeSelectMenu(interaction),
408
+ ...message ? { message } : {}
409
+ },
410
+ interaction
411
+ );
327
412
  }
328
413
  if (interaction.isChannelSelectMenu()) {
329
414
  const message = serializeInteractionMessage(interaction);
330
- return {
331
- ...base,
332
- kind: "channelSelect",
333
- ...serializeSelectMenu(interaction),
334
- ...message ? { message } : {}
335
- };
415
+ return enrichInteractionChannel(
416
+ {
417
+ ...base,
418
+ kind: "channelSelect",
419
+ ...serializeSelectMenu(interaction),
420
+ ...message ? { message } : {}
421
+ },
422
+ interaction
423
+ );
336
424
  }
337
425
  if (interaction.isModalSubmit()) {
338
- return {
339
- ...base,
340
- kind: "modalSubmit",
341
- customId: interaction.customId,
342
- fields: serializeModalFields(interaction)
343
- };
426
+ return enrichInteractionChannel(
427
+ {
428
+ ...base,
429
+ kind: "modalSubmit",
430
+ customId: interaction.customId,
431
+ fields: serializeModalFields(interaction)
432
+ },
433
+ interaction
434
+ );
344
435
  }
345
- return base;
436
+ return enrichInteractionChannel(base, interaction);
437
+ }
438
+ function enrichInteractionChannel(base, interaction) {
439
+ if (!("channel" in interaction) || !interaction.channel || interaction.channel.isDMBased()) {
440
+ return base;
441
+ }
442
+ const ch = interaction.channel;
443
+ const out = { ...base };
444
+ if (typeof ch.type === "number") {
445
+ out.channelType = ch.type;
446
+ }
447
+ if ("parentId" in ch && typeof ch.parentId === "string") {
448
+ out.parentChannelId = ch.parentId;
449
+ }
450
+ return out;
346
451
  }
347
452
 
348
453
  // src/discord/runtime/discordjs-adapter.ts
@@ -405,15 +510,24 @@ function mapDiscordErrorToActionExecutionError(error) {
405
510
  return new ActionExecutionError("INVALID_REQUEST", message, detailPayload);
406
511
  }
407
512
  if (details.status === 429) {
513
+ let retryAfterMs;
514
+ if (error instanceof DiscordAPIError) {
515
+ const raw = error.rawError;
516
+ const retryAfter = raw?.retry_after;
517
+ if (typeof retryAfter === "number" && Number.isFinite(retryAfter)) {
518
+ retryAfterMs = Math.max(0, Math.ceil(retryAfter * 1e3));
519
+ }
520
+ }
408
521
  return new ActionExecutionError("SERVICE_UNAVAILABLE", message, {
409
522
  discordStatus: 429,
410
523
  retryable: true,
411
- ...details.code !== void 0 ? { discordCode: details.code } : {}
524
+ ...details.code !== void 0 ? { discordCode: details.code } : {},
525
+ ...retryAfterMs !== void 0 ? { retryAfterMs } : {}
412
526
  });
413
527
  }
414
528
  return null;
415
529
  }
416
- var DiscordJsRuntimeAdapter = class {
530
+ var DiscordJsRuntimeAdapter = class _DiscordJsRuntimeAdapter {
417
531
  constructor(options) {
418
532
  this.options = options;
419
533
  const intentBits = options.intents.map((intent) => BOT_INTENT_BITS[intent]);
@@ -443,7 +557,14 @@ var DiscordJsRuntimeAdapter = class {
443
557
  addMemberRole: (payload) => this.addMemberRole(payload),
444
558
  removeMemberRole: (payload) => this.removeMemberRole(payload),
445
559
  addMessageReaction: (payload) => this.addMessageReaction(payload),
446
- removeOwnMessageReaction: (payload) => this.removeOwnMessageReaction(payload)
560
+ removeOwnMessageReaction: (payload) => this.removeOwnMessageReaction(payload),
561
+ timeoutMember: (payload) => this.timeoutMember(payload),
562
+ removeMemberTimeout: (payload) => this.removeMemberTimeout(payload),
563
+ createChannel: (payload) => this.createChannel(payload),
564
+ editChannel: (payload) => this.editChannel(payload),
565
+ deleteChannel: (payload) => this.deleteChannel(payload),
566
+ createThread: (payload) => this.createThread(payload),
567
+ archiveThread: (payload) => this.archiveThread(payload)
447
568
  };
448
569
  }
449
570
  options;
@@ -571,6 +692,23 @@ var DiscordJsRuntimeAdapter = class {
571
692
  this.client.off(Events.MessageDelete, listener);
572
693
  };
573
694
  }
695
+ case "messageBulkDelete": {
696
+ const listener = (messages, channel) => {
697
+ handler({
698
+ receivedAt: Date.now(),
699
+ ...this.shardEnvelope(),
700
+ channelId: channel.id,
701
+ guildId: channel.guildId,
702
+ messageIds: [...messages.keys()],
703
+ channelType: channel.type,
704
+ ..."parentId" in channel && typeof channel.parentId === "string" ? { parentChannelId: channel.parentId } : {}
705
+ });
706
+ };
707
+ this.client.on(Events.MessageBulkDelete, listener);
708
+ return () => {
709
+ this.client.off(Events.MessageBulkDelete, listener);
710
+ };
711
+ }
574
712
  case "messageReactionAdd": {
575
713
  const listener = (reaction, user) => {
576
714
  handler({
@@ -708,6 +846,46 @@ var DiscordJsRuntimeAdapter = class {
708
846
  this.client.off(Events.ThreadDelete, listener);
709
847
  };
710
848
  }
849
+ case "channelCreate": {
850
+ const listener = (channel) => {
851
+ handler({
852
+ receivedAt: Date.now(),
853
+ ...this.shardEnvelope(),
854
+ channel: serializeChannel(channel)
855
+ });
856
+ };
857
+ this.client.on(Events.ChannelCreate, listener);
858
+ return () => {
859
+ this.client.off(Events.ChannelCreate, listener);
860
+ };
861
+ }
862
+ case "channelUpdate": {
863
+ const listener = (oldChannel, newChannel) => {
864
+ handler({
865
+ receivedAt: Date.now(),
866
+ ...this.shardEnvelope(),
867
+ oldChannel: serializeChannel(oldChannel),
868
+ channel: serializeChannel(newChannel)
869
+ });
870
+ };
871
+ this.client.on(Events.ChannelUpdate, listener);
872
+ return () => {
873
+ this.client.off(Events.ChannelUpdate, listener);
874
+ };
875
+ }
876
+ case "channelDelete": {
877
+ const listener = (channel) => {
878
+ handler({
879
+ receivedAt: Date.now(),
880
+ ...this.shardEnvelope(),
881
+ channel: serializeChannel(channel)
882
+ });
883
+ };
884
+ this.client.on(Events.ChannelDelete, listener);
885
+ return () => {
886
+ this.client.off(Events.ChannelDelete, listener);
887
+ };
888
+ }
711
889
  default:
712
890
  return () => void 0;
713
891
  }
@@ -726,7 +904,10 @@ var DiscordJsRuntimeAdapter = class {
726
904
  throw mappedError;
727
905
  }
728
906
  this.logger.error("Discord action execution failed.", { action: name, error: String(error) });
729
- throw new ActionExecutionError("INTERNAL_ERROR", error instanceof Error ? error.message : "Discord action failed.");
907
+ throw new ActionExecutionError(
908
+ "INTERNAL_ERROR",
909
+ error instanceof Error ? error.message : "Discord action failed."
910
+ );
730
911
  }
731
912
  }
732
913
  async fetchSendableChannel(channelId) {
@@ -794,7 +975,10 @@ var DiscordJsRuntimeAdapter = class {
794
975
  async replyToInteraction(payload) {
795
976
  const interaction = this.getReplyCapableInteraction(payload.interactionId);
796
977
  if (interaction.replied || interaction.deferred) {
797
- throw new ActionExecutionError("INVALID_REQUEST", `Interaction "${payload.interactionId}" has already been acknowledged.`);
978
+ throw new ActionExecutionError(
979
+ "INVALID_REQUEST",
980
+ `Interaction "${payload.interactionId}" has already been acknowledged.`
981
+ );
798
982
  }
799
983
  const reply = await interaction.reply({
800
984
  ...toSendOptions(payload),
@@ -806,7 +990,10 @@ var DiscordJsRuntimeAdapter = class {
806
990
  async deferInteraction(payload) {
807
991
  const interaction = this.getReplyCapableInteraction(payload.interactionId);
808
992
  if (interaction.replied) {
809
- throw new ActionExecutionError("INVALID_REQUEST", `Interaction "${payload.interactionId}" has already been replied to.`);
993
+ throw new ActionExecutionError(
994
+ "INVALID_REQUEST",
995
+ `Interaction "${payload.interactionId}" has already been replied to.`
996
+ );
810
997
  }
811
998
  if (!interaction.deferred) {
812
999
  await interaction.deferReply({
@@ -829,7 +1016,10 @@ var DiscordJsRuntimeAdapter = class {
829
1016
  async followUpInteraction(payload) {
830
1017
  const interaction = this.getReplyCapableInteraction(payload.interactionId);
831
1018
  if (!interaction.replied && !interaction.deferred) {
832
- throw new ActionExecutionError("INVALID_REQUEST", `Interaction "${payload.interactionId}" has not been acknowledged yet.`);
1019
+ throw new ActionExecutionError(
1020
+ "INVALID_REQUEST",
1021
+ `Interaction "${payload.interactionId}" has not been acknowledged yet.`
1022
+ );
833
1023
  }
834
1024
  const followUp = await interaction.followUp({
835
1025
  ...toSendOptions(payload),
@@ -938,7 +1128,10 @@ var DiscordJsRuntimeAdapter = class {
938
1128
  return candidate.emoji.identifier === payload.emoji || candidate.emoji.name === payload.emoji || candidate.emoji.toString() === payload.emoji;
939
1129
  });
940
1130
  if (!reaction) {
941
- throw new ActionExecutionError("NOT_FOUND", `Reaction "${payload.emoji}" was not found on message "${payload.messageId}".`);
1131
+ throw new ActionExecutionError(
1132
+ "NOT_FOUND",
1133
+ `Reaction "${payload.emoji}" was not found on message "${payload.messageId}".`
1134
+ );
942
1135
  }
943
1136
  await reaction.users.remove(ownUserId);
944
1137
  return {
@@ -947,6 +1140,112 @@ var DiscordJsRuntimeAdapter = class {
947
1140
  emoji: payload.emoji
948
1141
  };
949
1142
  }
1143
+ static maxTimeoutMs = 28 * 24 * 60 * 60 * 1e3;
1144
+ async timeoutMember(payload) {
1145
+ if (typeof payload.durationMs !== "number" || !Number.isFinite(payload.durationMs) || payload.durationMs < 1 || payload.durationMs > _DiscordJsRuntimeAdapter.maxTimeoutMs) {
1146
+ throw new ActionExecutionError(
1147
+ "INVALID_REQUEST",
1148
+ `durationMs must be between 1 and ${_DiscordJsRuntimeAdapter.maxTimeoutMs} (28 days).`
1149
+ );
1150
+ }
1151
+ const guild = await this.client.guilds.fetch(payload.guildId);
1152
+ const member = await guild.members.fetch(payload.userId);
1153
+ await member.timeout(payload.durationMs, payload.reason);
1154
+ return {
1155
+ guildId: payload.guildId,
1156
+ userId: payload.userId
1157
+ };
1158
+ }
1159
+ async removeMemberTimeout(payload) {
1160
+ const guild = await this.client.guilds.fetch(payload.guildId);
1161
+ const member = await guild.members.fetch(payload.userId);
1162
+ await member.timeout(null, payload.reason);
1163
+ return serializeGuildMember(await member.fetch());
1164
+ }
1165
+ async createChannel(payload) {
1166
+ const guild = await this.client.guilds.fetch(payload.guildId);
1167
+ const created = await guild.channels.create({
1168
+ name: payload.name,
1169
+ type: payload.type ?? ChannelType.GuildText,
1170
+ ...payload.parentId ? { parent: payload.parentId } : {},
1171
+ ...payload.topic !== void 0 ? { topic: payload.topic } : {},
1172
+ ...payload.reason ? { reason: payload.reason } : {}
1173
+ });
1174
+ return serializeChannel(created);
1175
+ }
1176
+ async editChannel(payload) {
1177
+ const raw = await this.client.channels.fetch(payload.channelId);
1178
+ if (!raw) {
1179
+ throw new ActionExecutionError("NOT_FOUND", `Channel "${payload.channelId}" was not found.`);
1180
+ }
1181
+ if (!("edit" in raw) || typeof raw.edit !== "function") {
1182
+ throw new ActionExecutionError("INVALID_REQUEST", `Channel "${payload.channelId}" cannot be edited.`);
1183
+ }
1184
+ const edited = await raw.edit({
1185
+ ...payload.name !== void 0 ? { name: payload.name ?? void 0 } : {},
1186
+ ...payload.parentId !== void 0 ? { parent: payload.parentId } : {},
1187
+ ...payload.topic !== void 0 ? { topic: payload.topic } : {},
1188
+ ...payload.reason ? { reason: payload.reason } : {}
1189
+ });
1190
+ return serializeChannel(edited);
1191
+ }
1192
+ async deleteChannel(payload) {
1193
+ const raw = await this.client.channels.fetch(payload.channelId);
1194
+ if (!raw) {
1195
+ throw new ActionExecutionError("NOT_FOUND", `Channel "${payload.channelId}" was not found.`);
1196
+ }
1197
+ if (!("delete" in raw) || typeof raw.delete !== "function") {
1198
+ throw new ActionExecutionError("INVALID_REQUEST", `Channel "${payload.channelId}" cannot be deleted.`);
1199
+ }
1200
+ await raw.delete(payload.reason);
1201
+ return {
1202
+ deleted: true,
1203
+ channelId: payload.channelId
1204
+ };
1205
+ }
1206
+ async createThread(payload) {
1207
+ const parent = await this.client.channels.fetch(payload.parentChannelId);
1208
+ if (!parent || !parent.isTextBased()) {
1209
+ throw new ActionExecutionError(
1210
+ "NOT_FOUND",
1211
+ `Parent channel "${payload.parentChannelId}" was not found or is not text-based.`
1212
+ );
1213
+ }
1214
+ if (payload.messageId) {
1215
+ if (!("messages" in parent) || !parent.messages) {
1216
+ throw new ActionExecutionError(
1217
+ "INVALID_REQUEST",
1218
+ `Channel "${payload.parentChannelId}" does not support fetching messages.`
1219
+ );
1220
+ }
1221
+ const msg = await parent.messages.fetch(payload.messageId);
1222
+ const thread2 = await msg.startThread({
1223
+ name: payload.name,
1224
+ ...payload.reason ? { reason: payload.reason } : {},
1225
+ ...payload.autoArchiveDuration ? { autoArchiveDuration: payload.autoArchiveDuration } : {}
1226
+ });
1227
+ return serializeThread(thread2);
1228
+ }
1229
+ if (!("threads" in parent) || !parent.threads || typeof parent.threads.create !== "function") {
1230
+ throw new ActionExecutionError("INVALID_REQUEST", `Channel "${payload.parentChannelId}" cannot create threads.`);
1231
+ }
1232
+ const threadOptions = Object.assign(
1233
+ { name: payload.name },
1234
+ payload.type === "private" ? { type: ChannelType.PrivateThread } : {},
1235
+ payload.reason ? { reason: payload.reason } : {},
1236
+ payload.autoArchiveDuration ? { autoArchiveDuration: payload.autoArchiveDuration } : {}
1237
+ );
1238
+ const thread = await parent.threads.create(threadOptions);
1239
+ return serializeThread(thread);
1240
+ }
1241
+ async archiveThread(payload) {
1242
+ const raw = await this.client.channels.fetch(payload.threadId);
1243
+ if (!raw || !raw.isThread()) {
1244
+ throw new ActionExecutionError("NOT_FOUND", `Thread "${payload.threadId}" was not found.`);
1245
+ }
1246
+ await raw.setArchived(payload.archived ?? true, payload.reason);
1247
+ return serializeThread(raw);
1248
+ }
950
1249
  };
951
1250
  function createDiscordJsRuntimeAdapter(options) {
952
1251
  return new DiscordJsRuntimeAdapter(options);
@@ -968,6 +1267,7 @@ function createConnectionId() {
968
1267
  }
969
1268
 
970
1269
  // src/bridge/subscriptions.ts
1270
+ var THREAD_CHANNEL_TYPES = /* @__PURE__ */ new Set([10, 11, 12]);
971
1271
  function normalizeStringList(value) {
972
1272
  if (value === void 0) {
973
1273
  return void 0;
@@ -976,12 +1276,24 @@ function normalizeStringList(value) {
976
1276
  const normalized = [...new Set(rawValues.filter((entry) => typeof entry === "string" && entry.length > 0))].sort();
977
1277
  return normalized.length > 0 ? normalized : void 0;
978
1278
  }
1279
+ function normalizeNumberList(value) {
1280
+ if (value === void 0) {
1281
+ return void 0;
1282
+ }
1283
+ const rawValues = Array.isArray(value) ? value : [value];
1284
+ const normalized = [
1285
+ ...new Set(rawValues.filter((entry) => typeof entry === "number" && Number.isFinite(entry)))
1286
+ ].sort((a, b) => a - b);
1287
+ return normalized.length > 0 ? normalized : void 0;
1288
+ }
979
1289
  function normalizeKindList(value) {
980
1290
  if (value === void 0) {
981
1291
  return void 0;
982
1292
  }
983
1293
  const rawValues = Array.isArray(value) ? value : [value];
984
- const normalized = [...new Set(rawValues.filter((entry) => typeof entry === "string"))].sort();
1294
+ const normalized = [
1295
+ ...new Set(rawValues.filter((entry) => typeof entry === "string"))
1296
+ ].sort();
985
1297
  return normalized.length > 0 ? normalized : void 0;
986
1298
  }
987
1299
  function normalizeEventSubscriptionFilter(filter) {
@@ -995,6 +1307,9 @@ function normalizeEventSubscriptionFilter(filter) {
995
1307
  const commandNames = normalizeStringList(filter.commandName);
996
1308
  const customIds = normalizeStringList(filter.customId);
997
1309
  const interactionKinds = normalizeKindList(filter.interactionKind);
1310
+ const channelTypes = normalizeNumberList(filter.channelType);
1311
+ const parentChannelIds = normalizeStringList(filter.parentChannelId);
1312
+ const threadIds = normalizeStringList(filter.threadId);
998
1313
  if (guildIds) {
999
1314
  normalized.guildId = guildIds;
1000
1315
  }
@@ -1013,6 +1328,15 @@ function normalizeEventSubscriptionFilter(filter) {
1013
1328
  if (interactionKinds) {
1014
1329
  normalized.interactionKind = interactionKinds;
1015
1330
  }
1331
+ if (channelTypes) {
1332
+ normalized.channelType = channelTypes;
1333
+ }
1334
+ if (parentChannelIds) {
1335
+ normalized.parentChannelId = parentChannelIds;
1336
+ }
1337
+ if (threadIds) {
1338
+ normalized.threadId = threadIds;
1339
+ }
1016
1340
  return Object.keys(normalized).length > 0 ? normalized : void 0;
1017
1341
  }
1018
1342
  function normalizeEventSubscription(subscription) {
@@ -1043,6 +1367,24 @@ function matchesKind(value, allowed) {
1043
1367
  }
1044
1368
  return allowed.includes(value);
1045
1369
  }
1370
+ function matchesNumberField(value, allowed) {
1371
+ if (!allowed) {
1372
+ return true;
1373
+ }
1374
+ if (value === void 0) {
1375
+ return false;
1376
+ }
1377
+ return allowed.includes(value);
1378
+ }
1379
+ function threadIdFromMessageLike(message) {
1380
+ if (message.channelType === void 0) {
1381
+ return void 0;
1382
+ }
1383
+ if (THREAD_CHANNEL_TYPES.has(message.channelType)) {
1384
+ return message.channelId;
1385
+ }
1386
+ return void 0;
1387
+ }
1046
1388
  function eventMetadata(name, payload) {
1047
1389
  switch (name) {
1048
1390
  case "ready":
@@ -1050,36 +1392,66 @@ function eventMetadata(name, payload) {
1050
1392
  case "interactionCreate": {
1051
1393
  const interactionPayload = payload;
1052
1394
  const ix = interactionPayload.interaction;
1395
+ const threadId = ix.channelId && ix.channelType !== void 0 && THREAD_CHANNEL_TYPES.has(ix.channelType) ? ix.channelId : void 0;
1053
1396
  return {
1054
1397
  ...ix.guildId ? { guildId: ix.guildId } : {},
1055
1398
  ...ix.channelId ? { channelId: ix.channelId } : {},
1056
1399
  userId: ix.user.id,
1057
1400
  ...ix.commandName ? { commandName: ix.commandName } : {},
1058
1401
  ...ix.customId ? { customId: ix.customId } : {},
1059
- interactionKind: ix.kind
1402
+ interactionKind: ix.kind,
1403
+ ...ix.channelType !== void 0 ? { channelType: ix.channelType } : {},
1404
+ ...ix.parentChannelId ? { parentChannelId: ix.parentChannelId } : {},
1405
+ ...threadId ? { threadId } : {}
1060
1406
  };
1061
1407
  }
1062
1408
  case "messageCreate": {
1063
1409
  const messagePayload = payload;
1410
+ const msg = messagePayload.message;
1411
+ const threadId = threadIdFromMessageLike(msg);
1064
1412
  return {
1065
- ...messagePayload.message.guildId ? { guildId: messagePayload.message.guildId } : {},
1066
- channelId: messagePayload.message.channelId,
1067
- ...messagePayload.message.author ? { userId: messagePayload.message.author.id } : {}
1413
+ ...msg.guildId ? { guildId: msg.guildId } : {},
1414
+ channelId: msg.channelId,
1415
+ ...msg.author ? { userId: msg.author.id } : {},
1416
+ ...msg.channelType !== void 0 ? { channelType: msg.channelType } : {},
1417
+ ...msg.parentChannelId ? { parentChannelId: msg.parentChannelId } : {},
1418
+ ...threadId ? { threadId } : {}
1068
1419
  };
1069
1420
  }
1070
1421
  case "messageUpdate": {
1071
1422
  const messagePayload = payload;
1423
+ const msg = messagePayload.message;
1424
+ const threadId = threadIdFromMessageLike(msg);
1072
1425
  return {
1073
- ...messagePayload.message.guildId ? { guildId: messagePayload.message.guildId } : {},
1074
- channelId: messagePayload.message.channelId,
1075
- ...messagePayload.message.author ? { userId: messagePayload.message.author.id } : {}
1426
+ ...msg.guildId ? { guildId: msg.guildId } : {},
1427
+ channelId: msg.channelId,
1428
+ ...msg.author ? { userId: msg.author.id } : {},
1429
+ ...msg.channelType !== void 0 ? { channelType: msg.channelType } : {},
1430
+ ...msg.parentChannelId ? { parentChannelId: msg.parentChannelId } : {},
1431
+ ...threadId ? { threadId } : {}
1076
1432
  };
1077
1433
  }
1078
1434
  case "messageDelete": {
1079
1435
  const messagePayload = payload;
1436
+ const msg = messagePayload.message;
1437
+ const threadId = threadIdFromMessageLike(msg);
1080
1438
  return {
1081
- ...messagePayload.message.guildId ? { guildId: messagePayload.message.guildId } : {},
1082
- channelId: messagePayload.message.channelId
1439
+ ...msg.guildId ? { guildId: msg.guildId } : {},
1440
+ channelId: msg.channelId,
1441
+ ...msg.channelType !== void 0 ? { channelType: msg.channelType } : {},
1442
+ ...msg.parentChannelId ? { parentChannelId: msg.parentChannelId } : {},
1443
+ ...threadId ? { threadId } : {}
1444
+ };
1445
+ }
1446
+ case "messageBulkDelete": {
1447
+ const bulkPayload = payload;
1448
+ const threadId = bulkPayload.channelType !== void 0 && THREAD_CHANNEL_TYPES.has(bulkPayload.channelType) ? bulkPayload.channelId : void 0;
1449
+ return {
1450
+ guildId: bulkPayload.guildId,
1451
+ channelId: bulkPayload.channelId,
1452
+ ...bulkPayload.channelType !== void 0 ? { channelType: bulkPayload.channelType } : {},
1453
+ ...bulkPayload.parentChannelId ? { parentChannelId: bulkPayload.parentChannelId } : {},
1454
+ ...threadId ? { threadId } : {}
1083
1455
  };
1084
1456
  }
1085
1457
  case "messageReactionAdd": {
@@ -1132,7 +1504,22 @@ function eventMetadata(name, payload) {
1132
1504
  const threadPayload = payload;
1133
1505
  return {
1134
1506
  guildId: threadPayload.thread.guildId,
1135
- ...threadPayload.thread.parentId ? { channelId: threadPayload.thread.parentId } : { channelId: threadPayload.thread.id }
1507
+ ...threadPayload.thread.parentId ? { channelId: threadPayload.thread.parentId } : { channelId: threadPayload.thread.id },
1508
+ channelType: threadPayload.thread.type,
1509
+ ...threadPayload.thread.parentId ? { parentChannelId: threadPayload.thread.parentId } : {},
1510
+ threadId: threadPayload.thread.id
1511
+ };
1512
+ }
1513
+ case "channelCreate":
1514
+ case "channelUpdate":
1515
+ case "channelDelete": {
1516
+ const channelPayload = payload;
1517
+ const ch = channelPayload.channel;
1518
+ return {
1519
+ ...ch.guildId ? { guildId: ch.guildId } : {},
1520
+ channelId: ch.id,
1521
+ channelType: ch.type,
1522
+ ...ch.parentId ? { parentChannelId: ch.parentId } : {}
1136
1523
  };
1137
1524
  }
1138
1525
  default:
@@ -1145,7 +1532,7 @@ function matchesEventSubscription(subscription, payload) {
1145
1532
  return true;
1146
1533
  }
1147
1534
  const metadata = eventMetadata(normalized.name, payload);
1148
- return matchesField(metadata.guildId, normalized.filter.guildId) && matchesField(metadata.channelId, normalized.filter.channelId) && matchesField(metadata.userId, normalized.filter.userId) && matchesField(metadata.commandName, normalized.filter.commandName) && matchesField(metadata.customId, normalized.filter.customId) && matchesKind(metadata.interactionKind, normalized.filter.interactionKind);
1535
+ return matchesField(metadata.guildId, normalized.filter.guildId) && matchesField(metadata.channelId, normalized.filter.channelId) && matchesField(metadata.userId, normalized.filter.userId) && matchesField(metadata.commandName, normalized.filter.commandName) && matchesField(metadata.customId, normalized.filter.customId) && matchesKind(metadata.interactionKind, normalized.filter.interactionKind) && matchesNumberField(metadata.channelType, normalized.filter.channelType) && matchesField(metadata.parentChannelId, normalized.filter.parentChannelId) && matchesField(metadata.threadId, normalized.filter.threadId);
1149
1536
  }
1150
1537
 
1151
1538
  // src/bridge/transport/security.ts
@@ -1247,6 +1634,8 @@ var BridgeTransportServer = class {
1247
1634
  const maxConcurrent = config.options.server.maxConcurrentActions ?? 32;
1248
1635
  const queueTimeout = config.options.server.actionQueueTimeoutMs ?? 5e3;
1249
1636
  this.actionSemaphore = new AsyncSemaphore(maxConcurrent, queueTimeout);
1637
+ this.idempotencyTtlMs = config.options.server.idempotencyTtlMs ?? 12e4;
1638
+ this.idempotencyScope = config.options.server.idempotencyScope ?? "connection";
1250
1639
  this.wss = new WebSocketServer({
1251
1640
  host: config.options.server.host,
1252
1641
  port: config.options.server.port,
@@ -1269,7 +1658,8 @@ var BridgeTransportServer = class {
1269
1658
  stickyEvents = /* @__PURE__ */ new Map();
1270
1659
  actionSemaphore;
1271
1660
  idempotencyCache = /* @__PURE__ */ new Map();
1272
- idempotencyTtlMs = 12e4;
1661
+ idempotencyTtlMs;
1662
+ idempotencyScope;
1273
1663
  authBuckets = /* @__PURE__ */ new Map();
1274
1664
  connectionCount() {
1275
1665
  let count = 0;
@@ -1499,7 +1889,7 @@ var BridgeTransportServer = class {
1499
1889
  }
1500
1890
  const idempotencyRaw = payload.idempotencyKey;
1501
1891
  const idempotencyKey = typeof idempotencyRaw === "string" && idempotencyRaw.length > 0 && idempotencyRaw.length <= 256 ? idempotencyRaw : void 0;
1502
- const idempotencyCacheKey = idempotencyKey ? `${state.id}:${idempotencyKey}` : void 0;
1892
+ const idempotencyCacheKey = idempotencyKey ? this.idempotencyScope === "secret" && activeSecret ? `secret:${activeSecret.id}:${idempotencyKey}` : `conn:${state.id}:${idempotencyKey}` : void 0;
1503
1893
  if (idempotencyCacheKey) {
1504
1894
  this.pruneIdempotencyCache(Date.now());
1505
1895
  const cached = this.idempotencyCache.get(idempotencyCacheKey);
@@ -1512,9 +1902,7 @@ var BridgeTransportServer = class {
1512
1902
  });
1513
1903
  this.safeSend(
1514
1904
  state.socket,
1515
- stringifyEnvelope(
1516
- makeEnvelope(replay.ok ? "action.result" : "action.error", replay, { requestId })
1517
- )
1905
+ stringifyEnvelope(makeEnvelope(replay.ok ? "action.result" : "action.error", replay, { requestId }))
1518
1906
  );
1519
1907
  return;
1520
1908
  }
@@ -1705,11 +2093,7 @@ function normalizeSecretEntry(secret, index) {
1705
2093
  value: scoped.value,
1706
2094
  scope: {
1707
2095
  events: normalizeScopeList(scoped.allow?.events, BOT_EVENT_NAMES, `server.secrets[${index}].allow.events`),
1708
- actions: normalizeScopeList(
1709
- scoped.allow?.actions,
1710
- BOT_ACTION_NAMES,
1711
- `server.secrets[${index}].allow.actions`
1712
- )
2096
+ actions: normalizeScopeList(scoped.allow?.actions, BOT_ACTION_NAMES, `server.secrets[${index}].allow.actions`)
1713
2097
  }
1714
2098
  };
1715
2099
  }
@@ -1736,6 +2120,14 @@ function assertBotBridgeOptions(options) {
1736
2120
  if (options.server.actionQueueTimeoutMs !== void 0) {
1737
2121
  assertPositiveNumber("server.actionQueueTimeoutMs", options.server.actionQueueTimeoutMs);
1738
2122
  }
2123
+ if (options.server.idempotencyScope !== void 0) {
2124
+ if (options.server.idempotencyScope !== "connection" && options.server.idempotencyScope !== "secret") {
2125
+ throw new Error('server.idempotencyScope must be "connection" or "secret".');
2126
+ }
2127
+ }
2128
+ if (options.server.idempotencyTtlMs !== void 0) {
2129
+ assertPositiveNumber("server.idempotencyTtlMs", options.server.idempotencyTtlMs);
2130
+ }
1739
2131
  if (!Array.isArray(options.server.secrets) || options.server.secrets.length === 0) {
1740
2132
  throw new Error("server.secrets must contain at least one secret.");
1741
2133
  }
@@ -1935,6 +2327,24 @@ var AppRequestError = class extends Error {
1935
2327
  code;
1936
2328
  };
1937
2329
  var DEFAULT_REQUEST_TIMEOUT_MS = 1e4;
2330
+ function metricsExtrasFromActionError(error) {
2331
+ const details = error.details;
2332
+ if (!details || typeof details !== "object" || Array.isArray(details)) {
2333
+ return {};
2334
+ }
2335
+ const obj = details;
2336
+ const extras = {};
2337
+ if (typeof obj.retryAfterMs === "number" && Number.isFinite(obj.retryAfterMs)) {
2338
+ extras.retryAfterMs = obj.retryAfterMs;
2339
+ }
2340
+ if (typeof obj.discordStatus === "number" && Number.isFinite(obj.discordStatus)) {
2341
+ extras.discordStatus = obj.discordStatus;
2342
+ }
2343
+ if (typeof obj.discordCode === "number" && Number.isFinite(obj.discordCode)) {
2344
+ extras.discordCode = obj.discordCode;
2345
+ }
2346
+ return extras;
2347
+ }
1938
2348
  function connectBotBridge(options) {
1939
2349
  assertAppBridgeOptions(options);
1940
2350
  const logger = withLogger(options.logger);
@@ -2243,7 +2653,7 @@ function connectBotBridge(options) {
2243
2653
  requestId,
2244
2654
  durationMs: Date.now() - started,
2245
2655
  ok: result.ok,
2246
- ...!result.ok ? { errorCode: result.error.code } : {}
2656
+ ...!result.ok ? { errorCode: result.error.code, ...metricsExtrasFromActionError(result.error) } : {}
2247
2657
  });
2248
2658
  return result;
2249
2659
  } catch (error) {
@@ -2285,7 +2695,14 @@ function connectBotBridge(options) {
2285
2695
  addMemberRole: (payload, sendOptions) => invokeAction("addMemberRole", payload, sendOptions),
2286
2696
  removeMemberRole: (payload, sendOptions) => invokeAction("removeMemberRole", payload, sendOptions),
2287
2697
  addMessageReaction: (payload, sendOptions) => invokeAction("addMessageReaction", payload, sendOptions),
2288
- removeOwnMessageReaction: (payload, sendOptions) => invokeAction("removeOwnMessageReaction", payload, sendOptions)
2698
+ removeOwnMessageReaction: (payload, sendOptions) => invokeAction("removeOwnMessageReaction", payload, sendOptions),
2699
+ timeoutMember: (payload, sendOptions) => invokeAction("timeoutMember", payload, sendOptions),
2700
+ removeMemberTimeout: (payload, sendOptions) => invokeAction("removeMemberTimeout", payload, sendOptions),
2701
+ createChannel: (payload, sendOptions) => invokeAction("createChannel", payload, sendOptions),
2702
+ editChannel: (payload, sendOptions) => invokeAction("editChannel", payload, sendOptions),
2703
+ deleteChannel: (payload, sendOptions) => invokeAction("deleteChannel", payload, sendOptions),
2704
+ createThread: (payload, sendOptions) => invokeAction("createThread", payload, sendOptions),
2705
+ archiveThread: (payload, sendOptions) => invokeAction("archiveThread", payload, sendOptions)
2289
2706
  };
2290
2707
  return {
2291
2708
  actions,