@automagik/omni 2.260804.2 → 2.260830.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -24040,6 +24040,8 @@ var init_instance = __esm(() => {
24040
24040
  sessionIdPrefix: exports_external.string().max(50).nullable(),
24041
24041
  discordBotToken: exports_external.string().nullable(),
24042
24042
  slackBotToken: exports_external.string().nullable(),
24043
+ slackUserToken: exports_external.string().nullable(),
24044
+ slackAuthMode: exports_external.string().nullable(),
24043
24045
  slackAppToken: exports_external.string().nullable(),
24044
24046
  slackSigningSecret: exports_external.string().nullable(),
24045
24047
  telegramBotToken: exports_external.string().nullable(),
@@ -37375,6 +37377,82 @@ var init_download_guard = __esm(() => {
37375
37377
  };
37376
37378
  });
37377
37379
 
37380
+ // ../channel-sdk/src/interactive-plan.ts
37381
+ function buttonId(btn, index) {
37382
+ return (btn.data ?? btn.text ?? `btn_${index}`).slice(0, MAX_ID);
37383
+ }
37384
+ function planInteractive(bodyText, buttons, listButtonLabel, listOptions = {}) {
37385
+ const replyButtons = buttons.filter((b2) => !b2.url);
37386
+ const urlButtons = buttons.filter((b2) => b2.url);
37387
+ const soleCta = replyButtons.length === 0 && urlButtons.length === 1 ? urlButtons[0] : undefined;
37388
+ if (soleCta) {
37389
+ return {
37390
+ interactive: {
37391
+ type: "cta_url",
37392
+ body: { text: bodyText },
37393
+ action: {
37394
+ name: "cta_url",
37395
+ parameters: { display_text: truncate(soleCta.text, MAX_BUTTON_TITLE), url: soleCta.url }
37396
+ }
37397
+ },
37398
+ body: bodyText,
37399
+ droppedRows: 0
37400
+ };
37401
+ }
37402
+ let body = bodyText;
37403
+ if (urlButtons.length > 0) {
37404
+ const lines = urlButtons.map((b2) => `${b2.text}: ${b2.url}`);
37405
+ body = body ? `${body}
37406
+
37407
+ ${lines.join(`
37408
+ `)}` : lines.join(`
37409
+ `);
37410
+ }
37411
+ if (replyButtons.length === 0) {
37412
+ return { interactive: null, body, droppedRows: 0 };
37413
+ }
37414
+ const wantsList = listOptions.forceList === true || listOptions.sectionTitle !== undefined || replyButtons.some((b2) => b2.description !== undefined);
37415
+ if (replyButtons.length <= MAX_REPLY_BUTTONS && !wantsList) {
37416
+ return {
37417
+ interactive: {
37418
+ type: "button",
37419
+ body: { text: body },
37420
+ action: {
37421
+ buttons: replyButtons.map((b2, i) => ({
37422
+ type: "reply",
37423
+ reply: { id: buttonId(b2, i), title: truncate(b2.text, MAX_BUTTON_TITLE) }
37424
+ }))
37425
+ }
37426
+ },
37427
+ body,
37428
+ droppedRows: 0
37429
+ };
37430
+ }
37431
+ const rows = replyButtons.slice(0, MAX_LIST_ROWS);
37432
+ return {
37433
+ interactive: {
37434
+ type: "list",
37435
+ body: { text: body },
37436
+ action: {
37437
+ button: truncate(listButtonLabel, MAX_BUTTON_TITLE),
37438
+ sections: [
37439
+ {
37440
+ ...listOptions.sectionTitle ? { title: truncate(listOptions.sectionTitle, MAX_SECTION_TITLE) } : {},
37441
+ rows: rows.map((b2, i) => ({
37442
+ id: buttonId(b2, i),
37443
+ title: truncate(b2.text, MAX_ROW_TITLE),
37444
+ ...b2.description ? { description: truncate(b2.description, MAX_ROW_DESCRIPTION) } : {}
37445
+ }))
37446
+ }
37447
+ ]
37448
+ }
37449
+ },
37450
+ body,
37451
+ droppedRows: replyButtons.length - rows.length
37452
+ };
37453
+ }
37454
+ var MAX_REPLY_BUTTONS = 3, MAX_LIST_ROWS = 10, MAX_BUTTON_TITLE = 20, MAX_ROW_TITLE = 24, MAX_ROW_DESCRIPTION = 72, MAX_SECTION_TITLE = 24, MAX_ID = 256, truncate = (s, max) => s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
37455
+
37378
37456
  // ../channel-sdk/src/media-backends/config.ts
37379
37457
  function boolFromEnv(defaultValue) {
37380
37458
  return exports_external.string().optional().transform((value) => {
@@ -37929,6 +38007,7 @@ __export(exports_src2, {
37929
38007
  sanitizeOutboundText: () => sanitizeOutboundText,
37930
38008
  sanitizeMessage: () => sanitizeMessage,
37931
38009
  resolveMediaBackendConfig: () => resolveMediaBackendConfig,
38010
+ planInteractive: () => planInteractive,
37932
38011
  parseSubject: () => parseSubject,
37933
38012
  parseAssumeRoleWithWebIdentityResponse: () => parseAssumeRoleWithWebIdentityResponse,
37934
38013
  matchesPattern: () => matchesPattern,
@@ -138163,11 +138242,11 @@ var require_applyToDefaults = __commonJS((exports, module) => {
138163
138242
  return Merge(copy, source, { nullOverride, mergeArrays: false });
138164
138243
  };
138165
138244
  internals.reachCopy = function(dst, src, path) {
138166
- for (const segment of path) {
138167
- if (!(segment in src)) {
138245
+ for (const segment2 of path) {
138246
+ if (!(segment2 in src)) {
138168
138247
  return;
138169
138248
  }
138170
- const val = src[segment];
138249
+ const val = src[segment2];
138171
138250
  if (typeof val !== "object" || val === null) {
138172
138251
  return;
138173
138252
  }
@@ -138176,11 +138255,11 @@ var require_applyToDefaults = __commonJS((exports, module) => {
138176
138255
  const value = src;
138177
138256
  let ref = dst;
138178
138257
  for (let i = 0;i < path.length - 1; ++i) {
138179
- const segment = path[i];
138180
- if (typeof ref[segment] !== "object") {
138181
- ref[segment] = {};
138258
+ const segment2 = path[i];
138259
+ if (typeof ref[segment2] !== "object") {
138260
+ ref[segment2] = {};
138182
138261
  }
138183
- ref = ref[segment];
138262
+ ref = ref[segment2];
138184
138263
  }
138185
138264
  ref[path[path.length - 1]] = value;
138186
138265
  };
@@ -156479,11 +156558,11 @@ var require_applyToDefaults2 = __commonJS((exports, module) => {
156479
156558
  return Merge(copy, source, { nullOverride, mergeArrays: false });
156480
156559
  };
156481
156560
  internals.reachCopy = function(dst, src, path) {
156482
- for (const segment of path) {
156483
- if (!(segment in src)) {
156561
+ for (const segment2 of path) {
156562
+ if (!(segment2 in src)) {
156484
156563
  return;
156485
156564
  }
156486
- const val = src[segment];
156565
+ const val = src[segment2];
156487
156566
  if (typeof val !== "object" || val === null) {
156488
156567
  return;
156489
156568
  }
@@ -156492,11 +156571,11 @@ var require_applyToDefaults2 = __commonJS((exports, module) => {
156492
156571
  const value = src;
156493
156572
  let ref = dst;
156494
156573
  for (let i = 0;i < path.length - 1; ++i) {
156495
- const segment = path[i];
156496
- if (typeof ref[segment] !== "object") {
156497
- ref[segment] = {};
156574
+ const segment2 = path[i];
156575
+ if (typeof ref[segment2] !== "object") {
156576
+ ref[segment2] = {};
156498
156577
  }
156499
- ref = ref[segment];
156578
+ ref = ref[segment2];
156500
156579
  }
156501
156580
  ref[path[path.length - 1]] = value;
156502
156581
  };
@@ -164262,6 +164341,9 @@ __export(exports_schema, {
164262
164341
  settingValueTypes: () => settingValueTypes,
164263
164342
  settingChangeHistoryRelations: () => settingChangeHistoryRelations,
164264
164343
  settingChangeHistory: () => settingChangeHistory,
164344
+ scheduledMessages: () => scheduledMessages,
164345
+ scheduledMessageStatuses: () => scheduledMessageStatuses,
164346
+ scheduledMessageDeliveryModes: () => scheduledMessageDeliveryModes,
164265
164347
  ruleTypes: () => ruleTypes,
164266
164348
  replyFilterMode: () => replyFilterMode,
164267
164349
  providerSchemas: () => providerSchemas,
@@ -164362,7 +164444,7 @@ __export(exports_schema, {
164362
164444
  accessRules: () => accessRules,
164363
164445
  accessModes: () => accessModes
164364
164446
  });
164365
- var channelTypes, agentTypes, agentSystems, agentEntityTypes, debounceMode, splitDelayMode, supersedeMode, replyFilterMode, agentSessionStrategies, ruleTypes, accessModes, settingValueTypes, apiKeyStatuses, apiKeyProfiles, eventTypes, contentTypes, chatTypes, messageSources, messageTypes, messageStatuses, deliveryStatuses, jobStatuses, providerSchemas, agentProviders, agents, agentRoutes, agentSessions, apiKeys, apiKeyAuditLogs, apiKeysRelations, apiKeyAuditLogsRelations, instances, whatsappTemplates, whatsappFlowKeys, persons, platformIdentities, conversations, chats, chatParticipants, omniGroups, messages2, omniEvents, handoffLogs, closeContactOutcomes, closeContactLogs, accessRules, globalSettings, settingChangeHistory, batchJobs, syncJobTypes, syncJobs, mediaContent, chatIdMappings, pluginStorage, agentProvidersRelations, agentsRelations, instancesRelations, syncJobsRelations, personsRelations, platformIdentitiesRelations, conversationsRelations, chatsRelations, chatParticipantsRelations, messagesRelations, omniEventsRelations, accessRulesRelations, globalSettingsRelations, settingChangeHistoryRelations, batchJobsRelations, mediaContentRelations, chatIdMappingsRelations, deadLetterStatuses, deadLetterEvents, payloadStorageConfig, payloadStages, eventPayloads, webhookSources, conditionOperators, actionTypes, automationDebounceModes, automations2, automationLogStatuses, automationLogs, consumerOffsets, automationsRelations, automationLogsRelations, triggerLogs, triggerLogsRelations, agentRoutesRelations, agentTaskStatuses, agentTasks, agentTasksRelations, turnStatuses, turnActions, turns, turnsRelations, followUpDisarmReasons, chatFollowUpState, chatFollowUpStateRelations, processedEvents, genieHosts, tenantStatuses, principalTypes, principalStatuses, tenantRoles, membershipStatuses, credentialClasses, authCredentialStatuses, platformApiKeyStatuses, tenants, principals, tenantMemberships, tenantRolePolicies, platformApiKeys, tenantKeyLineage, authCredentials, tenantAuditLogs, platformAuditLogs, platformProviderCatalog, tenantProviderConfig, platformSettings, tenantSettings, platformSettingChangeHistory, tenantSettingChangeHistory, platformPluginStorage, tenantPluginStorage, platformPayloadStorageConfig, tenantPayloadStorageOverrides, tenantMigrationLedger, tenantMigrationLedgerHistory;
164447
+ var channelTypes, agentTypes, agentSystems, agentEntityTypes, debounceMode, splitDelayMode, supersedeMode, replyFilterMode, agentSessionStrategies, ruleTypes, accessModes, settingValueTypes, apiKeyStatuses, apiKeyProfiles, eventTypes, contentTypes, chatTypes, messageSources, messageTypes, messageStatuses, deliveryStatuses, scheduledMessageDeliveryModes, scheduledMessageStatuses, jobStatuses, providerSchemas, agentProviders, agents, agentRoutes, agentSessions, apiKeys, apiKeyAuditLogs, apiKeysRelations, apiKeyAuditLogsRelations, instances, whatsappTemplates, whatsappFlowKeys, scheduledMessages, persons, platformIdentities, conversations, chats, chatParticipants, omniGroups, messages2, omniEvents, handoffLogs, closeContactOutcomes, closeContactLogs, accessRules, globalSettings, settingChangeHistory, batchJobs, syncJobTypes, syncJobs, mediaContent, chatIdMappings, pluginStorage, agentProvidersRelations, agentsRelations, instancesRelations, syncJobsRelations, personsRelations, platformIdentitiesRelations, conversationsRelations, chatsRelations, chatParticipantsRelations, messagesRelations, omniEventsRelations, accessRulesRelations, globalSettingsRelations, settingChangeHistoryRelations, batchJobsRelations, mediaContentRelations, chatIdMappingsRelations, deadLetterStatuses, deadLetterEvents, payloadStorageConfig, payloadStages, eventPayloads, webhookSources, conditionOperators, actionTypes, automationDebounceModes, automations2, automationLogStatuses, automationLogs, consumerOffsets, automationsRelations, automationLogsRelations, triggerLogs, triggerLogsRelations, agentRoutesRelations, agentTaskStatuses, agentTasks, agentTasksRelations, turnStatuses, turnActions, turns, turnsRelations, followUpDisarmReasons, chatFollowUpState, chatFollowUpStateRelations, processedEvents, genieHosts, tenantStatuses, principalTypes, principalStatuses, tenantRoles, membershipStatuses, credentialClasses, authCredentialStatuses, platformApiKeyStatuses, tenants, principals, tenantMemberships, tenantRolePolicies, platformApiKeys, tenantKeyLineage, authCredentials, tenantAuditLogs, platformAuditLogs, platformProviderCatalog, tenantProviderConfig, platformSettings, tenantSettings, platformSettingChangeHistory, tenantSettingChangeHistory, platformPluginStorage, tenantPluginStorage, platformPayloadStorageConfig, tenantPayloadStorageOverrides, tenantMigrationLedger, tenantMigrationLedgerHistory;
164366
164448
  var init_schema2 = __esm(() => {
164367
164449
  init_events();
164368
164450
  init_types5();
@@ -164437,6 +164519,8 @@ var init_schema2 = __esm(() => {
164437
164519
  ];
164438
164520
  messageStatuses = ["active", "edited", "deleted", "expired"];
164439
164521
  deliveryStatuses = ["pending", "sent", "delivered", "read", "failed"];
164522
+ scheduledMessageDeliveryModes = ["platform", "local"];
164523
+ scheduledMessageStatuses = ["pending", "sending", "sent", "canceled", "failed"];
164440
164524
  jobStatuses = ["pending", "running", "completed", "failed", "cancelled"];
164441
164525
  providerSchemas = [
164442
164526
  "agno",
@@ -164639,6 +164723,8 @@ var init_schema2 = __esm(() => {
164639
164723
  guildConfigOverrides: jsonb("guild_config_overrides").$type(),
164640
164724
  discordPresence: jsonb("discord_presence").$type(),
164641
164725
  slackBotToken: text("slack_bot_token"),
164726
+ slackUserToken: text("slack_user_token"),
164727
+ slackAuthMode: varchar("slack_auth_mode", { length: 10 }),
164642
164728
  slackAppToken: text("slack_app_token"),
164643
164729
  slackSigningSecret: text("slack_signing_secret"),
164644
164730
  telegramBotToken: text("telegram_bot_token"),
@@ -164783,6 +164869,31 @@ var init_schema2 = __esm(() => {
164783
164869
  }, (t) => ({
164784
164870
  instanceUnique: uniqueIndex("idx_wa_flow_keys_instance").on(t.instanceId)
164785
164871
  }));
164872
+ scheduledMessages = pgTable("scheduled_messages", {
164873
+ id: uuid("id").primaryKey().defaultRandom(),
164874
+ instanceId: uuid("instance_id").notNull().references(() => instances.id, { onDelete: "cascade" }),
164875
+ chatExternalId: varchar("chat_external_id", { length: 255 }).notNull(),
164876
+ threadExternalId: varchar("thread_external_id", { length: 255 }),
164877
+ isThreadBroadcast: boolean("is_thread_broadcast").notNull().default(false),
164878
+ content: jsonb("content").$type().notNull(),
164879
+ sendAt: timestamp("send_at", { withTimezone: true }).notNull(),
164880
+ deliveryMode: varchar("delivery_mode", { length: 20 }).notNull().$type(),
164881
+ status: varchar("status", { length: 20 }).notNull().default("pending").$type(),
164882
+ externalScheduledId: varchar("external_scheduled_id", { length: 255 }),
164883
+ sentExternalId: varchar("sent_external_id", { length: 255 }),
164884
+ sentAt: timestamp("sent_at", { withTimezone: true }),
164885
+ canceledAt: timestamp("canceled_at", { withTimezone: true }),
164886
+ failedAt: timestamp("failed_at", { withTimezone: true }),
164887
+ lastError: text("last_error"),
164888
+ attemptCount: integer("attempt_count").notNull().default(0),
164889
+ createdByAgentId: uuid("created_by_agent_id"),
164890
+ createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
164891
+ updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow()
164892
+ }, (t) => ({
164893
+ dueIdx: index("scheduled_messages_due_idx").on(t.status, t.sendAt),
164894
+ instanceIdx: index("scheduled_messages_instance_idx").on(t.instanceId, t.status),
164895
+ chatIdx: index("scheduled_messages_chat_idx").on(t.instanceId, t.chatExternalId)
164896
+ }));
164786
164897
  persons = pgTable("persons", {
164787
164898
  id: uuid("id").primaryKey().defaultRandom(),
164788
164899
  displayName: varchar("display_name", { length: 255 }),
@@ -164967,6 +165078,15 @@ var init_schema2 = __esm(() => {
164967
165078
  replyToExternalId: varchar("reply_to_external_id", { length: 255 }),
164968
165079
  quotedText: text("quoted_text"),
164969
165080
  quotedSenderName: varchar("quoted_sender_name", { length: 255 }),
165081
+ threadExternalId: varchar("thread_external_id", { length: 255 }),
165082
+ threadRootMessageId: uuid("thread_root_message_id"),
165083
+ isThreadBroadcast: boolean("is_thread_broadcast").notNull().default(false),
165084
+ replyCount: integer("reply_count").notNull().default(0),
165085
+ latestReplyAt: timestamp("latest_reply_at", { withTimezone: true }),
165086
+ permalink: text("permalink"),
165087
+ pinnedAt: timestamp("pinned_at", { withTimezone: true }),
165088
+ pinnedBy: varchar("pinned_by", { length: 255 }),
165089
+ starredAt: timestamp("starred_at", { withTimezone: true }),
164970
165090
  forwardedFromMessageId: uuid("forwarded_from_message_id"),
164971
165091
  forwardedFromExternalId: varchar("forwarded_from_external_id", { length: 255 }),
164972
165092
  forwardCount: integer("forward_count").notNull().default(0),
@@ -165004,6 +165124,8 @@ var init_schema2 = __esm(() => {
165004
165124
  platformTimestampIdx: index("messages_platform_timestamp_idx").on(table3.platformTimestamp),
165005
165125
  replyToIdx: index("messages_reply_to_idx").on(table3.replyToMessageId),
165006
165126
  replyToExternalIdx: index("messages_reply_to_external_idx").on(table3.chatId, table3.replyToExternalId, table3.isFromMe),
165127
+ threadExternalIdx: index("messages_thread_external_idx").on(table3.chatId, table3.threadExternalId, table3.platformTimestamp),
165128
+ threadRootIdx: index("messages_thread_root_idx").on(table3.threadRootMessageId),
165007
165129
  hasMediaIdx: index("messages_has_media_idx").on(table3.hasMedia),
165008
165130
  originalEventIdx: index("messages_original_event_idx").on(table3.originalEventId)
165009
165131
  }));
@@ -169905,6 +170027,9 @@ __export(exports_src3, {
169905
170027
  settingChangeHistoryRelations: () => settingChangeHistoryRelations,
169906
170028
  settingChangeHistory: () => settingChangeHistory,
169907
170029
  scrubDdlCredential: () => scrubDdlCredential,
170030
+ scheduledMessages: () => scheduledMessages,
170031
+ scheduledMessageStatuses: () => scheduledMessageStatuses,
170032
+ scheduledMessageDeliveryModes: () => scheduledMessageDeliveryModes,
169908
170033
  ruleTypes: () => ruleTypes,
169909
170034
  roleAttributeViolations: () => roleAttributeViolations,
169910
170035
  revertTenantRlsEnforcement: () => revertTenantRlsEnforcement,
@@ -170620,80 +170745,7 @@ var init_reaction = __esm(() => {
170620
170745
  });
170621
170746
 
170622
170747
  // ../channel-whatsapp-business/src/senders/interactive.ts
170623
- function buttonId(btn, index2) {
170624
- return (btn.data ?? btn.text ?? `btn_${index2}`).slice(0, MAX_ID);
170625
- }
170626
- function planInteractive(bodyText, buttons, listButtonLabel, listOptions = {}) {
170627
- const replyButtons = buttons.filter((b3) => !b3.url);
170628
- const urlButtons = buttons.filter((b3) => b3.url);
170629
- const soleCta = replyButtons.length === 0 && urlButtons.length === 1 ? urlButtons[0] : undefined;
170630
- if (soleCta) {
170631
- return {
170632
- interactive: {
170633
- type: "cta_url",
170634
- body: { text: bodyText },
170635
- action: {
170636
- name: "cta_url",
170637
- parameters: { display_text: truncate2(soleCta.text, MAX_BUTTON_TITLE), url: soleCta.url }
170638
- }
170639
- },
170640
- body: bodyText,
170641
- droppedRows: 0
170642
- };
170643
- }
170644
- let body = bodyText;
170645
- if (urlButtons.length > 0) {
170646
- const lines = urlButtons.map((b3) => `${b3.text}: ${b3.url}`);
170647
- body = body ? `${body}
170648
-
170649
- ${lines.join(`
170650
- `)}` : lines.join(`
170651
- `);
170652
- }
170653
- if (replyButtons.length === 0) {
170654
- return { interactive: null, body, droppedRows: 0 };
170655
- }
170656
- const wantsList = listOptions.forceList === true || listOptions.sectionTitle !== undefined || replyButtons.some((b3) => b3.description !== undefined);
170657
- if (replyButtons.length <= MAX_REPLY_BUTTONS && !wantsList) {
170658
- return {
170659
- interactive: {
170660
- type: "button",
170661
- body: { text: body },
170662
- action: {
170663
- buttons: replyButtons.map((b3, i) => ({
170664
- type: "reply",
170665
- reply: { id: buttonId(b3, i), title: truncate2(b3.text, MAX_BUTTON_TITLE) }
170666
- }))
170667
- }
170668
- },
170669
- body,
170670
- droppedRows: 0
170671
- };
170672
- }
170673
- const rows = replyButtons.slice(0, MAX_LIST_ROWS);
170674
- return {
170675
- interactive: {
170676
- type: "list",
170677
- body: { text: body },
170678
- action: {
170679
- button: truncate2(listButtonLabel, MAX_BUTTON_TITLE),
170680
- sections: [
170681
- {
170682
- ...listOptions.sectionTitle ? { title: truncate2(listOptions.sectionTitle, MAX_SECTION_TITLE) } : {},
170683
- rows: rows.map((b3, i) => ({
170684
- id: buttonId(b3, i),
170685
- title: truncate2(b3.text, MAX_ROW_TITLE),
170686
- ...b3.description ? { description: truncate2(b3.description, MAX_ROW_DESCRIPTION) } : {}
170687
- }))
170688
- }
170689
- ]
170690
- }
170691
- },
170692
- body,
170693
- droppedRows: replyButtons.length - rows.length
170694
- };
170695
- }
170696
- async function sendLocationRequest(client, to, bodyText, replyTo) {
170748
+ async function sendLocationRequest2(client, to, bodyText, replyTo) {
170697
170749
  const payload = {
170698
170750
  messaging_product: "whatsapp",
170699
170751
  recipient_type: "individual",
@@ -170728,8 +170780,9 @@ async function sendInteractive(client, to, bodyText, buttons, replyTo, listButto
170728
170780
  payload.context = { message_id: replyTo };
170729
170781
  return { response: await client.sendMessage(payload), droppedRows: plan.droppedRows };
170730
170782
  }
170731
- var MAX_REPLY_BUTTONS = 3, MAX_LIST_ROWS = 10, MAX_BUTTON_TITLE = 20, MAX_ROW_TITLE = 24, MAX_ROW_DESCRIPTION = 72, MAX_SECTION_TITLE = 24, MAX_ID = 256, truncate2 = (s, max) => s.length <= max ? s : `${s.slice(0, max - 1)}\u2026`;
170732
- var init_interactive = () => {};
170783
+ var init_interactive = __esm(() => {
170784
+ init_src2();
170785
+ });
170733
170786
 
170734
170787
  // ../channel-whatsapp-business/src/senders/flow.ts
170735
170788
  async function sendFlow(client, to, flow, replyTo) {
@@ -170807,7 +170860,7 @@ var init_senders = __esm(() => {
170807
170860
  async function dispatchOutbound2(client, message2, logger6) {
170808
170861
  const { content, to, replyTo } = message2;
170809
170862
  if (content.type === "text") {
170810
- return { ok: true, response: await dispatchOutboundText(client, message2, logger6) };
170863
+ return { ok: true, response: await dispatchOutboundText2(client, message2, logger6) };
170811
170864
  }
170812
170865
  if (META_MEDIA_TYPES.has(content.type)) {
170813
170866
  return {
@@ -170833,7 +170886,7 @@ async function dispatchOutbound2(client, message2, logger6) {
170833
170886
  return { ok: true, response: await sendReaction3(client, to, content.targetMessageId ?? "", content.emoji ?? "") };
170834
170887
  }
170835
170888
  if (content.type === "location_request") {
170836
- return { ok: true, response: await sendLocationRequest(client, to, content.text ?? "", replyTo) };
170889
+ return { ok: true, response: await sendLocationRequest2(client, to, content.text ?? "", replyTo) };
170837
170890
  }
170838
170891
  if (content.type === "template") {
170839
170892
  return dispatchOutboundTemplate2(client, message2);
@@ -170854,7 +170907,7 @@ async function dispatchOutboundFlow(client, message2) {
170854
170907
  const { response } = await sendFlow(client, message2.to, parsed.data, message2.replyTo);
170855
170908
  return { ok: true, response };
170856
170909
  }
170857
- async function dispatchOutboundText(client, message2, logger6) {
170910
+ async function dispatchOutboundText2(client, message2, logger6) {
170858
170911
  const { content, to, replyTo } = message2;
170859
170912
  const formatMode = message2.metadata?.messageFormatMode ?? "convert";
170860
170913
  const text3 = content.text ?? "";
@@ -171385,6 +171438,7 @@ var init_plugin = __esm(() => {
171385
171438
  type: content.type,
171386
171439
  text: content.text ?? content.caption,
171387
171440
  mediaUrl: content.mediaUrl,
171441
+ mediaId: content.mediaId,
171388
171442
  mimeType: content.mimeType,
171389
171443
  isVoiceNote: content.isVoiceNote
171390
171444
  },
@@ -188043,7 +188097,7 @@ function shouldIgnoreSpansForIncomingRequest(request, {
188043
188097
  ignoreStaticAssets,
188044
188098
  ignoreIncomingRequests
188045
188099
  }) {
188046
- if (import_core60.isTracingSuppressed(import_api7.context.active())) {
188100
+ if (import_core61.isTracingSuppressed(import_api7.context.active())) {
188047
188101
  return true;
188048
188102
  }
188049
188103
  const urlPath = request.url;
@@ -188095,7 +188149,7 @@ function getIncomingRequestAttributesOnResponse(request, response) {
188095
188149
  [import_semantic_conventions.SEMATTRS_HTTP_STATUS_CODE]: statusCode,
188096
188150
  "http.status_text": statusMessage?.toUpperCase()
188097
188151
  };
188098
- const rpcMetadata = import_core60.getRPCMetadata(import_api7.context.active());
188152
+ const rpcMetadata = import_core61.getRPCMetadata(import_api7.context.active());
188099
188153
  if (socket) {
188100
188154
  const { localAddress, localPort, remoteAddress, remotePort } = socket;
188101
188155
  newAttributes[import_semantic_conventions.SEMATTRS_NET_HOST_IP] = localAddress;
@@ -188105,7 +188159,7 @@ function getIncomingRequestAttributesOnResponse(request, response) {
188105
188159
  }
188106
188160
  newAttributes[import_semantic_conventions.SEMATTRS_HTTP_STATUS_CODE] = statusCode;
188107
188161
  newAttributes["http.status_text"] = (statusMessage || "").toUpperCase();
188108
- if (rpcMetadata?.type === import_core60.RPCType.HTTP && rpcMetadata.route !== undefined) {
188162
+ if (rpcMetadata?.type === import_core61.RPCType.HTTP && rpcMetadata.route !== undefined) {
188109
188163
  const routeName = rpcMetadata.route;
188110
188164
  newAttributes[import_semantic_conventions.ATTR_HTTP_ROUTE] = routeName;
188111
188165
  }
@@ -188120,7 +188174,7 @@ function shouldFilterStatusCode(statusCode, dropForStatusCodes) {
188120
188174
  return statusCode >= min && statusCode <= max;
188121
188175
  });
188122
188176
  }
188123
- var import_api7, import_core60, import_semantic_conventions, INTEGRATION_NAME8 = "Http.ServerSpans", _httpServerSpansIntegration = (options = {}) => {
188177
+ var import_api7, import_core61, import_semantic_conventions, INTEGRATION_NAME8 = "Http.ServerSpans", _httpServerSpansIntegration = (options = {}) => {
188124
188178
  const ignoreStaticAssets = options.ignoreStaticAssets ?? true;
188125
188179
  const ignoreIncomingRequests = options.ignoreIncomingRequests;
188126
188180
  const ignoreStatusCodes = options.ignoreStatusCodes ?? [
@@ -188185,10 +188239,10 @@ var import_api7, import_core60, import_semantic_conventions, INTEGRATION_NAME8 =
188185
188239
  applyCustomAttributesOnSpan?.(span, request, response);
188186
188240
  onSpanCreated?.(span, request, response);
188187
188241
  const rpcMetadata = {
188188
- type: import_core60.RPCType.HTTP,
188242
+ type: import_core61.RPCType.HTTP,
188189
188243
  span
188190
188244
  };
188191
- return import_api7.context.with(import_core60.setRPCMetadata(import_api7.trace.setSpan(import_api7.context.active(), span), rpcMetadata), () => {
188245
+ return import_api7.context.with(import_core61.setRPCMetadata(import_api7.trace.setSpan(import_api7.context.active(), span), rpcMetadata), () => {
188192
188246
  import_api7.context.bind(import_api7.context.active(), request);
188193
188247
  import_api7.context.bind(import_api7.context.active(), response);
188194
188248
  let isEnded = false;
@@ -188250,7 +188304,7 @@ var init_httpServerSpansIntegration = __esm(() => {
188250
188304
  init_debug_build2();
188251
188305
  init_httpServerIntegration();
188252
188306
  import_api7 = __toESM(require_src(), 1);
188253
- import_core60 = __toESM(require_src6(), 1);
188307
+ import_core61 = __toESM(require_src6(), 1);
188254
188308
  import_semantic_conventions = __toESM(require_src5(), 1);
188255
188309
  httpServerSpansIntegration = _httpServerSpansIntegration;
188256
188310
  });
@@ -190084,13 +190138,13 @@ import { subscribe as subscribe2 } from "diagnostics_channel";
190084
190138
  import { errorMonitor as errorMonitor2 } from "events";
190085
190139
  import * as http from "http";
190086
190140
  import * as https from "https";
190087
- var import_api8, import_core63, import_instrumentation, FULLY_SUPPORTS_HTTP_DIAGNOSTICS_CHANNEL, SentryHttpInstrumentation;
190141
+ var import_api8, import_core64, import_instrumentation, FULLY_SUPPORTS_HTTP_DIAGNOSTICS_CHANNEL, SentryHttpInstrumentation;
190088
190142
  var init_SentryHttpInstrumentation = __esm(() => {
190089
190143
  init_esm();
190090
190144
  init_constants10();
190091
190145
  init_nodeVersion();
190092
190146
  import_api8 = __toESM(require_src(), 1);
190093
- import_core63 = __toESM(require_src6(), 1);
190147
+ import_core64 = __toESM(require_src6(), 1);
190094
190148
  import_instrumentation = __toESM(require_src8(), 1);
190095
190149
  FULLY_SUPPORTS_HTTP_DIAGNOSTICS_CHANNEL = NODE_VERSION.major === 22 && NODE_VERSION.minor >= 12 || NODE_VERSION.major === 23 && NODE_VERSION.minor >= 2 || NODE_VERSION.major >= 24;
190096
190150
  SentryHttpInstrumentation = class SentryHttpInstrumentation extends import_instrumentation.InstrumentationBase {
@@ -190105,7 +190159,7 @@ var init_SentryHttpInstrumentation = __esm(() => {
190105
190159
  ...options,
190106
190160
  spans: options.createSpansForOutgoingRequests && (options.spans ?? true),
190107
190161
  ignoreOutgoingRequests(url, request) {
190108
- return import_core63.isTracingSuppressed(import_api8.context.active()) || !!options.ignoreOutgoingRequests?.(url, getRequestOptions(request));
190162
+ return import_core64.isTracingSuppressed(import_api8.context.active()) || !!options.ignoreOutgoingRequests?.(url, getRequestOptions(request));
190109
190163
  },
190110
190164
  outgoingRequestHook(span, request) {
190111
190165
  options.outgoingRequestHook?.(span, request);
@@ -190325,13 +190379,13 @@ var init_outgoingFetchRequest = __esm(() => {
190325
190379
 
190326
190380
  // ../../node_modules/.bun/@sentry+node-core@10.52.0+6488de8736d8e524/node_modules/@sentry/node-core/build/esm/integrations/node-fetch/SentryNodeFetchInstrumentation.js
190327
190381
  import * as diagch from "diagnostics_channel";
190328
- var import_api9, import_core66, import_instrumentation2, SentryNodeFetchInstrumentation;
190382
+ var import_api9, import_core67, import_instrumentation2, SentryNodeFetchInstrumentation;
190329
190383
  var init_SentryNodeFetchInstrumentation = __esm(() => {
190330
190384
  init_esm();
190331
190385
  init_nodeVersion();
190332
190386
  init_outgoingFetchRequest();
190333
190387
  import_api9 = __toESM(require_src(), 1);
190334
- import_core66 = __toESM(require_src6(), 1);
190388
+ import_core67 = __toESM(require_src6(), 1);
190335
190389
  import_instrumentation2 = __toESM(require_src8(), 1);
190336
190390
  SentryNodeFetchInstrumentation = class SentryNodeFetchInstrumentation extends import_instrumentation2.InstrumentationBase {
190337
190391
  constructor(config2 = {}) {
@@ -190402,7 +190456,7 @@ var init_SentryNodeFetchInstrumentation = __esm(() => {
190402
190456
  });
190403
190457
  }
190404
190458
  _shouldIgnoreOutgoingRequest(request) {
190405
- if (import_core66.isTracingSuppressed(import_api9.context.active())) {
190459
+ if (import_core67.isTracingSuppressed(import_api9.context.active())) {
190406
190460
  return true;
190407
190461
  }
190408
190462
  const url = getAbsoluteUrl2(request.origin, request.path);
@@ -192762,7 +192816,7 @@ function makeTraceState({
192762
192816
  sampled
192763
192817
  }) {
192764
192818
  const dscString = dsc ? dynamicSamplingContextToSentryBaggageHeader(dsc) : undefined;
192765
- const traceStateBase = new import_core69.TraceState;
192819
+ const traceStateBase = new import_core70.TraceState;
192766
192820
  const traceStateWithDsc = dscString ? traceStateBase.set(SENTRY_TRACE_STATE_DSC, dscString) : traceStateBase;
192767
192821
  return sampled === false ? traceStateWithDsc.set(SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING, "1") : traceStateWithDsc;
192768
192822
  }
@@ -192886,13 +192940,13 @@ function _startSpan(options, callback, autoEnd) {
192886
192940
  return wrapper(() => {
192887
192941
  const activeCtx = getContext(options.scope, options.forceTransaction);
192888
192942
  const missingRequiredParent = options.onlyIfParent && !import_api10.trace.getSpan(activeCtx);
192889
- const ctx = missingRequiredParent ? import_core69.suppressTracing(activeCtx) : activeCtx;
192943
+ const ctx = missingRequiredParent ? import_core70.suppressTracing(activeCtx) : activeCtx;
192890
192944
  if (missingRequiredParent) {
192891
192945
  getClient()?.recordDroppedEvent("no_parent_span", "span");
192892
192946
  }
192893
192947
  const spanOptions = getSpanOptions(options);
192894
192948
  if (!hasSpansEnabled()) {
192895
- const suppressedCtx = import_core69.isTracingSuppressed(ctx) ? ctx : import_core69.suppressTracing(ctx);
192949
+ const suppressedCtx = import_core70.isTracingSuppressed(ctx) ? ctx : import_core70.suppressTracing(ctx);
192896
192950
  return import_api10.context.with(suppressedCtx, () => {
192897
192951
  return tracer2.startActiveSpan(name, spanOptions, suppressedCtx, (span) => {
192898
192952
  patchSpanEnd(span);
@@ -192929,13 +192983,13 @@ function startInactiveSpan2(options) {
192929
192983
  return wrapper(() => {
192930
192984
  const activeCtx = getContext(options.scope, options.forceTransaction);
192931
192985
  const missingRequiredParent = options.onlyIfParent && !import_api10.trace.getSpan(activeCtx);
192932
- let ctx = missingRequiredParent ? import_core69.suppressTracing(activeCtx) : activeCtx;
192986
+ let ctx = missingRequiredParent ? import_core70.suppressTracing(activeCtx) : activeCtx;
192933
192987
  if (missingRequiredParent) {
192934
192988
  getClient()?.recordDroppedEvent("no_parent_span", "span");
192935
192989
  }
192936
192990
  const spanOptions = getSpanOptions(options);
192937
192991
  if (!hasSpansEnabled()) {
192938
- ctx = import_core69.isTracingSuppressed(ctx) ? ctx : import_core69.suppressTracing(ctx);
192992
+ ctx = import_core70.isTracingSuppressed(ctx) ? ctx : import_core70.suppressTracing(ctx);
192939
192993
  }
192940
192994
  const span = tracer2.startSpan(name, spanOptions, ctx);
192941
192995
  patchSpanEnd(span);
@@ -193044,7 +193098,7 @@ function getActiveSpanWrapper2(parentSpan) {
193044
193098
  } : (callback) => callback();
193045
193099
  }
193046
193100
  function suppressTracing2(callback) {
193047
- const ctx = import_core69.suppressTracing(import_api10.context.active());
193101
+ const ctx = import_core70.suppressTracing(import_api10.context.active());
193048
193102
  return import_api10.context.with(ctx, callback);
193049
193103
  }
193050
193104
  function setupEventContextTrace(client) {
@@ -193731,7 +193785,7 @@ function wrapSamplingDecision({
193731
193785
  function getBaseTraceState(context8, spanAttributes) {
193732
193786
  const parentSpan = import_api10.trace.getSpan(context8);
193733
193787
  const parentContext = parentSpan?.spanContext();
193734
- let traceState = parentContext?.traceState || new import_core69.TraceState;
193788
+ let traceState = parentContext?.traceState || new import_core70.TraceState;
193735
193789
  const url = spanAttributes[import_semantic_conventions2.SEMATTRS_HTTP_URL] || spanAttributes[import_semantic_conventions2.ATTR_URL_FULL];
193736
193790
  if (url && typeof url === "string") {
193737
193791
  traceState = traceState.set(SENTRY_TRACE_STATE_URL, url);
@@ -193792,12 +193846,12 @@ function getSentryResource(serviceNameFallback) {
193792
193846
  ...otelResourceAttrs,
193793
193847
  ...otelServiceName ? { [import_semantic_conventions2.ATTR_SERVICE_NAME]: otelServiceName } : {},
193794
193848
  [import_semantic_conventions2.ATTR_SERVICE_VERSION]: SDK_VERSION,
193795
- [import_semantic_conventions2.ATTR_TELEMETRY_SDK_LANGUAGE]: import_core69.SDK_INFO[import_semantic_conventions2.ATTR_TELEMETRY_SDK_LANGUAGE],
193796
- [import_semantic_conventions2.ATTR_TELEMETRY_SDK_NAME]: import_core69.SDK_INFO[import_semantic_conventions2.ATTR_TELEMETRY_SDK_NAME],
193797
- [import_semantic_conventions2.ATTR_TELEMETRY_SDK_VERSION]: import_core69.SDK_INFO[import_semantic_conventions2.ATTR_TELEMETRY_SDK_VERSION]
193849
+ [import_semantic_conventions2.ATTR_TELEMETRY_SDK_LANGUAGE]: import_core70.SDK_INFO[import_semantic_conventions2.ATTR_TELEMETRY_SDK_LANGUAGE],
193850
+ [import_semantic_conventions2.ATTR_TELEMETRY_SDK_NAME]: import_core70.SDK_INFO[import_semantic_conventions2.ATTR_TELEMETRY_SDK_NAME],
193851
+ [import_semantic_conventions2.ATTR_TELEMETRY_SDK_VERSION]: import_core70.SDK_INFO[import_semantic_conventions2.ATTR_TELEMETRY_SDK_VERSION]
193798
193852
  });
193799
193853
  }
193800
- var api, import_api10, import_semantic_conventions2, import_core69, import_sdk_trace_base, SEMANTIC_ATTRIBUTE_SENTRY_PARENT_IS_REMOTE = "sentry.parentIsRemote", SEMANTIC_ATTRIBUTE_SENTRY_GRAPHQL_OPERATION = "sentry.graphql.operation", SENTRY_TRACE_HEADER2 = "sentry-trace", SENTRY_BAGGAGE_HEADER2 = "baggage", SENTRY_TRACE_STATE_DSC = "sentry.dsc", SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING = "sentry.sampled_not_recording", SENTRY_TRACE_STATE_URL = "sentry.url", SENTRY_TRACE_STATE_SAMPLE_RAND = "sentry.sample_rand", SENTRY_TRACE_STATE_SAMPLE_RATE = "sentry.sample_rate", SENTRY_TRACE_STATE_CHILD_IGNORED = "sentry.ignored", SENTRY_TRACE_STATE_SEGMENT_IGNORED = "sentry.segment_ignored", SENTRY_SCOPES_CONTEXT_KEY, SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY, SENTRY_FORK_SET_SCOPE_CONTEXT_KEY, SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY, SCOPE_CONTEXT_FIELD = "_scopeContext", setupElements, SentryPropagator, canonicalGrpcErrorCodesMap, isStatusErrorMessageValid = (message2) => {
193854
+ var api, import_api10, import_semantic_conventions2, import_core70, import_sdk_trace_base, SEMANTIC_ATTRIBUTE_SENTRY_PARENT_IS_REMOTE = "sentry.parentIsRemote", SEMANTIC_ATTRIBUTE_SENTRY_GRAPHQL_OPERATION = "sentry.graphql.operation", SENTRY_TRACE_HEADER2 = "sentry-trace", SENTRY_BAGGAGE_HEADER2 = "baggage", SENTRY_TRACE_STATE_DSC = "sentry.dsc", SENTRY_TRACE_STATE_SAMPLED_NOT_RECORDING = "sentry.sampled_not_recording", SENTRY_TRACE_STATE_URL = "sentry.url", SENTRY_TRACE_STATE_SAMPLE_RAND = "sentry.sample_rand", SENTRY_TRACE_STATE_SAMPLE_RATE = "sentry.sample_rate", SENTRY_TRACE_STATE_CHILD_IGNORED = "sentry.ignored", SENTRY_TRACE_STATE_SEGMENT_IGNORED = "sentry.segment_ignored", SENTRY_SCOPES_CONTEXT_KEY, SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY, SENTRY_FORK_SET_SCOPE_CONTEXT_KEY, SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY, SCOPE_CONTEXT_FIELD = "_scopeContext", setupElements, SentryPropagator, canonicalGrpcErrorCodesMap, isStatusErrorMessageValid = (message2) => {
193801
193855
  return Object.values(canonicalGrpcErrorCodesMap).includes(message2);
193802
193856
  }, MAX_SPAN_COUNT2 = 1000, DEFAULT_TIMEOUT = 300;
193803
193857
  var init_resource_Bhpm2sLf = __esm(() => {
@@ -193806,21 +193860,21 @@ var init_resource_Bhpm2sLf = __esm(() => {
193806
193860
  api = __toESM(require_src(), 1);
193807
193861
  import_api10 = __toESM(require_src(), 1);
193808
193862
  import_semantic_conventions2 = __toESM(require_src5(), 1);
193809
- import_core69 = __toESM(require_src6(), 1);
193863
+ import_core70 = __toESM(require_src6(), 1);
193810
193864
  import_sdk_trace_base = __toESM(require_src10(), 1);
193811
193865
  SENTRY_SCOPES_CONTEXT_KEY = import_api10.createContextKey("sentry_scopes");
193812
193866
  SENTRY_FORK_ISOLATION_SCOPE_CONTEXT_KEY = import_api10.createContextKey("sentry_fork_isolation_scope");
193813
193867
  SENTRY_FORK_SET_SCOPE_CONTEXT_KEY = import_api10.createContextKey("sentry_fork_set_scope");
193814
193868
  SENTRY_FORK_SET_ISOLATION_SCOPE_CONTEXT_KEY = import_api10.createContextKey("sentry_fork_set_isolation_scope");
193815
193869
  setupElements = new Set;
193816
- SentryPropagator = class SentryPropagator extends import_core69.W3CBaggagePropagator {
193870
+ SentryPropagator = class SentryPropagator extends import_core70.W3CBaggagePropagator {
193817
193871
  constructor() {
193818
193872
  super();
193819
193873
  setIsSetup("SentryPropagator");
193820
193874
  this._urlMatchesTargetsMap = new LRUMap(100);
193821
193875
  }
193822
193876
  inject(context8, carrier, setter) {
193823
- if (import_core69.isTracingSuppressed(context8)) {
193877
+ if (import_core70.isTracingSuppressed(context8)) {
193824
193878
  DEBUG_BUILD3 && debug32.log("[Tracing] Not injecting trace data for url because tracing is suppressed.");
193825
193879
  return;
193826
193880
  }
@@ -196669,12 +196723,12 @@ var init_http3 = __esm(() => {
196669
196723
  // ../../node_modules/.bun/@sentry+node@10.52.0+1b9a589cee1ff949/node_modules/@sentry/node/build/esm/integrations/node-fetch/vendored/undici.js
196670
196724
  import * as diagch2 from "diagnostics_channel";
196671
196725
  import { URL as URL4 } from "url";
196672
- var import_instrumentation5, import_api15, import_core98, import_semantic_conventions3, PACKAGE_NAME = "@sentry/instrumentation-undici", UndiciInstrumentation;
196726
+ var import_instrumentation5, import_api15, import_core99, import_semantic_conventions3, PACKAGE_NAME = "@sentry/instrumentation-undici", UndiciInstrumentation;
196673
196727
  var init_undici = __esm(() => {
196674
196728
  init_esm();
196675
196729
  import_instrumentation5 = __toESM(require_src8(), 1);
196676
196730
  import_api15 = __toESM(require_src(), 1);
196677
- import_core98 = __toESM(require_src6(), 1);
196731
+ import_core99 = __toESM(require_src6(), 1);
196678
196732
  import_semantic_conventions3 = __toESM(require_src5(), 1);
196679
196733
  UndiciInstrumentation = class UndiciInstrumentation extends import_instrumentation5.InstrumentationBase {
196680
196734
  __init() {
@@ -196773,7 +196827,7 @@ var init_undici = __esm(() => {
196773
196827
  if (shouldIgnoreReq) {
196774
196828
  return;
196775
196829
  }
196776
- const startTime = import_core98.hrTime();
196830
+ const startTime = import_core99.hrTime();
196777
196831
  let requestUrl;
196778
196832
  try {
196779
196833
  requestUrl = new URL4(request2.path, request2.origin);
@@ -196946,7 +197000,7 @@ var init_undici = __esm(() => {
196946
197000
  metricsAttributes[key] = attributes[key];
196947
197001
  }
196948
197002
  });
196949
- const durationSeconds = import_core98.hrTimeToMilliseconds(import_core98.hrTimeDuration(startTime, import_core98.hrTime())) / 1000;
197003
+ const durationSeconds = import_core99.hrTimeToMilliseconds(import_core99.hrTimeDuration(startTime, import_core99.hrTime())) / 1000;
196950
197004
  this._httpClientDurationHistogram.record(durationSeconds, metricsAttributes);
196951
197005
  }
196952
197006
  getRequestMethod(original) {
@@ -197491,7 +197545,7 @@ var init_debug_build3 = __esm(() => {
197491
197545
  });
197492
197546
 
197493
197547
  // ../../node_modules/.bun/@sentry+node@10.52.0+1b9a589cee1ff949/node_modules/@sentry/node/build/esm/integrations/tracing/express.js
197494
- var import_instrumentation6, import_api16, import_core101, INTEGRATION_NAME23 = "Express", SUPPORTED_VERSIONS, instrumentExpress, ExpressInstrumentation, _expressIntegration = (options) => {
197548
+ var import_instrumentation6, import_api16, import_core102, INTEGRATION_NAME23 = "Express", SUPPORTED_VERSIONS, instrumentExpress, ExpressInstrumentation, _expressIntegration = (options) => {
197495
197549
  return {
197496
197550
  name: INTEGRATION_NAME23,
197497
197551
  setupOnce() {
@@ -197505,7 +197559,7 @@ var init_express2 = __esm(() => {
197505
197559
  init_debug_build3();
197506
197560
  import_instrumentation6 = __toESM(require_src8(), 1);
197507
197561
  import_api16 = __toESM(require_src(), 1);
197508
- import_core101 = __toESM(require_src6(), 1);
197562
+ import_core102 = __toESM(require_src6(), 1);
197509
197563
  SUPPORTED_VERSIONS = [">=4.0.0 <6"];
197510
197564
  instrumentExpress = generateInstrumentOnce(INTEGRATION_NAME23, (options) => new ExpressInstrumentation(options));
197511
197565
  ExpressInstrumentation = class ExpressInstrumentation extends import_instrumentation6.InstrumentationBase {
@@ -197518,8 +197572,8 @@ var init_express2 = __esm(() => {
197518
197572
  patchExpressModule(express, () => ({
197519
197573
  ...this.getConfig(),
197520
197574
  onRouteResolved(route) {
197521
- const rpcMetadata = import_core101.getRPCMetadata(import_api16.context.active());
197522
- if (route && rpcMetadata?.type === import_core101.RPCType.HTTP) {
197575
+ const rpcMetadata = import_core102.getRPCMetadata(import_api16.context.active());
197576
+ if (route && rpcMetadata?.type === import_core102.RPCType.HTTP) {
197523
197577
  rpcMetadata.route = route;
197524
197578
  }
197525
197579
  }
@@ -201415,13 +201469,13 @@ function addFastifyV3SpanAttributes(span) {
201415
201469
  span.updateName(updatedName);
201416
201470
  }
201417
201471
  }
201418
- var import_api18, import_core103, import_instrumentation7, import_semantic_conventions4, PACKAGE_VERSION = "0.1.0", PACKAGE_NAME2 = "@sentry/instrumentation-fastify-v3", ANONYMOUS_NAME = "anonymous", hooksNamesToWrap, FastifyInstrumentationV3;
201472
+ var import_api18, import_core104, import_instrumentation7, import_semantic_conventions4, PACKAGE_VERSION = "0.1.0", PACKAGE_NAME2 = "@sentry/instrumentation-fastify-v3", ANONYMOUS_NAME = "anonymous", hooksNamesToWrap, FastifyInstrumentationV3;
201419
201473
  var init_instrumentation = __esm(() => {
201420
201474
  init_esm();
201421
201475
  init_AttributeNames();
201422
201476
  init_utils14();
201423
201477
  import_api18 = __toESM(require_src(), 1);
201424
- import_core103 = __toESM(require_src6(), 1);
201478
+ import_core104 = __toESM(require_src6(), 1);
201425
201479
  import_instrumentation7 = __toESM(require_src8(), 1);
201426
201480
  import_semantic_conventions4 = __toESM(require_src5(), 1);
201427
201481
  hooksNamesToWrap = new Set([
@@ -201454,9 +201508,9 @@ var init_instrumentation = __esm(() => {
201454
201508
  }
201455
201509
  instrumentation._wrap(reply, "send", instrumentation._patchSend());
201456
201510
  const anyRequest = request2;
201457
- const rpcMetadata = import_core103.getRPCMetadata(import_api18.context.active());
201511
+ const rpcMetadata = import_core104.getRPCMetadata(import_api18.context.active());
201458
201512
  const routeName = anyRequest.routeOptions ? anyRequest.routeOptions.url : request2.routerPath;
201459
- if (routeName && rpcMetadata?.type === import_core103.RPCType.HTTP) {
201513
+ if (routeName && rpcMetadata?.type === import_core104.RPCType.HTTP) {
201460
201514
  rpcMetadata.route = routeName;
201461
201515
  }
201462
201516
  const method = request2.method || "GET";
@@ -242831,7 +242885,7 @@ var init_sentry_scrub = __esm(() => {
242831
242885
  var require_package7 = __commonJS((exports, module) => {
242832
242886
  module.exports = {
242833
242887
  name: "@omni/api",
242834
- version: "2.260804.2",
242888
+ version: "2.260830.1",
242835
242889
  type: "module",
242836
242890
  exports: {
242837
242891
  ".": {
@@ -278800,7 +278854,7 @@ var require_channelz = __commonJS((exports) => {
278800
278854
  return (0, net_1.isIPv6)(ipAddress) && ipAddress.toLowerCase().startsWith("::ffff:") && (0, net_1.isIPv4)(ipAddress.substring(7));
278801
278855
  }
278802
278856
  function ipv4AddressStringToBuffer(ipAddress) {
278803
- return Buffer.from(Uint8Array.from(ipAddress.split(".").map((segment) => Number.parseInt(segment))));
278857
+ return Buffer.from(Uint8Array.from(ipAddress.split(".").map((segment2) => Number.parseInt(segment2))));
278804
278858
  }
278805
278859
  function ipAddressStringToBuffer(ipAddress) {
278806
278860
  if ((0, net_1.isIPv4)(ipAddress)) {
@@ -289937,21 +289991,21 @@ var splitPath2 = (path2) => {
289937
289991
  const segments = path2.split("/");
289938
289992
  const results = [];
289939
289993
  let basePath = "";
289940
- segments.forEach((segment) => {
289941
- if (segment !== "" && !/\:/.test(segment)) {
289942
- basePath += "/" + segment;
289943
- } else if (/\:/.test(segment)) {
289944
- if (/\?/.test(segment)) {
289994
+ segments.forEach((segment2) => {
289995
+ if (segment2 !== "" && !/\:/.test(segment2)) {
289996
+ basePath += "/" + segment2;
289997
+ } else if (/\:/.test(segment2)) {
289998
+ if (/\?/.test(segment2)) {
289945
289999
  if (results.length === 0 && basePath === "") {
289946
290000
  results.push("/");
289947
290001
  } else {
289948
290002
  results.push(basePath);
289949
290003
  }
289950
- const optionalSegment = segment.replace("?", "");
290004
+ const optionalSegment = segment2.replace("?", "");
289951
290005
  basePath += "/" + optionalSegment;
289952
290006
  results.push(basePath);
289953
290007
  } else {
289954
- basePath += "/" + segment;
290008
+ basePath += "/" + segment2;
289955
290009
  }
289956
290010
  }
289957
290011
  });
@@ -291483,11 +291537,11 @@ var defaultJoin = (...paths) => {
291483
291537
  result = result.replace(/(?<=\/)\/+/g, "");
291484
291538
  const segments = result.split("/");
291485
291539
  const resolved = [];
291486
- for (const segment of segments) {
291487
- if (segment === ".." && resolved.length > 0 && resolved.at(-1) !== "..") {
291540
+ for (const segment2 of segments) {
291541
+ if (segment2 === ".." && resolved.length > 0 && resolved.at(-1) !== "..") {
291488
291542
  resolved.pop();
291489
- } else if (segment !== ".") {
291490
- resolved.push(segment);
291543
+ } else if (segment2 !== ".") {
291544
+ resolved.push(segment2);
291491
291545
  }
291492
291546
  }
291493
291547
  return resolved.join("/") || ".";
@@ -293276,9 +293330,13 @@ var init_profiles = __esm(() => {
293276
293330
  });
293277
293331
 
293278
293332
  // ../api/src/middleware/output-redactor.ts
293333
+ import { createHash as createHash12 } from "crypto";
293279
293334
  function escapeRegex(literal) {
293280
293335
  return literal.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
293281
293336
  }
293337
+ function hashPattern(source) {
293338
+ return createHash12("sha256").update(source).digest("hex").slice(0, 12);
293339
+ }
293282
293340
  function compilePatterns(literals) {
293283
293341
  const out = [];
293284
293342
  for (const literal of literals) {
@@ -293448,7 +293506,7 @@ async function emitRedactionEvents(c, apiKey, presetKey, hits) {
293448
293506
  keyId: apiKey.id,
293449
293507
  profile: apiKey.profile ?? null,
293450
293508
  presetKey,
293451
- pattern: hit.pattern,
293509
+ patternHash: hashPattern(hit.pattern),
293452
293510
  field: hit.field,
293453
293511
  count: hit.count,
293454
293512
  route: c.req.path,
@@ -293457,7 +293515,7 @@ async function emitRedactionEvents(c, apiKey, presetKey, hits) {
293457
293515
  } catch (err2) {
293458
293516
  log60.warn("output-redactor: failed to publish secret.redacted", {
293459
293517
  keyId: apiKey.id,
293460
- pattern: hit.pattern,
293518
+ patternHash: hashPattern(hit.pattern),
293461
293519
  error: String(err2)
293462
293520
  });
293463
293521
  }
@@ -293648,6 +293706,9 @@ var init_scopes = __esm(() => {
293648
293706
  "GET /instances/:id/status": "instances:read",
293649
293707
  "GET /instances/:id/qr": "instances:read",
293650
293708
  "POST /instances/:id/pair": "instances:write",
293709
+ "GET /instances/:id/passkey": "instances:read",
293710
+ "POST /instances/:id/passkey/response": "instances:write",
293711
+ "POST /instances/:id/passkey/confirm": "instances:write",
293651
293712
  "POST /instances/:id/connect": "instances:write",
293652
293713
  "POST /instances/:id/disconnect": "instances:write",
293653
293714
  "POST /instances/:id/restart": "instances:write",
@@ -294372,17 +294433,18 @@ var init_route_ownership = __esm(() => {
294372
294433
  "DELETE /api/v2/follow-up/chats/:id",
294373
294434
  "DELETE /api/v2/follow-up/instances/:id",
294374
294435
  "DELETE /api/v2/instances/:id",
294375
- "DELETE /api/v2/instances/:id/whatsapp-flows/:flowId",
294376
294436
  "DELETE /api/v2/instances/:id/block",
294377
294437
  "DELETE /api/v2/instances/:id/guilds/:guildId/config",
294378
294438
  "DELETE /api/v2/instances/:id/profile/picture",
294379
294439
  "DELETE /api/v2/instances/:id/whatsapp-business/connection",
294440
+ "DELETE /api/v2/instances/:id/whatsapp-flows/:flowId",
294380
294441
  "DELETE /api/v2/instances/:id/whatsapp-templates/:templateId",
294381
294442
  "DELETE /api/v2/instances/:instanceId/routes/:id",
294382
294443
  "DELETE /api/v2/messages/:id",
294383
294444
  "DELETE /api/v2/messages/:id/reactions",
294384
294445
  "DELETE /api/v2/messages/:id/star",
294385
294446
  "DELETE /api/v2/providers/:id",
294447
+ "DELETE /api/v2/scheduled-messages/:id",
294386
294448
  "DELETE /api/v2/settings/:key",
294387
294449
  "DELETE /api/v2/webhook-sources/:id",
294388
294450
  "GET /api/v2",
@@ -294451,6 +294513,7 @@ var init_route_ownership = __esm(() => {
294451
294513
  "GET /api/v2/instances/:id/guilds/:guildId/audit",
294452
294514
  "GET /api/v2/instances/:id/guilds/:guildId/config",
294453
294515
  "GET /api/v2/instances/:id/pairing-requests",
294516
+ "GET /api/v2/instances/:id/passkey",
294454
294517
  "GET /api/v2/instances/:id/privacy",
294455
294518
  "GET /api/v2/instances/:id/qr",
294456
294519
  "GET /api/v2/instances/:id/status",
@@ -294475,6 +294538,7 @@ var init_route_ownership = __esm(() => {
294475
294538
  "GET /api/v2/media/:instanceId/*",
294476
294539
  "GET /api/v2/messages",
294477
294540
  "GET /api/v2/messages/:id",
294541
+ "GET /api/v2/messages/:id/permalink",
294478
294542
  "GET /api/v2/messages/by-external",
294479
294543
  "GET /api/v2/messages/tts/voices",
294480
294544
  "GET /api/v2/payload-config",
@@ -294490,9 +294554,12 @@ var init_route_ownership = __esm(() => {
294490
294554
  "GET /api/v2/providers/:id/teams",
294491
294555
  "GET /api/v2/providers/:id/workflows",
294492
294556
  "GET /api/v2/routes/metrics",
294557
+ "GET /api/v2/scheduled-messages",
294558
+ "GET /api/v2/scheduled-messages/:id",
294493
294559
  "GET /api/v2/settings",
294494
294560
  "GET /api/v2/settings/:key",
294495
294561
  "GET /api/v2/settings/:key/history",
294562
+ "GET /api/v2/slack/search",
294496
294563
  "GET /api/v2/turns",
294497
294564
  "GET /api/v2/turns/:id",
294498
294565
  "GET /api/v2/turns/stats",
@@ -294584,6 +294651,8 @@ var init_route_ownership = __esm(() => {
294584
294651
  "POST /api/v2/instances/:id/groups/join",
294585
294652
  "POST /api/v2/instances/:id/logout",
294586
294653
  "POST /api/v2/instances/:id/pair",
294654
+ "POST /api/v2/instances/:id/passkey/confirm",
294655
+ "POST /api/v2/instances/:id/passkey/response",
294587
294656
  "POST /api/v2/instances/:id/pairing-requests/:requestId/action",
294588
294657
  "POST /api/v2/instances/:id/replay",
294589
294658
  "POST /api/v2/instances/:id/restart",
@@ -294638,6 +294707,8 @@ var init_route_ownership = __esm(() => {
294638
294707
  "POST /api/v2/persons/unlink",
294639
294708
  "POST /api/v2/providers",
294640
294709
  "POST /api/v2/providers/:id/health",
294710
+ "POST /api/v2/scheduled-messages",
294711
+ "POST /api/v2/slack/dm/open",
294641
294712
  "POST /api/v2/turns/:id/close",
294642
294713
  "POST /api/v2/turns/close",
294643
294714
  "POST /api/v2/turns/close-all",
@@ -294656,9 +294727,9 @@ var init_route_ownership = __esm(() => {
294656
294727
  "PUT /api/v2/instances/:id/presence",
294657
294728
  "PUT /api/v2/instances/:id/profile/name",
294658
294729
  "PUT /api/v2/instances/:id/profile/picture",
294730
+ "PUT /api/v2/instances/:id/profile/status",
294659
294731
  "PUT /api/v2/instances/:id/whatsapp-business/profile",
294660
294732
  "PUT /api/v2/instances/:id/whatsapp-flows/:flowId",
294661
- "PUT /api/v2/instances/:id/profile/status",
294662
294733
  "PUT /api/v2/payload-config/:eventType",
294663
294734
  "PUT /api/v2/settings/:key"
294664
294735
  ];
@@ -316516,10 +316587,10 @@ var import_p_retry, import_google_auth_library, _defaultBaseGeminiUrl = undefine
316516
316587
  invalidSegments.sort((a, b3) => a.start - b3.start);
316517
316588
  if (invalidSegments.length > 0) {
316518
316589
  let lastEnd = 0;
316519
- const underline = invalidSegments.reduce((acc, segment) => {
316520
- const spaces = " ".repeat(segment.start - lastEnd);
316521
- const arrows = "^".repeat(segment.length);
316522
- lastEnd = segment.start + segment.length;
316590
+ const underline = invalidSegments.reduce((acc, segment2) => {
316591
+ const spaces = " ".repeat(segment2.start - lastEnd);
316592
+ const arrows = "^".repeat(segment2.length);
316593
+ lastEnd = segment2.start + segment2.length;
316523
316594
  return acc + spaces + arrows;
316524
316595
  }, "");
316525
316596
  throw new GeminiNextGenAPIClientError(`Path parameters result in path with invalid segments:
@@ -322495,10 +322566,10 @@ function extractSegments(buffer3) {
322495
322566
  const splitIndex = remaining.indexOf(`
322496
322567
 
322497
322568
  `);
322498
- const segment = remaining.slice(0, splitIndex).trim();
322569
+ const segment2 = remaining.slice(0, splitIndex).trim();
322499
322570
  remaining = remaining.slice(splitIndex + 2);
322500
- if (segment)
322501
- segments.push(segment);
322571
+ if (segment2)
322572
+ segments.push(segment2);
322502
322573
  }
322503
322574
  return [segments, remaining];
322504
322575
  }
@@ -322513,8 +322584,8 @@ async function* processStreamChunks(streamGenerator, enableSplit) {
322513
322584
  `)) {
322514
322585
  const [segments, remaining] = extractSegments(buffer3);
322515
322586
  buffer3 = remaining;
322516
- for (const segment of segments) {
322517
- yield segment;
322587
+ for (const segment2 of segments) {
322588
+ yield segment2;
322518
322589
  }
322519
322590
  }
322520
322591
  if (chunk2.isComplete && buffer3.trim()) {
@@ -327226,12 +327297,12 @@ var is_array, hex_table, limit = 1024, encode3 = (str, _defaultEncoder, charset,
327226
327297
  }
327227
327298
  let out = "";
327228
327299
  for (let j2 = 0;j2 < string.length; j2 += limit) {
327229
- const segment = string.length >= limit ? string.slice(j2, j2 + limit) : string;
327300
+ const segment2 = string.length >= limit ? string.slice(j2, j2 + limit) : string;
327230
327301
  const arr = [];
327231
- for (let i = 0;i < segment.length; ++i) {
327232
- let c = segment.charCodeAt(i);
327302
+ for (let i = 0;i < segment2.length; ++i) {
327303
+ let c = segment2.charCodeAt(i);
327233
327304
  if (c === 45 || c === 46 || c === 95 || c === 126 || c >= 48 && c <= 57 || c >= 65 && c <= 90 || c >= 97 && c <= 122 || format === RFC1738 && (c === 40 || c === 41)) {
327234
- arr[arr.length] = segment.charAt(i);
327305
+ arr[arr.length] = segment2.charAt(i);
327235
327306
  continue;
327236
327307
  }
327237
327308
  if (c < 128) {
@@ -327247,7 +327318,7 @@ var is_array, hex_table, limit = 1024, encode3 = (str, _defaultEncoder, charset,
327247
327318
  continue;
327248
327319
  }
327249
327320
  i += 1;
327250
- c = 65536 + ((c & 1023) << 10 | segment.charCodeAt(i) & 1023);
327321
+ c = 65536 + ((c & 1023) << 10 | segment2.charCodeAt(i) & 1023);
327251
327322
  arr[arr.length] = hex_table[240 | c >> 18] + hex_table[128 | c >> 12 & 63] + hex_table[128 | c >> 6 & 63] + hex_table[128 | c & 63];
327252
327323
  }
327253
327324
  out += arr.join("");
@@ -346308,6 +346379,7 @@ var init_instances = __esm(() => {
346308
346379
  SEALED_CREDENTIAL_COLUMNS = [
346309
346380
  "discordBotToken",
346310
346381
  "slackBotToken",
346382
+ "slackUserToken",
346311
346383
  "slackAppToken",
346312
346384
  "slackSigningSecret",
346313
346385
  "telegramBotToken",
@@ -346463,7 +346535,7 @@ class MessageService {
346463
346535
  return result;
346464
346536
  }
346465
346537
  async hasBotRepliedInThread(chatId, threadExternalId) {
346466
- const [result] = await this.db.select({ id: messages2.id }).from(messages2).where(and2(eq(messages2.chatId, chatId), eq(messages2.isFromMe, true), or2(eq(messages2.replyToExternalId, threadExternalId), eq(messages2.externalId, threadExternalId)))).limit(1);
346538
+ const [result] = await this.db.select({ id: messages2.id }).from(messages2).where(and2(eq(messages2.chatId, chatId), eq(messages2.isFromMe, true), or2(eq(messages2.threadExternalId, threadExternalId), eq(messages2.replyToExternalId, threadExternalId), eq(messages2.externalId, threadExternalId)))).limit(1);
346467
346539
  return !!result;
346468
346540
  }
346469
346541
  async getByExternalId(chatId, externalId) {
@@ -346552,6 +346624,8 @@ class MessageService {
346552
346624
  quotedSenderName: options.quotedSenderName,
346553
346625
  isForwarded: options.isForwarded ?? false,
346554
346626
  forwardedFromExternalId: options.forwardedFromExternalId,
346627
+ threadExternalId: options.threadExternalId,
346628
+ isThreadBroadcast: options.isThreadBroadcast ?? false,
346555
346629
  rawPayload: options.rawPayload,
346556
346630
  originalEventId: options.originalEventId,
346557
346631
  latestEventId: options.originalEventId
@@ -346755,6 +346829,19 @@ class MessageService {
346755
346829
  const [replyToMessage] = await this.db.select({ id: messages2.id }).from(messages2).where(and2(eq(messages2.chatId, chatId), eq(messages2.externalId, replyToExternalId))).limit(1);
346756
346830
  return replyToMessage?.id ?? null;
346757
346831
  }
346832
+ async setStarred(chatId, externalId, starred) {
346833
+ await this.db.update(messages2).set({ starredAt: starred ? new Date : null, updatedAt: new Date }).where(and2(eq(messages2.chatId, chatId), eq(messages2.externalId, externalId)));
346834
+ }
346835
+ async setPinned(chatId, externalId, pinned, pinnedBy) {
346836
+ await this.db.update(messages2).set({
346837
+ pinnedAt: pinned ? new Date : null,
346838
+ pinnedBy: pinned ? pinnedBy ?? null : null,
346839
+ updatedAt: new Date
346840
+ }).where(and2(eq(messages2.chatId, chatId), eq(messages2.externalId, externalId)));
346841
+ }
346842
+ async setPermalink(chatId, externalId, permalink) {
346843
+ await this.db.update(messages2).set({ permalink, updatedAt: new Date }).where(and2(eq(messages2.chatId, chatId), eq(messages2.externalId, externalId)));
346844
+ }
346758
346845
  async updateReplyToReference(id, replyToMessageId) {
346759
346846
  await this.db.update(messages2).set({ replyToMessageId, updatedAt: new Date }).where(eq(messages2.id, id));
346760
346847
  }
@@ -346936,7 +347023,92 @@ var init_payload_store2 = __esm(() => {
346936
347023
  log97 = createLogger("payload-store");
346937
347024
  });
346938
347025
 
347026
+ // ../api/src/utils/phone.ts
347027
+ function isValidE164Phone(phone) {
347028
+ const bare = phone.replace(/^\+/, "");
347029
+ if (!/^\d+$/.test(bare))
347030
+ return false;
347031
+ return bare.length >= 7 && bare.length <= 15;
347032
+ }
347033
+ function isLidFormat(platformUserId) {
347034
+ const bare = platformUserId.split("@")[0] || platformUserId;
347035
+ return /^\d{14,}$/.test(bare);
347036
+ }
347037
+ function validateContactPhone(phone, platformUserId) {
347038
+ if (!phone)
347039
+ return;
347040
+ if (!isValidE164Phone(phone))
347041
+ return;
347042
+ const barePhone = phone.replace(/^\+/, "");
347043
+ const barePuid = platformUserId.split("@")[0] || platformUserId;
347044
+ if (isLidFormat(platformUserId) && barePhone === barePuid)
347045
+ return;
347046
+ return phone;
347047
+ }
347048
+
347049
+ // ../api/src/utils/canonical-handle.ts
347050
+ function isWhatsAppFamily(channel5) {
347051
+ return WHATSAPP_FAMILY_CHANNELS.has(channel5);
347052
+ }
347053
+ function isPersonlessChannel(channel5) {
347054
+ return PERSONLESS_CHANNELS.has(channel5);
347055
+ }
347056
+ function stripProviderPrefix(raw2) {
347057
+ return raw2.replace(/^whatsapp:/i, "").trim();
347058
+ }
347059
+ function stripDeviceSuffix2(local) {
347060
+ const colon = local.indexOf(":");
347061
+ return colon === -1 ? local : local.slice(0, colon);
347062
+ }
347063
+ function canonicalizeHandle(channel5, rawUserId) {
347064
+ if (!rawUserId)
347065
+ return { platformUserId: rawUserId };
347066
+ if (!isWhatsAppFamily(channel5))
347067
+ return { platformUserId: rawUserId };
347068
+ const stripped = stripProviderPrefix(rawUserId);
347069
+ const atIndex = stripped.indexOf("@");
347070
+ const suffix = atIndex === -1 ? "" : stripped.slice(atIndex);
347071
+ const localRaw = atIndex === -1 ? stripped : stripped.slice(0, atIndex);
347072
+ if (suffix === "@lid") {
347073
+ return { platformUserId: `${stripDeviceSuffix2(localRaw)}@lid` };
347074
+ }
347075
+ if (suffix === "@g.us" || suffix === "@broadcast" || suffix === "@newsletter") {
347076
+ return { platformUserId: stripped };
347077
+ }
347078
+ const digits = stripDeviceSuffix2(localRaw).replace(/^\+/, "");
347079
+ if (isValidE164Phone(digits)) {
347080
+ return { platformUserId: `${digits}${WA_PHONE_SUFFIX}`, phone: `+${digits}` };
347081
+ }
347082
+ return { platformUserId: rawUserId };
347083
+ }
347084
+ var WHATSAPP_FAMILY_CHANNELS, PERSONLESS_CHANNELS, WA_PHONE_SUFFIX = "@s.whatsapp.net";
347085
+ var init_canonical_handle = __esm(() => {
347086
+ WHATSAPP_FAMILY_CHANNELS = new Set([
347087
+ "whatsapp-baileys",
347088
+ "whatsapp-business",
347089
+ "twilio-whatsapp",
347090
+ "gupshup",
347091
+ "hermes"
347092
+ ]);
347093
+ PERSONLESS_CHANNELS = new Set(["internal", "a2a"]);
347094
+ });
347095
+
346939
347096
  // ../api/src/services/persons.ts
347097
+ function canonicalizeIdentityInput(rawData, rawLinkOptions) {
347098
+ const data = {
347099
+ ...rawData,
347100
+ platformUserId: canonicalizeHandle(rawData.channel, rawData.platformUserId).platformUserId
347101
+ };
347102
+ if (rawLinkOptions?.matchByPlatformUserId === undefined) {
347103
+ return { data, linkOptions: rawLinkOptions };
347104
+ }
347105
+ const linkOptions = {
347106
+ ...rawLinkOptions,
347107
+ matchByPlatformUserId: canonicalizeHandle(rawData.channel, rawLinkOptions.matchByPlatformUserId).platformUserId
347108
+ };
347109
+ return { data, linkOptions };
347110
+ }
347111
+
346940
347112
  class PersonService {
346941
347113
  pool;
346942
347114
  eventBus;
@@ -347029,8 +347201,8 @@ class PersonService {
347029
347201
  `);
347030
347202
  return result;
347031
347203
  }
347032
- async getById(id) {
347033
- const [result] = await this.db.select().from(persons).where(eq(persons.id, id)).limit(1);
347204
+ async getById(id, handle = this.db) {
347205
+ const [result] = await handle.select().from(persons).where(eq(persons.id, id)).limit(1);
347034
347206
  if (!result) {
347035
347207
  throw new NotFoundError("Person", id);
347036
347208
  }
@@ -347096,8 +347268,8 @@ class PersonService {
347096
347268
  const [mapping] = await this.db.select({ phoneId: chatIdMappings.phoneId }).from(chatIdMappings).where(and2(eq(chatIdMappings.instanceId, instanceId), eq(chatIdMappings.lidId, lidId))).limit(1);
347097
347269
  return mapping?.phoneId ?? null;
347098
347270
  }
347099
- async findPersonByPhone(phone) {
347100
- const [existing] = await this.db.select().from(persons).where(eq(persons.primaryPhone, phone)).limit(1);
347271
+ async findPersonByPhone(phone, handle = this.db) {
347272
+ const [existing] = await handle.select().from(persons).where(eq(persons.primaryPhone, phone)).limit(1);
347101
347273
  return existing ? { personId: existing.id, wasLinked: true } : null;
347102
347274
  }
347103
347275
  async findPersonToLink(linkOptions) {
@@ -347130,9 +347302,9 @@ class PersonService {
347130
347302
  return { wasLinked: false };
347131
347303
  return this.createPersonWithConflictHandling(linkOptions);
347132
347304
  }
347133
- async createPersonWithConflictHandling(linkOptions) {
347305
+ async createPersonWithConflictHandling(linkOptions, handle = this.db) {
347134
347306
  try {
347135
- const [newPerson] = await this.db.insert(persons).values({
347307
+ const [newPerson] = await handle.insert(persons).values({
347136
347308
  displayName: linkOptions.displayName,
347137
347309
  primaryPhone: linkOptions.matchByPhone,
347138
347310
  primaryEmail: linkOptions.matchByEmail
@@ -347140,17 +347312,17 @@ class PersonService {
347140
347312
  if (newPerson)
347141
347313
  return { personId: newPerson.id, wasLinked: false };
347142
347314
  if (linkOptions.matchByPhone) {
347143
- return await this.findPersonByPhone(linkOptions.matchByPhone) ?? { wasLinked: false };
347315
+ return await this.findPersonByPhone(linkOptions.matchByPhone, handle) ?? { wasLinked: false };
347144
347316
  }
347145
347317
  } catch {
347146
347318
  if (linkOptions.matchByPhone) {
347147
- return await this.findPersonByPhone(linkOptions.matchByPhone) ?? { wasLinked: false };
347319
+ return await this.findPersonByPhone(linkOptions.matchByPhone, handle) ?? { wasLinked: false };
347148
347320
  }
347149
347321
  }
347150
347322
  return { wasLinked: false };
347151
347323
  }
347152
- async updateExistingIdentity(existing, data) {
347153
- const [updated] = await this.db.update(platformIdentities).set({
347324
+ async updateExistingIdentity(existing, data, handle = this.db) {
347325
+ const [updated] = await handle.update(platformIdentities).set({
347154
347326
  platformUsername: data.platformUsername ?? existing.platformUsername,
347155
347327
  profilePicUrl: data.profilePicUrl ?? existing.profilePicUrl,
347156
347328
  profileData: data.profileData ?? existing.profileData,
@@ -347162,23 +347334,24 @@ class PersonService {
347162
347334
  }
347163
347335
  let person2 = null;
347164
347336
  if (updated.personId) {
347165
- person2 = await this.getById(updated.personId);
347337
+ person2 = await this.getById(updated.personId, handle);
347166
347338
  }
347167
347339
  return { identity: updated, person: person2, isNew: false, wasLinked: false };
347168
347340
  }
347169
- async findOrCreateIdentity(data, linkOptions) {
347170
- const instanceId = data.instanceId;
347341
+ async findOrCreateIdentity(rawData, rawLinkOptions) {
347342
+ const instanceId = rawData.instanceId;
347171
347343
  if (!instanceId) {
347172
347344
  throw new Error("instanceId is required");
347173
347345
  }
347346
+ const { data, linkOptions } = canonicalizeIdentityInput(rawData, rawLinkOptions);
347174
347347
  const existing = await this.db.select().from(platformIdentities).where(and2(eq(platformIdentities.channel, data.channel), eq(platformIdentities.instanceId, instanceId), eq(platformIdentities.platformUserId, data.platformUserId))).limit(1);
347175
347348
  if (existing[0]) {
347176
347349
  return this.updateExistingIdentity(existing[0], data);
347177
347350
  }
347178
347351
  let personId = data.personId;
347179
347352
  let wasLinked = false;
347353
+ let resolvedLinkOptions = linkOptions;
347180
347354
  if (!personId && linkOptions) {
347181
- let resolvedLinkOptions = linkOptions;
347182
347355
  if (!linkOptions.matchByPhone && data.platformUserId.endsWith("@lid") && instanceId) {
347183
347356
  const phoneFromLid = await this.resolvePhoneFromLid(instanceId, data.platformUserId);
347184
347357
  if (phoneFromLid) {
@@ -347186,31 +347359,63 @@ class PersonService {
347186
347359
  resolvedLinkOptions = { ...linkOptions, matchByPhone: phoneNumber };
347187
347360
  }
347188
347361
  }
347189
- const linkResult = await this.findPersonToLink({ ...resolvedLinkOptions, instanceId });
347362
+ const linkResult = await this.findPersonToLink({ ...resolvedLinkOptions, instanceId, createPerson: false });
347190
347363
  personId = linkResult.personId;
347191
347364
  wasLinked = linkResult.wasLinked;
347192
347365
  }
347193
347366
  const matchType = linkOptions?.matchByPhone ? "phone" : linkOptions?.matchByEmail ? "email" : "platform_id";
347194
347367
  const linkedBy = wasLinked ? `${matchType}_match` : personId ? "initial" : undefined;
347195
347368
  const linkReason = wasLinked ? `Matched by ${matchType}` : undefined;
347196
- const [created] = await this.db.insert(platformIdentities).values({
347197
- ...data,
347369
+ const createPersonOptions = !personId && resolvedLinkOptions?.createPerson ? {
347370
+ matchByPhone: resolvedLinkOptions.matchByPhone,
347371
+ matchByEmail: resolvedLinkOptions.matchByEmail,
347372
+ displayName: resolvedLinkOptions.displayName
347373
+ } : undefined;
347374
+ return this.secureIdentity({
347375
+ data,
347198
347376
  personId,
347377
+ wasLinked,
347199
347378
  linkedBy,
347379
+ linkReason,
347200
347380
  confidence: wasLinked ? 90 : 100,
347201
- linkReason
347202
- }).returning();
347203
- if (!created) {
347204
- throw new Error("Failed to create identity");
347205
- }
347206
- let person2 = null;
347207
- if (personId) {
347208
- person2 = await this.getById(personId);
347209
- }
347210
- return { identity: created, person: person2, isNew: true, wasLinked };
347381
+ createPersonOptions
347382
+ });
347383
+ }
347384
+ async secureIdentity(params) {
347385
+ const { data, personId, wasLinked, linkedBy, linkReason, confidence, createPersonOptions } = params;
347386
+ const { instanceId, channel: channel5, platformUserId } = data;
347387
+ return this.db.transaction(async (tx) => {
347388
+ const txDb = tx;
347389
+ const [inserted] = await txDb.insert(platformIdentities).values({
347390
+ ...data,
347391
+ personId,
347392
+ linkedBy,
347393
+ confidence,
347394
+ linkReason
347395
+ }).onConflictDoNothing().returning();
347396
+ if (!inserted) {
347397
+ const [existing] = await txDb.select().from(platformIdentities).where(and2(eq(platformIdentities.channel, channel5), eq(platformIdentities.instanceId, instanceId ?? ""), eq(platformIdentities.platformUserId, platformUserId))).limit(1);
347398
+ if (!existing) {
347399
+ throw new Error("Failed to resolve identity after conflict");
347400
+ }
347401
+ return this.updateExistingIdentity(existing, data, txDb);
347402
+ }
347403
+ if (inserted.personId || !createPersonOptions) {
347404
+ const person3 = inserted.personId ? await this.getById(inserted.personId, txDb) : null;
347405
+ return { identity: inserted, person: person3, isNew: true, wasLinked };
347406
+ }
347407
+ const created = await this.createPersonWithConflictHandling(createPersonOptions, txDb);
347408
+ if (!created.personId) {
347409
+ return { identity: inserted, person: null, isNew: true, wasLinked };
347410
+ }
347411
+ const [linked] = await txDb.update(platformIdentities).set({ personId: created.personId, linkedBy: "initial", confidence: 100, updatedAt: new Date }).where(eq(platformIdentities.id, inserted.id)).returning();
347412
+ const person2 = await this.getById(created.personId, txDb);
347413
+ return { identity: linked ?? inserted, person: person2, isNew: true, wasLinked: created.wasLinked };
347414
+ });
347211
347415
  }
347212
347416
  async getIdentityByPlatformId(channel5, instanceId, platformUserId) {
347213
- const [identity] = await this.db.select().from(platformIdentities).where(and2(eq(platformIdentities.channel, channel5), eq(platformIdentities.instanceId, instanceId), eq(platformIdentities.platformUserId, platformUserId))).limit(1);
347417
+ const canonicalUserId = canonicalizeHandle(channel5, platformUserId).platformUserId;
347418
+ const [identity] = await this.db.select().from(platformIdentities).where(and2(eq(platformIdentities.channel, channel5), eq(platformIdentities.instanceId, instanceId), eq(platformIdentities.platformUserId, canonicalUserId))).limit(1);
347214
347419
  return identity || null;
347215
347420
  }
347216
347421
  async listIdentitiesByInstance(instanceId, options = {}) {
@@ -347387,6 +347592,7 @@ var init_persons = __esm(() => {
347387
347592
  init_src5();
347388
347593
  init_drizzle_orm();
347389
347594
  init_tenant_scope();
347595
+ init_canonical_handle();
347390
347596
  });
347391
347597
 
347392
347598
  // ../api/src/lib/agent-key-name.ts
@@ -348358,16 +348564,6 @@ function buildSentChatPreview(payload) {
348358
348564
  const badge = payload.content.type !== "text" ? MEDIA_BADGES2[payload.content.type] ?? `[${payload.content.type}]` : "";
348359
348565
  return badge ? text3 ? `${badge} ${text3}` : badge : text3;
348360
348566
  }
348361
- function extractPhoneFromSender(senderId, channel5) {
348362
- if (!channel5.startsWith("whatsapp"))
348363
- return;
348364
- const bare = senderId.split("@")[0] || senderId;
348365
- if (!/^\d+$/.test(bare))
348366
- return;
348367
- if (bare.length < 7 || bare.length > 15)
348368
- return;
348369
- return `+${bare}`;
348370
- }
348371
348567
  async function processSenderIdentity(services, payload, metadata, channel5, trustedTenantId) {
348372
348568
  if (metadata.platformIdentityId) {
348373
348569
  return { personId: metadata.personId, platformIdentityId: metadata.platformIdentityId };
@@ -348375,11 +348571,15 @@ async function processSenderIdentity(services, payload, metadata, channel5, trus
348375
348571
  if (!payload.from) {
348376
348572
  return { personId: metadata.personId, platformIdentityId: undefined };
348377
348573
  }
348574
+ if (isPersonlessChannel(channel5)) {
348575
+ return { personId: metadata.personId, platformIdentityId: metadata.platformIdentityId };
348576
+ }
348378
348577
  const displayName = truncate4(payload.senderName ?? payload.rawPayload?.pushName, 255);
348379
- const platformUserId = truncate4(payload.from, 255) ?? payload.from;
348578
+ const canonical = canonicalizeHandle(channel5, payload.from);
348579
+ const platformUserId = truncate4(canonical.platformUserId, 255) ?? canonical.platformUserId;
348380
348580
  const isLidAddressed = payload.rawPayload?.addressingMode === "lid" || payload.rawPayload?.senderIsLid === true;
348381
348581
  const resolvedPhone = isLidAddressed ? payload.rawPayload?.resolvedSenderPhone : undefined;
348382
- const phoneNumber = isLidAddressed ? resolvedPhone ? `+${resolvedPhone}` : undefined : extractPhoneFromSender(platformUserId, channel5);
348582
+ const phoneNumber = isLidAddressed ? resolvedPhone ? `+${resolvedPhone}` : undefined : canonical.phone;
348383
348583
  const { identity, person: person2, isNew } = await services.persons.findOrCreateIdentity({ channel: channel5, instanceId: metadata.instanceId, platformUserId, platformUsername: displayName }, {
348384
348584
  createPerson: true,
348385
348585
  displayName,
@@ -348554,6 +348754,22 @@ async function resolveOrCreateChat(services, instanceId, chatExternalId, chatTyp
348554
348754
  canonicalId
348555
348755
  });
348556
348756
  }
348757
+ async function linkReplyTarget(services, chatId, messageId, replyToExternalId) {
348758
+ if (!replyToExternalId)
348759
+ return;
348760
+ try {
348761
+ const replyToMessageId = await services.messages.resolveReplyToMessage(chatId, replyToExternalId);
348762
+ if (replyToMessageId) {
348763
+ await services.messages.updateReplyToReference(messageId, replyToMessageId);
348764
+ }
348765
+ } catch (error3) {
348766
+ log103.debug("Could not resolve reply target", {
348767
+ chatId,
348768
+ replyToExternalId,
348769
+ error: error3 instanceof Error ? error3.message : String(error3)
348770
+ });
348771
+ }
348772
+ }
348557
348773
  async function handleMessageReceived(services, payload, metadata, eventTimestamp, trustedTenantId, identity) {
348558
348774
  const channel5 = metadata.channelType ?? "whatsapp";
348559
348775
  const isHistorySync = metadata.ingestMode === "history-sync";
@@ -348597,12 +348813,15 @@ async function handleMessageReceived(services, payload, metadata, eventTimestamp
348597
348813
  replyToExternalId: truncate4(payload.replyToId, 255),
348598
348814
  quotedText: quotedMessage?.conversation,
348599
348815
  quotedSenderName: truncate4(quotedMessage?.pushName, 255),
348816
+ threadExternalId: truncate4(payload.threadId, 255),
348817
+ isThreadBroadcast: rawPayload?.isThreadBroadcast === true,
348600
348818
  isForwarded: !!(rawPayload?.isForwarded || rawPayload?.forwardingScore),
348601
348819
  rawPayload
348602
348820
  });
348603
348821
  await maybeRecordMessageEdit(services, created, rawPayload, message2.id, sanitizeText(payload.content.text) ?? undefined, platformTimestamp ?? new Date(eventTimestamp), payload.from);
348604
348822
  if (created) {
348605
348823
  log103.debug("Created message", { externalId: payload.externalId, chatId: chat2.id });
348824
+ await linkReplyTarget(services, chat2.id, message2.id, payload.replyToId);
348606
348825
  }
348607
348826
  await maybeRecordParticipantActivity(services, chat2.id, payload.from);
348608
348827
  maybeUpdateRecency(services, metadata.instanceId, chat2.id, rawPayload, payload, platformTimestamp ?? new Date(eventTimestamp), trustedTenantId);
@@ -348700,7 +348919,8 @@ async function setupMessagePersistence(eventBus, services) {
348700
348919
  mediaLocalPath: sentContent.mediaLocalPath,
348701
348920
  mediaMetadata: sentContent.mediaMetadata,
348702
348921
  rawPayload: sentContent.rawPayload,
348703
- replyToExternalId: truncate4(payload.replyToId, 255)
348922
+ replyToExternalId: truncate4(payload.replyToId, 255),
348923
+ threadExternalId: truncate4(payload.threadId, 255)
348704
348924
  });
348705
348925
  return { chat: chat3, message: message3, created: created2 };
348706
348926
  });
@@ -348898,6 +349118,7 @@ var init_message_persistence = __esm(() => {
348898
349118
  init_esm5();
348899
349119
  init_sentry_scrub();
348900
349120
  init_worker_tenant_context();
349121
+ init_canonical_handle();
348901
349122
  init_loader2();
348902
349123
  log103 = createLogger("message-persistence");
348903
349124
  CONTENT_TYPE_MAP = {
@@ -349033,12 +349254,12 @@ var init_session_storage = __esm(() => {
349033
349254
  });
349034
349255
 
349035
349256
  // ../api/src/plugins/agent-dispatcher.ts
349036
- import { createHash as createHash12 } from "crypto";
349257
+ import { createHash as createHash13 } from "crypto";
349037
349258
  import { unlink, writeFile as writeFile5 } from "fs/promises";
349038
349259
  import { tmpdir as tmpdir12 } from "os";
349039
349260
  import { join as join22, resolve as resolve3 } from "path";
349040
349261
  function sha256Digest(value) {
349041
- return `sha256:${createHash12("sha256").update(value).digest("hex")}`;
349262
+ return `sha256:${createHash13("sha256").update(value).digest("hex")}`;
349042
349263
  }
349043
349264
  function redactLifecycleText(value) {
349044
349265
  return value.replace(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi, "[EMAIL]").replace(/\b\+?\d[\d\s().-]{5,}\d\b/g, "[PHONE]").replace(/\b\d{6,}\b/g, "[NUMBER]").replace(/\b[^\s@]+@(s\.whatsapp\.net|g\.us|lid|newsletter)\b/gi, "[JID]");
@@ -365900,6 +366121,10 @@ function sanitizeInstance(instance4) {
365900
366121
  function applySlackProfileMetadata(opts, metadata) {
365901
366122
  if (!metadata)
365902
366123
  return;
366124
+ if (metadata.mode)
366125
+ opts.mode = metadata.mode;
366126
+ if (metadata.httpPort)
366127
+ opts.httpPort = metadata.httpPort;
365903
366128
  if (metadata.replyToMode)
365904
366129
  opts.replyToMode = metadata.replyToMode;
365905
366130
  if (metadata.streamMode)
@@ -365913,6 +366138,12 @@ function applySlackConnectOptions(opts, instance4, overrides) {
365913
366138
  const botToken = overrides?.slackBotToken ?? instance4.slackBotToken ?? opts.token;
365914
366139
  if (botToken)
365915
366140
  opts.botToken = botToken;
366141
+ const userToken = overrides?.slackUserToken ?? instance4.slackUserToken;
366142
+ if (userToken)
366143
+ opts.userToken = userToken;
366144
+ const authMode = overrides?.slackAuthMode ?? instance4.slackAuthMode;
366145
+ if (authMode)
366146
+ opts.authMode = authMode;
365916
366147
  const appToken = overrides?.slackAppToken ?? instance4.slackAppToken;
365917
366148
  if (appToken)
365918
366149
  opts.appToken = appToken;
@@ -365937,8 +366168,13 @@ function applyTelegramConnectionOptions(options, input) {
365937
366168
  options.telegramReactionLevel = input.telegramReactionLevel;
365938
366169
  }
365939
366170
  function applySlackConnectionOptions(options, input) {
366171
+ applySlackProfileMetadata(options, input.profileMetadata);
365940
366172
  if (input.token)
365941
366173
  options.botToken = input.token;
366174
+ if (input.slackUserToken)
366175
+ options.userToken = input.slackUserToken;
366176
+ if (input.slackAuthMode)
366177
+ options.authMode = input.slackAuthMode;
365942
366178
  if (input.slackAppToken)
365943
366179
  options.appToken = input.slackAppToken;
365944
366180
  if (input.slackSigningSecret)
@@ -366049,6 +366285,10 @@ function buildTokenPersistUpdates(channel5, body) {
366049
366285
  if (channel5 === "slack") {
366050
366286
  if (body.slackBotToken)
366051
366287
  updates.slackBotToken = body.slackBotToken;
366288
+ if (body.slackUserToken)
366289
+ updates.slackUserToken = body.slackUserToken;
366290
+ if (body.slackAuthMode)
366291
+ updates.slackAuthMode = body.slackAuthMode;
366052
366292
  if (body.slackAppToken)
366053
366293
  updates.slackAppToken = body.slackAppToken;
366054
366294
  if (body.slackSigningSecret)
@@ -366108,6 +366348,9 @@ async function handleAgentKeyProvisioning(services, instanceId, newAgentId, oldA
366108
366348
  }
366109
366349
  }
366110
366350
  }
366351
+ function supportsPasskey(plugin10) {
366352
+ return "getPasskeyState" in plugin10 && "submitPasskeyResponse" in plugin10 && "confirmPasskey" in plugin10;
366353
+ }
366111
366354
  function buildConnectConnectionOptions(instance4, body, forceNewQr) {
366112
366355
  const connectToken = body.token ?? persistedTokenForChannel(instance4);
366113
366356
  return buildInstanceConnectionOptions({
@@ -366115,8 +366358,11 @@ function buildConnectConnectionOptions(instance4, body, forceNewQr) {
366115
366358
  forceNewQr,
366116
366359
  token: connectToken,
366117
366360
  telegramReactionLevel: instance4.telegramReactionLevel,
366361
+ slackUserToken: body.slackUserToken ?? instance4.slackUserToken,
366362
+ slackAuthMode: body.slackAuthMode ?? instance4.slackAuthMode,
366118
366363
  slackAppToken: body.slackAppToken ?? instance4.slackAppToken,
366119
366364
  slackSigningSecret: body.slackSigningSecret ?? instance4.slackSigningSecret,
366365
+ profileMetadata: instance4.profileMetadata,
366120
366366
  whatsapp: body.whatsapp,
366121
366367
  gupshupCallbackUrl: instance4.gupshupCallbackUrl,
366122
366368
  gupshupAuthToken: instance4.gupshupAuthToken,
@@ -366274,7 +366520,7 @@ function getCacheKey(instanceId, guildId) {
366274
366520
  function invalidateGuildCache(instanceId, guildId) {
366275
366521
  guildConfigCache.delete(getCacheKey(instanceId, guildId));
366276
366522
  }
366277
- var log115, instancesRoutes, instanceAccess2, listQuerySchema12, agentReplyFilterSchema, createInstanceSchema, updateInstanceSchema, DEFAULT_AGENT_REPLY_FILTER, SENSITIVE_INSTANCE_FIELDS, pairingCodeSchema, connectInstanceSchema, syncRequestSchema, listContactsQuerySchema, listGroupsQuerySchema, checkNumberSchema, updateBioSchema, blockContactSchema, groupParticipantActionSchema, groupSettingSchema, groupParticipantsSchema, groupParticipantsPatchSchema, updateGroupSubjectSchema, updateGroupDescriptionSchema, updateGroupSettingsSchema, patchGroupSchema, resyncSchema, replaySchema, pairingActionSchema, guildConfigOverrideSchema, guildConfigCache, GUILD_CONFIG_TTL, presenceSchema;
366523
+ var log115, instancesRoutes, instanceAccess2, listQuerySchema12, agentReplyFilterSchema, createInstanceSchema, updateInstanceSchema, DEFAULT_AGENT_REPLY_FILTER, SENSITIVE_INSTANCE_FIELDS, pairingCodeSchema, passkeyBase64UrlSchema, passkeyCredentialSchema, connectInstanceSchema, syncRequestSchema, listContactsQuerySchema, listGroupsQuerySchema, checkNumberSchema, updateBioSchema, blockContactSchema, groupParticipantActionSchema, groupSettingSchema, groupParticipantsSchema, groupParticipantsPatchSchema, updateGroupSubjectSchema, updateGroupDescriptionSchema, updateGroupSettingsSchema, patchGroupSchema, resyncSchema, replaySchema, pairingActionSchema, guildConfigOverrideSchema, guildConfigCache, GUILD_CONFIG_TTL, presenceSchema;
366278
366524
  var init_instances3 = __esm(() => {
366279
366525
  init_dist6();
366280
366526
  init_src();
@@ -366341,6 +366587,8 @@ var init_instances3 = __esm(() => {
366341
366587
  agentGatePrompt: exports_external.string().nullable().default(null).describe("Custom prompt for response gate (null = use default)"),
366342
366588
  telegramBotToken: exports_external.string().optional().nullable().describe("Telegram bot token (persisted for reconnection)"),
366343
366589
  discordBotToken: exports_external.string().optional().nullable().describe("Discord bot token (persisted for reconnection)"),
366590
+ slackUserToken: exports_external.string().optional().nullable().describe("Slack user token xoxp (required when slackAuthMode is user)"),
366591
+ slackAuthMode: exports_external.enum(["bot", "user"]).optional().nullable().describe("Slack identity for outbound actions"),
366344
366592
  slackBotToken: exports_external.string().optional().nullable().describe("Slack bot token (persisted for reconnection)"),
366345
366593
  slackAppToken: exports_external.string().optional().nullable().describe("Slack app token (persisted for reconnection)"),
366346
366594
  slackSigningSecret: exports_external.string().optional().nullable().describe("Slack signing secret (persisted for reconnection)"),
@@ -366372,6 +366620,7 @@ var init_instances3 = __esm(() => {
366372
366620
  allowFirstParty: exports_external.boolean().default(false).describe(`When true, this instance processes (does not drop) inbound messages whose sender matches another active instance owner. Lets an "assistant" instance reply to messages from the operator's own personal number. Default: false (loop-protection drop).`)
366373
366621
  });
366374
366622
  updateInstanceSchema = createInstanceSchema.partial().extend({
366623
+ profileMetadata: exports_external.record(exports_external.unknown()).nullable().optional(),
366375
366624
  agentId: exports_external.string().uuid().nullable().optional(),
366376
366625
  agentErrorMessages: exports_external.array(exports_external.string()).nullable().optional(),
366377
366626
  agentReplyFilter: agentReplyFilterSchema.nullable().optional(),
@@ -366379,6 +366628,8 @@ var init_instances3 = __esm(() => {
366379
366628
  telegramBotToken: exports_external.string().nullable().optional(),
366380
366629
  discordBotToken: exports_external.string().nullable().optional(),
366381
366630
  slackBotToken: exports_external.string().nullable().optional(),
366631
+ slackUserToken: exports_external.string().nullable().optional(),
366632
+ slackAuthMode: exports_external.enum(["bot", "user"]).nullable().optional(),
366382
366633
  slackAppToken: exports_external.string().nullable().optional(),
366383
366634
  gupshupCallbackUrl: exports_external.string().nullable().optional(),
366384
366635
  gupshupAuthToken: exports_external.string().nullable().optional(),
@@ -366413,6 +366664,7 @@ var init_instances3 = __esm(() => {
366413
366664
  "telegramBotToken",
366414
366665
  "discordBotToken",
366415
366666
  "slackBotToken",
366667
+ "slackUserToken",
366416
366668
  "slackAppToken",
366417
366669
  "slackSigningSecret",
366418
366670
  "gupshupAuthToken",
@@ -366504,8 +366756,11 @@ var init_instances3 = __esm(() => {
366504
366756
  forceNewQr: true,
366505
366757
  token: connectToken,
366506
366758
  telegramReactionLevel: instance4.telegramReactionLevel,
366759
+ slackUserToken: instance4.slackUserToken,
366760
+ slackAuthMode: instance4.slackAuthMode,
366507
366761
  slackAppToken: instance4.slackAppToken,
366508
366762
  slackSigningSecret: instance4.slackSigningSecret,
366763
+ profileMetadata: instance4.profileMetadata,
366509
366764
  gupshupCallbackUrl: instance4.gupshupCallbackUrl,
366510
366765
  gupshupAuthToken: instance4.gupshupAuthToken,
366511
366766
  gupshupEventId: instance4.gupshupEventId,
@@ -366651,6 +366906,18 @@ var init_instances3 = __esm(() => {
366651
366906
  pairingCodeSchema = exports_external.object({
366652
366907
  phoneNumber: exports_external.string().min(10).max(20).describe("Phone number in international format (e.g., +5511999999999)")
366653
366908
  });
366909
+ passkeyBase64UrlSchema = exports_external.string().min(1).regex(/^[A-Za-z0-9_-]+$/);
366910
+ passkeyCredentialSchema = exports_external.object({
366911
+ id: passkeyBase64UrlSchema,
366912
+ rawId: passkeyBase64UrlSchema,
366913
+ type: exports_external.literal("public-key"),
366914
+ response: exports_external.object({
366915
+ clientDataJSON: passkeyBase64UrlSchema,
366916
+ authenticatorData: passkeyBase64UrlSchema,
366917
+ signature: passkeyBase64UrlSchema,
366918
+ userHandle: passkeyBase64UrlSchema.nullable()
366919
+ })
366920
+ });
366654
366921
  instancesRoutes.post("/:id/pair", instanceAccess2, zValidator("json", pairingCodeSchema), async (c) => {
366655
366922
  const id = c.req.param("id");
366656
366923
  const { phoneNumber } = c.req.valid("json");
@@ -366686,9 +366953,59 @@ var init_instances3 = __esm(() => {
366686
366953
  return c.json({ error: { code: "PAIRING_FAILED", message: message2 } }, 500);
366687
366954
  }
366688
366955
  });
366956
+ instancesRoutes.get("/:id/passkey", instanceAccess2, async (c) => {
366957
+ const id = c.req.param("id");
366958
+ const services = c.get("services");
366959
+ const channelRegistry2 = c.get("channelRegistry");
366960
+ const instance4 = await services.instances.getById(id);
366961
+ if (!instance4.channel.startsWith("whatsapp")) {
366962
+ return c.json({ error: { code: "INVALID_OPERATION", message: "Passkey is only available for WhatsApp." } }, 400);
366963
+ }
366964
+ const plugin10 = channelRegistry2?.get(instance4.channel);
366965
+ if (!plugin10 || !supportsPasskey(plugin10)) {
366966
+ return c.json({ error: { code: "NOT_SUPPORTED", message: "This WhatsApp connector does not support passkey." } }, 400);
366967
+ }
366968
+ return c.json({ data: plugin10.getPasskeyState(id) });
366969
+ });
366970
+ instancesRoutes.post("/:id/passkey/response", instanceAccess2, zValidator("json", passkeyCredentialSchema), async (c) => {
366971
+ const id = c.req.param("id");
366972
+ const services = c.get("services");
366973
+ const channelRegistry2 = c.get("channelRegistry");
366974
+ const instance4 = await services.instances.getById(id);
366975
+ const plugin10 = channelRegistry2?.get(instance4.channel);
366976
+ if (!instance4.channel.startsWith("whatsapp") || !plugin10 || !supportsPasskey(plugin10)) {
366977
+ return c.json({ error: { code: "NOT_SUPPORTED", message: "Passkey is not available for this instance." } }, 400);
366978
+ }
366979
+ try {
366980
+ await plugin10.submitPasskeyResponse(id, c.req.valid("json"));
366981
+ return c.json({ data: { status: "submitted" } });
366982
+ } catch (error3) {
366983
+ const message2 = error3 instanceof Error ? error3.message : "Unable to submit passkey response.";
366984
+ return c.json({ error: { code: "PASSKEY_FAILED", message: message2 } }, 500);
366985
+ }
366986
+ });
366987
+ instancesRoutes.post("/:id/passkey/confirm", instanceAccess2, async (c) => {
366988
+ const id = c.req.param("id");
366989
+ const services = c.get("services");
366990
+ const channelRegistry2 = c.get("channelRegistry");
366991
+ const instance4 = await services.instances.getById(id);
366992
+ const plugin10 = channelRegistry2?.get(instance4.channel);
366993
+ if (!instance4.channel.startsWith("whatsapp") || !plugin10 || !supportsPasskey(plugin10)) {
366994
+ return c.json({ error: { code: "NOT_SUPPORTED", message: "Passkey is not available for this instance." } }, 400);
366995
+ }
366996
+ try {
366997
+ await plugin10.confirmPasskey(id);
366998
+ return c.json({ data: { status: "confirmed" } });
366999
+ } catch (error3) {
367000
+ const message2 = error3 instanceof Error ? error3.message : "Unable to confirm passkey pairing.";
367001
+ return c.json({ error: { code: "PASSKEY_FAILED", message: message2 } }, 500);
367002
+ }
367003
+ });
366689
367004
  connectInstanceSchema = exports_external.object({
366690
367005
  token: exports_external.string().optional().describe("Bot token for Discord/Telegram instances"),
366691
367006
  slackBotToken: exports_external.string().optional().describe("Slack bot token (xoxb-...)"),
367007
+ slackUserToken: exports_external.string().optional().describe("Slack user token (xoxp-...), for authMode user"),
367008
+ slackAuthMode: exports_external.enum(["bot", "user"]).optional().describe("Act as the bot (default) or as the authorizing user"),
366692
367009
  slackAppToken: exports_external.string().optional().describe("Slack app-level token (xapp-...)"),
366693
367010
  slackSigningSecret: exports_external.string().optional().describe("Slack signing secret"),
366694
367011
  forceNewQr: exports_external.boolean().optional().describe("Force new QR code for WhatsApp (re-authentication)"),
@@ -366782,11 +367099,6 @@ var init_instances3 = __esm(() => {
366782
367099
  restartOptions.token = restartToken;
366783
367100
  if (instance4.channel === "telegram") {
366784
367101
  restartOptions.telegramReactionLevel = instance4.telegramReactionLevel;
366785
- } else if (instance4.channel === "slack") {
366786
- if (restartToken)
366787
- restartOptions.botToken = restartToken;
366788
- if (instance4.slackAppToken)
366789
- restartOptions.appToken = instance4.slackAppToken;
366790
367102
  }
366791
367103
  if (instance4.channel === "slack") {
366792
367104
  applySlackConnectOptions(restartOptions, instance4);
@@ -369491,8 +369803,8 @@ var init_media2 = __esm(() => {
369491
369803
  return c.json({ error: { code: "INVALID_PATH", message: "No path specified" } }, 400);
369492
369804
  }
369493
369805
  const pathSegments = path3.split("/");
369494
- for (const segment of pathSegments) {
369495
- if (!isValidPathComponent(segment)) {
369806
+ for (const segment2 of pathSegments) {
369807
+ if (!isValidPathComponent(segment2)) {
369496
369808
  return c.json({ error: { code: "INVALID_PATH", message: "Invalid path" } }, 400);
369497
369809
  }
369498
369810
  }
@@ -369680,6 +369992,13 @@ async function resolveRecipient(to, channelType, services) {
369680
369992
  recoverable: false
369681
369993
  });
369682
369994
  }
369995
+ async function persistStarState(services, instanceId, channelExternalId, messageExternalId, starred) {
369996
+ try {
369997
+ const chat2 = await services.chats.getByExternalId(instanceId, channelExternalId);
369998
+ if (chat2)
369999
+ await services.messages.setStarred(chat2.id, messageExternalId, starred);
370000
+ } catch {}
370001
+ }
369683
370002
  async function getPluginForInstance2(services, channelRegistry2, instanceId, requiredCapability) {
369684
370003
  const instance4 = await services.instances.getById(instanceId);
369685
370004
  if (!channelRegistry2) {
@@ -369845,7 +370164,7 @@ async function computeCloseContactTerminalState(db2, chatUuid, outcome, auditRow
369845
370164
  return { terminal: false, escalated: false, closeUntil };
369846
370165
  }
369847
370166
  function hasPresenceStatusSender(plugin10) {
369848
- return typeof plugin10 === "object" && plugin10 !== null && "sendPresenceStatus" in plugin10 && typeof plugin10.sendPresenceStatus === "function";
370167
+ return typeof plugin10 === "object" && plugin10 !== null && typeof plugin10.sendPresenceStatus === "function";
369849
370168
  }
369850
370169
  async function verifyMessageInstanceOwnership(services, message2, instanceId) {
369851
370170
  if (!message2.chatId)
@@ -369868,7 +370187,7 @@ async function resolveChannelMessageId(services, messageId, instanceId) {
369868
370187
  log117.debug("Resolved internal UUID to external ID", { messageId, externalId: message2.externalId });
369869
370188
  return message2.externalId;
369870
370189
  }
369871
- var log117, mediaDownloadLog, messagesRoutes, MIME_BY_EXTENSION, DEFAULT_MIME_BY_MEDIA_TYPE, UUID_REGEX2, MessageSourceSchema, MessageTypeSchema, MessageStatusSchema, DeliveryStatusSchema, listQuerySchema14, createMessageSchema, updateMessageSchema, recordEditSchema, addReactionSchema, removeReactionSchema, updateDeliveryStatusSchema, MentionSchema, sendTextSchema, sendMediaSchema, sendReactionSchema, sendStickerSchema, sendContactSchema, sendLocationSchema, sendHandoffSchema, sendCloseContactSchema, messageRefSchema, _mediaStorageForDownload = null, sendTtsSchema, forwardMessageSchema, sendPresenceSchema, markMessageReadSchema, markBatchReadSchema, sendPollSchema, sendEmbedSchema, editMessageChannelSchema, deleteMessageChannelSchema, starMessageSchema;
370190
+ var log117, mediaDownloadLog, messagesRoutes, MIME_BY_EXTENSION, DEFAULT_MIME_BY_MEDIA_TYPE, UUID_REGEX2, permalinkQuerySchema, MessageSourceSchema, MessageTypeSchema, MessageStatusSchema, DeliveryStatusSchema, listQuerySchema14, createMessageSchema, updateMessageSchema, recordEditSchema, addReactionSchema, removeReactionSchema, updateDeliveryStatusSchema, MentionSchema, sendTextSchema, sendMediaSchema, sendReactionSchema, sendStickerSchema, sendContactSchema, sendLocationSchema, sendHandoffSchema, sendCloseContactSchema, messageRefSchema, _mediaStorageForDownload = null, sendTtsSchema, forwardMessageSchema, sendPresenceSchema, markMessageReadSchema, markBatchReadSchema, sendPollSchema, sendEmbedSchema, editMessageChannelSchema, deleteMessageChannelSchema, starMessageSchema;
369872
370191
  var init_messages5 = __esm(() => {
369873
370192
  init_dist6();
369874
370193
  init_src2();
@@ -369911,6 +370230,42 @@ var init_messages5 = __esm(() => {
369911
370230
  document: "application/octet-stream"
369912
370231
  };
369913
370232
  UUID_REGEX2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
370233
+ permalinkQuerySchema = exports_external.object({
370234
+ instanceId: exports_external.string().uuid(),
370235
+ channelId: exports_external.string().min(1).describe("Platform chat/channel id containing the message")
370236
+ });
370237
+ messagesRoutes.get("/:id/permalink", zValidator("query", permalinkQuerySchema), async (c) => {
370238
+ const messageId = c.req.param("id");
370239
+ const { instanceId, channelId } = c.req.valid("query");
370240
+ checkInstanceAccess2(c.get("apiKey"), instanceId);
370241
+ const services = c.get("services");
370242
+ const chat2 = await services.chats.getByExternalId(instanceId, channelId);
370243
+ const stored = chat2 ? await services.messages.getByExternalId(chat2.id, messageId) : null;
370244
+ if (stored?.permalink) {
370245
+ return c.json({ data: { messageId, permalink: stored.permalink, cached: true } });
370246
+ }
370247
+ const { instance: instance4, plugin: plugin10 } = await getPluginForInstance2(services, c.get("channelRegistry"), instanceId);
370248
+ if (typeof plugin10.getPermalink !== "function") {
370249
+ throw new OmniError({
370250
+ code: ERROR_CODES.CAPABILITY_NOT_SUPPORTED,
370251
+ message: `Channel ${instance4.channel} cannot resolve permalinks`,
370252
+ context: { channelType: instance4.channel },
370253
+ recoverable: false
370254
+ });
370255
+ }
370256
+ const permalink = await plugin10.getPermalink(instanceId, channelId, messageId);
370257
+ if (!permalink) {
370258
+ throw new OmniError({
370259
+ code: ERROR_CODES.NOT_FOUND,
370260
+ message: `No permalink available for message ${messageId}`,
370261
+ recoverable: false
370262
+ });
370263
+ }
370264
+ if (chat2) {
370265
+ await services.messages.setPermalink(chat2.id, messageId, permalink).catch(() => {});
370266
+ }
370267
+ return c.json({ data: { messageId, permalink, cached: false } });
370268
+ });
369914
370269
  MessageSourceSchema = exports_external.enum(["realtime", "sync", "api", "import"]);
369915
370270
  MessageTypeSchema = exports_external.enum([
369916
370271
  "text",
@@ -370923,7 +371278,7 @@ var init_messages5 = __esm(() => {
370923
371278
  stability: data.stability,
370924
371279
  similarityBoost: data.similarityBoost
370925
371280
  });
370926
- if ("sendTyping" in plugin10 && typeof plugin10.sendTyping === "function") {
371281
+ if (typeof plugin10.sendTyping === "function") {
370927
371282
  const presenceDuration = data.presenceDelay ?? Math.min(ttsResult.durationMs, 15000);
370928
371283
  try {
370929
371284
  await plugin10.sendTyping(data.instanceId, resolvedTo, presenceDuration);
@@ -371100,7 +371455,7 @@ var init_messages5 = __esm(() => {
371100
371455
  status,
371101
371456
  loadingMessages
371102
371457
  }) : await (async () => {
371103
- if (!("sendTyping" in plugin10) || typeof plugin10.sendTyping !== "function") {
371458
+ if (typeof plugin10.sendTyping !== "function") {
371104
371459
  throw new OmniError({
371105
371460
  code: ERROR_CODES.CAPABILITY_NOT_SUPPORTED,
371106
371461
  message: `Channel ${instance4.channel} plugin does not implement sendTyping`,
@@ -371152,7 +371507,7 @@ var init_messages5 = __esm(() => {
371152
371507
  });
371153
371508
  }
371154
371509
  const { instance: instance4, plugin: plugin10 } = await getPluginForInstance2(services, channelRegistry2, instanceId, "canReceiveReadReceipts");
371155
- if (!("markAsRead" in plugin10) || typeof plugin10.markAsRead !== "function") {
371510
+ if (typeof plugin10.markAsRead !== "function") {
371156
371511
  throw new OmniError({
371157
371512
  code: ERROR_CODES.CAPABILITY_NOT_SUPPORTED,
371158
371513
  message: `Channel ${instance4.channel} plugin does not implement markAsRead`,
@@ -371178,7 +371533,7 @@ var init_messages5 = __esm(() => {
371178
371533
  const channelRegistry2 = c.get("channelRegistry");
371179
371534
  checkInstanceAccess2(c.get("apiKey"), instanceId);
371180
371535
  const { instance: instance4, plugin: plugin10 } = await getPluginForInstance2(services, channelRegistry2, instanceId, "canReceiveReadReceipts");
371181
- if (!("markAsRead" in plugin10) || typeof plugin10.markAsRead !== "function") {
371536
+ if (typeof plugin10.markAsRead !== "function") {
371182
371537
  throw new OmniError({
371183
371538
  code: ERROR_CODES.CAPABILITY_NOT_SUPPORTED,
371184
371539
  message: `Channel ${instance4.channel} plugin does not implement markAsRead`,
@@ -371414,7 +371769,7 @@ var init_messages5 = __esm(() => {
371414
371769
  recoverable: false
371415
371770
  });
371416
371771
  }
371417
- if (!("editMessage" in plugin10) || typeof plugin10.editMessage !== "function") {
371772
+ if (typeof plugin10.editMessage !== "function") {
371418
371773
  throw new OmniError({
371419
371774
  code: ERROR_CODES.CAPABILITY_NOT_SUPPORTED,
371420
371775
  message: `Channel ${instance4.channel} plugin does not implement editMessage`,
@@ -371470,7 +371825,7 @@ var init_messages5 = __esm(() => {
371470
371825
  recoverable: false
371471
371826
  });
371472
371827
  }
371473
- if (!("deleteMessage" in plugin10) || typeof plugin10.deleteMessage !== "function") {
371828
+ if (typeof plugin10.deleteMessage !== "function") {
371474
371829
  throw new OmniError({
371475
371830
  code: ERROR_CODES.CAPABILITY_NOT_SUPPORTED,
371476
371831
  message: `Channel ${instance4.channel} plugin does not implement deleteMessage`,
@@ -371513,7 +371868,7 @@ var init_messages5 = __esm(() => {
371513
371868
  recoverable: false
371514
371869
  });
371515
371870
  }
371516
- if (!("starMessage" in plugin10) || typeof plugin10.starMessage !== "function") {
371871
+ if (typeof plugin10.starMessage !== "function") {
371517
371872
  throw new OmniError({
371518
371873
  code: ERROR_CODES.CAPABILITY_NOT_SUPPORTED,
371519
371874
  message: `Channel ${instance4.channel} does not support starring messages`,
@@ -371522,6 +371877,7 @@ var init_messages5 = __esm(() => {
371522
371877
  });
371523
371878
  }
371524
371879
  await plugin10.starMessage(instanceId, channelId, messageId, true, fromMe);
371880
+ await persistStarState(services, instanceId, channelId, messageId, true);
371525
371881
  return c.json({
371526
371882
  success: true,
371527
371883
  data: { messageId, starred: true }
@@ -371550,7 +371906,7 @@ var init_messages5 = __esm(() => {
371550
371906
  recoverable: false
371551
371907
  });
371552
371908
  }
371553
- if (!("starMessage" in plugin10) || typeof plugin10.starMessage !== "function") {
371909
+ if (typeof plugin10.starMessage !== "function") {
371554
371910
  throw new OmniError({
371555
371911
  code: ERROR_CODES.CAPABILITY_NOT_SUPPORTED,
371556
371912
  message: `Channel ${instance4.channel} does not support starring messages`,
@@ -371559,6 +371915,7 @@ var init_messages5 = __esm(() => {
371559
371915
  });
371560
371916
  }
371561
371917
  await plugin10.starMessage(instanceId, channelId, messageId, false, fromMe);
371918
+ await persistStarState(services, instanceId, channelId, messageId, false);
371562
371919
  return c.json({
371563
371920
  success: true,
371564
371921
  data: { messageId, starred: false }
@@ -372024,15 +372381,345 @@ var init_providers4 = __esm(() => {
372024
372381
  });
372025
372382
  });
372026
372383
 
372384
+ // ../api/src/services/scheduled-messages.ts
372385
+ function accumulate(total, pass) {
372386
+ total.scanned += pass.scanned;
372387
+ total.sent += pass.sent;
372388
+ total.failed += pass.failed;
372389
+ }
372390
+ function parseOutgoingContent(raw2, scheduledMessageId) {
372391
+ if (!raw2 || typeof raw2 !== "object" || Array.isArray(raw2)) {
372392
+ throw callerError(`Scheduled message ${scheduledMessageId} has non-object content`);
372393
+ }
372394
+ const type = raw2.type;
372395
+ if (typeof type !== "string" || type.length === 0) {
372396
+ throw callerError(`Scheduled message ${scheduledMessageId} has content without a 'type' discriminant`);
372397
+ }
372398
+ return raw2;
372399
+ }
372400
+ function callerError(message2) {
372401
+ return new OmniError({ code: ERROR_CODES.VALIDATION, message: message2, recoverable: false });
372402
+ }
372403
+
372404
+ class ScheduledMessageService {
372405
+ db;
372406
+ resolvePlugin;
372407
+ logger;
372408
+ authPlaneDb;
372409
+ warnedMissingAuthPlane = false;
372410
+ constructor(db2, resolvePlugin, logger7) {
372411
+ this.db = db2;
372412
+ this.resolvePlugin = resolvePlugin;
372413
+ this.logger = logger7 ?? createLogger("services:scheduled-messages");
372414
+ }
372415
+ async schedule(input) {
372416
+ if (input.sendAt.getTime() <= Date.now()) {
372417
+ throw callerError(`sendAt must be in the future (got ${input.sendAt.toISOString()})`);
372418
+ }
372419
+ parseOutgoingContent(input.content, "(new)");
372420
+ const plugin10 = await this.resolvePlugin(input.instanceId);
372421
+ if (!plugin10) {
372422
+ throw new Error(`No channel plugin for instance ${input.instanceId}`);
372423
+ }
372424
+ const native = plugin10.capabilities.canScheduleMessage === true && typeof plugin10.scheduleMessage === "function";
372425
+ const deliveryMode = native ? "platform" : "local";
372426
+ const maxAhead = plugin10.capabilities.maxScheduleAheadMs;
372427
+ if (native && typeof maxAhead === "number" && input.sendAt.getTime() - Date.now() > maxAhead) {
372428
+ throw callerError(`sendAt exceeds what ${plugin10.id} accepts natively (${Math.round(maxAhead / 86400000)} days ahead).`);
372429
+ }
372430
+ let externalScheduledId;
372431
+ if (native) {
372432
+ externalScheduledId = await plugin10.scheduleMessage?.(input.instanceId, this.toOutgoingMessage(input), input.sendAt);
372433
+ }
372434
+ try {
372435
+ const [row] = await this.db.insert(scheduledMessages).values({
372436
+ instanceId: input.instanceId,
372437
+ chatExternalId: input.chatExternalId,
372438
+ threadExternalId: input.threadExternalId,
372439
+ isThreadBroadcast: input.isThreadBroadcast ?? false,
372440
+ content: input.content,
372441
+ sendAt: input.sendAt,
372442
+ deliveryMode,
372443
+ status: "pending",
372444
+ externalScheduledId,
372445
+ createdByAgentId: input.createdByAgentId
372446
+ }).returning();
372447
+ if (!row)
372448
+ throw new Error("Failed to persist scheduled message");
372449
+ return row;
372450
+ } catch (error3) {
372451
+ if (native && externalScheduledId) {
372452
+ try {
372453
+ await plugin10.cancelScheduledMessage?.(input.instanceId, input.chatExternalId, externalScheduledId);
372454
+ } catch (cancelError) {
372455
+ this.logger.warn("Failed to roll back native schedule after a row-insert failure", {
372456
+ instanceId: input.instanceId,
372457
+ externalScheduledId,
372458
+ error: cancelError instanceof Error ? cancelError.message : String(cancelError)
372459
+ });
372460
+ }
372461
+ }
372462
+ throw error3;
372463
+ }
372464
+ }
372465
+ async cancel(id) {
372466
+ const row = await this.getById(id);
372467
+ if (!row)
372468
+ return null;
372469
+ if (row.status !== "pending")
372470
+ return row;
372471
+ if (row.deliveryMode === "platform" && row.externalScheduledId) {
372472
+ const plugin10 = await this.resolvePlugin(row.instanceId);
372473
+ try {
372474
+ await plugin10?.cancelScheduledMessage?.(row.instanceId, row.chatExternalId, row.externalScheduledId);
372475
+ } catch (error3) {
372476
+ this.logger.warn("Platform refused the cancel; marking canceled locally anyway", {
372477
+ scheduledMessageId: id,
372478
+ error: error3 instanceof Error ? error3.message : String(error3)
372479
+ });
372480
+ }
372481
+ }
372482
+ const [updated] = await this.db.update(scheduledMessages).set({ status: "canceled", canceledAt: new Date, updatedAt: new Date }).where(and2(eq(scheduledMessages.id, id), eq(scheduledMessages.status, "pending"))).returning();
372483
+ return updated ?? row;
372484
+ }
372485
+ async getById(id) {
372486
+ const [row] = await this.db.select().from(scheduledMessages).where(eq(scheduledMessages.id, id)).limit(1);
372487
+ return row ?? null;
372488
+ }
372489
+ async listPending(instanceId, limit2 = 100) {
372490
+ return this.db.select().from(scheduledMessages).where(and2(eq(scheduledMessages.instanceId, instanceId), eq(scheduledMessages.status, "pending"))).orderBy(asc(scheduledMessages.sendAt)).limit(limit2);
372491
+ }
372492
+ setAuthPlane(db2) {
372493
+ this.authPlaneDb = db2;
372494
+ }
372495
+ async sweep() {
372496
+ if (!isMultitenancyEnabled()) {
372497
+ return this.sweepWorld({ kind: "all" });
372498
+ }
372499
+ const totals = { scanned: 0, sent: 0, failed: 0 };
372500
+ if (this.authPlaneDb) {
372501
+ for (const tenantId of await enumerateActiveWorkTenants(this.authPlaneDb)) {
372502
+ try {
372503
+ accumulate(totals, await this.sweepWorld({ kind: "tenant", tenantId }));
372504
+ } catch (error3) {
372505
+ this.logger.warn("scheduled-message sweeper: tenant pass failed", {
372506
+ tenantId,
372507
+ error: error3 instanceof Error ? error3.message : String(error3)
372508
+ });
372509
+ }
372510
+ }
372511
+ } else if (!this.warnedMissingAuthPlane) {
372512
+ this.warnedMissingAuthPlane = true;
372513
+ this.logger.warn("scheduled-message sweeper: multitenancy is enabled but no auth-plane connection was injected \u2014 tenant rows will not be swept");
372514
+ }
372515
+ if (resolveEnforcementMode(process.env) !== "enforced") {
372516
+ accumulate(totals, await this.sweepWorld({ kind: "legacy-rows" }));
372517
+ }
372518
+ return totals;
372519
+ }
372520
+ worldPredicate(world) {
372521
+ if (world.kind === "tenant") {
372522
+ return [
372523
+ sql`EXISTS (SELECT 1 FROM ${instances} WHERE ${instances.id} = ${scheduledMessages.instanceId} AND ${instances.tenantId} = ${world.tenantId})`
372524
+ ];
372525
+ }
372526
+ if (world.kind === "legacy-rows") {
372527
+ return [
372528
+ sql`EXISTS (SELECT 1 FROM ${instances} WHERE ${instances.id} = ${scheduledMessages.instanceId} AND ${instances.tenantId} IS NULL)`
372529
+ ];
372530
+ }
372531
+ return [];
372532
+ }
372533
+ async sweepWorld(world) {
372534
+ const stats = { scanned: 0, sent: 0, failed: 0 };
372535
+ const now = new Date;
372536
+ const claimCutoff = new Date(now.getTime() - CLAIM_LEASE_MS);
372537
+ const due = await this.db.transaction(async (tx) => {
372538
+ const locked = await tx.select({ id: scheduledMessages.id }).from(scheduledMessages).where(and2(eq(scheduledMessages.deliveryMode, "local"), lte(scheduledMessages.sendAt, now), or2(eq(scheduledMessages.status, "pending"), and2(eq(scheduledMessages.status, "sending"), lte(scheduledMessages.updatedAt, claimCutoff))), ...this.worldPredicate(world))).orderBy(asc(scheduledMessages.sendAt)).limit(MAX_PER_TICK).for("update", { skipLocked: true });
372539
+ if (locked.length === 0)
372540
+ return [];
372541
+ return tx.update(scheduledMessages).set({ status: "sending", updatedAt: now }).where(inArray(scheduledMessages.id, locked.map((r) => r.id))).returning();
372542
+ });
372543
+ stats.scanned = due.length;
372544
+ for (const row of due) {
372545
+ try {
372546
+ const plugin10 = await this.resolvePlugin(row.instanceId);
372547
+ if (!plugin10)
372548
+ throw new Error(`No channel plugin for instance ${row.instanceId}`);
372549
+ const result = await plugin10.sendMessage(row.instanceId, {
372550
+ to: row.chatExternalId,
372551
+ threadId: row.threadExternalId ?? undefined,
372552
+ content: parseOutgoingContent(row.content, row.id),
372553
+ metadata: { isThreadBroadcast: row.isThreadBroadcast }
372554
+ });
372555
+ if (!result.success) {
372556
+ throw new Error(result.error ?? "sendMessage reported failure without an error");
372557
+ }
372558
+ await this.db.update(scheduledMessages).set({
372559
+ status: "sent",
372560
+ sentAt: new Date,
372561
+ sentExternalId: result.messageId,
372562
+ attemptCount: row.attemptCount + 1,
372563
+ updatedAt: new Date
372564
+ }).where(and2(eq(scheduledMessages.id, row.id), eq(scheduledMessages.status, "sending")));
372565
+ stats.sent++;
372566
+ } catch (error3) {
372567
+ const message2 = error3 instanceof Error ? error3.message : String(error3);
372568
+ const attempts = row.attemptCount + 1;
372569
+ const exhausted = attempts >= MAX_ATTEMPTS;
372570
+ await this.db.update(scheduledMessages).set({
372571
+ status: exhausted ? "failed" : "pending",
372572
+ failedAt: exhausted ? new Date : null,
372573
+ lastError: message2,
372574
+ attemptCount: attempts,
372575
+ updatedAt: new Date
372576
+ }).where(and2(eq(scheduledMessages.id, row.id), eq(scheduledMessages.status, "sending")));
372577
+ stats.failed++;
372578
+ this.logger.warn("Scheduled message delivery failed", {
372579
+ scheduledMessageId: row.id,
372580
+ attempts,
372581
+ exhausted,
372582
+ error: message2
372583
+ });
372584
+ }
372585
+ }
372586
+ return stats;
372587
+ }
372588
+ async pruneTerminal(olderThan) {
372589
+ const result = await this.db.delete(scheduledMessages).where(and2(sql`${scheduledMessages.status} IN ('sent', 'canceled', 'failed')`, lte(scheduledMessages.updatedAt, olderThan))).returning({ id: scheduledMessages.id });
372590
+ return result.length;
372591
+ }
372592
+ toOutgoingMessage(input) {
372593
+ return {
372594
+ to: input.chatExternalId,
372595
+ threadId: input.threadExternalId,
372596
+ content: parseOutgoingContent(input.content, "(new)"),
372597
+ metadata: { isThreadBroadcast: input.isThreadBroadcast ?? false }
372598
+ };
372599
+ }
372600
+ }
372601
+ function createPluginResolver(db2, getPlugin2) {
372602
+ return async (instanceId) => {
372603
+ const [row] = await db2.select({ channel: instances.channel }).from(instances).where(eq(instances.id, instanceId)).limit(1);
372604
+ if (!row)
372605
+ return null;
372606
+ return getPlugin2(row.channel) ?? null;
372607
+ };
372608
+ }
372609
+ var MAX_PER_TICK = 50, MAX_ATTEMPTS = 3, CLAIM_LEASE_MS;
372610
+ var init_scheduled_messages = __esm(() => {
372611
+ init_src();
372612
+ init_src5();
372613
+ init_drizzle_orm();
372614
+ init_periodic_tenant_work();
372615
+ CLAIM_LEASE_MS = 5 * 60000;
372616
+ });
372617
+
372618
+ // ../api/src/routes/v2/scheduled-messages.ts
372619
+ function getService(c) {
372620
+ const db2 = c.get("db");
372621
+ const registry5 = c.get("channelRegistry");
372622
+ if (!registry5) {
372623
+ throw new OmniError({
372624
+ code: ERROR_CODES.CHANNEL_NOT_CONNECTED,
372625
+ message: "Channel registry not available",
372626
+ recoverable: false
372627
+ });
372628
+ }
372629
+ return new ScheduledMessageService(db2, createPluginResolver(db2, (channel5) => registry5.get(channel5)));
372630
+ }
372631
+ var scheduledMessagesRoutes, scheduleSchema, listQuerySchema17;
372632
+ var init_scheduled_messages2 = __esm(() => {
372633
+ init_dist6();
372634
+ init_src();
372635
+ init_dist2();
372636
+ init_zod();
372637
+ init_scheduled_messages();
372638
+ scheduledMessagesRoutes = new Hono2;
372639
+ scheduleSchema = exports_external.object({
372640
+ instanceId: exports_external.string().uuid(),
372641
+ chatId: exports_external.string().min(1).describe("Platform chat/channel id (e.g. Slack C\u2026/D\u2026)"),
372642
+ content: exports_external.record(exports_external.unknown()).describe("OutgoingContent, e.g. { type: 'text', text: 'oi' }"),
372643
+ sendAt: exports_external.string().datetime().describe("ISO-8601 delivery time (UTC-aware)"),
372644
+ threadId: exports_external.string().optional().describe("Post into this thread (Slack thread_ts)"),
372645
+ isThreadBroadcast: exports_external.boolean().optional().describe("Also surface it in the channel (Slack reply_broadcast)")
372646
+ });
372647
+ listQuerySchema17 = exports_external.object({
372648
+ instanceId: exports_external.string().uuid(),
372649
+ limit: exports_external.coerce.number().int().min(1).max(500).default(100)
372650
+ });
372651
+ scheduledMessagesRoutes.post("/", zValidator("json", scheduleSchema), async (c) => {
372652
+ const body = c.req.valid("json");
372653
+ const sendAt = new Date(body.sendAt);
372654
+ try {
372655
+ const row = await getService(c).schedule({
372656
+ instanceId: body.instanceId,
372657
+ chatExternalId: body.chatId,
372658
+ content: body.content,
372659
+ sendAt,
372660
+ threadExternalId: body.threadId,
372661
+ isThreadBroadcast: body.isThreadBroadcast
372662
+ });
372663
+ return c.json({ success: true, data: row }, 201);
372664
+ } catch (error3) {
372665
+ if (error3 instanceof OmniError)
372666
+ throw error3;
372667
+ throw new OmniError({
372668
+ code: ERROR_CODES.UNKNOWN,
372669
+ message: error3 instanceof Error ? error3.message : String(error3),
372670
+ recoverable: false,
372671
+ cause: error3 instanceof Error ? error3 : undefined
372672
+ });
372673
+ }
372674
+ });
372675
+ scheduledMessagesRoutes.get("/", zValidator("query", listQuerySchema17), async (c) => {
372676
+ const { instanceId, limit: limit2 } = c.req.valid("query");
372677
+ const rows = await getService(c).listPending(instanceId, limit2);
372678
+ return c.json({
372679
+ data: rows,
372680
+ meta: {
372681
+ count: rows.length,
372682
+ scope: "scheduled-via-omni"
372683
+ }
372684
+ });
372685
+ });
372686
+ scheduledMessagesRoutes.get("/:id", async (c) => {
372687
+ const row = await getService(c).getById(c.req.param("id"));
372688
+ if (!row) {
372689
+ throw new OmniError({
372690
+ code: ERROR_CODES.NOT_FOUND,
372691
+ message: `Scheduled message ${c.req.param("id")} not found`,
372692
+ recoverable: false
372693
+ });
372694
+ }
372695
+ return c.json({ data: row });
372696
+ });
372697
+ scheduledMessagesRoutes.delete("/:id", async (c) => {
372698
+ const id = c.req.param("id");
372699
+ const row = await getService(c).cancel(id);
372700
+ if (!row) {
372701
+ throw new OmniError({
372702
+ code: ERROR_CODES.NOT_FOUND,
372703
+ message: `Scheduled message ${id} not found`,
372704
+ recoverable: false
372705
+ });
372706
+ }
372707
+ return c.json({
372708
+ success: true,
372709
+ data: { id: row.id, status: row.status, canceledAt: row.canceledAt }
372710
+ });
372711
+ });
372712
+ });
372713
+
372027
372714
  // ../api/src/routes/v2/settings.ts
372028
- var settingsRoutes, listQuerySchema17, setSettingSchema, bulkUpdateSchema, historyQuerySchema;
372715
+ var settingsRoutes, listQuerySchema18, setSettingSchema, bulkUpdateSchema, historyQuerySchema;
372029
372716
  var init_settings3 = __esm(() => {
372030
372717
  init_dist6();
372031
372718
  init_dist2();
372032
372719
  init_zod();
372033
372720
  init_date_query();
372034
372721
  settingsRoutes = new Hono2;
372035
- listQuerySchema17 = exports_external.object({
372722
+ listQuerySchema18 = exports_external.object({
372036
372723
  category: exports_external.string().optional()
372037
372724
  });
372038
372725
  setSettingSchema = exports_external.object({
@@ -372047,7 +372734,7 @@ var init_settings3 = __esm(() => {
372047
372734
  limit: exports_external.coerce.number().int().min(1).max(100).default(50),
372048
372735
  since: optionalDateParam("since")
372049
372736
  });
372050
- settingsRoutes.get("/", zValidator("query", listQuerySchema17), async (c) => {
372737
+ settingsRoutes.get("/", zValidator("query", listQuerySchema18), async (c) => {
372051
372738
  const { category } = c.req.valid("query");
372052
372739
  const services = c.get("services");
372053
372740
  const settings = await services.settings.list(category);
@@ -372122,6 +372809,116 @@ var init_settings3 = __esm(() => {
372122
372809
  });
372123
372810
  });
372124
372811
 
372812
+ // ../api/src/routes/v2/slack.ts
372813
+ function checkInstanceAccess3(apiKey, instanceId) {
372814
+ if (apiKey && !ApiKeyService.instanceAllowed(apiKey.instanceIds, instanceId)) {
372815
+ throw new OmniError({
372816
+ code: ERROR_CODES.FORBIDDEN,
372817
+ message: "API key does not have access to this instance",
372818
+ context: { instanceId },
372819
+ recoverable: false
372820
+ });
372821
+ }
372822
+ }
372823
+ async function getSlackPlugin(c, instanceId) {
372824
+ checkInstanceAccess3(c.get("apiKey"), instanceId);
372825
+ const services = c.get("services");
372826
+ const registry5 = c.get("channelRegistry");
372827
+ const instance4 = await services.instances.getById(instanceId);
372828
+ if (instance4.channel !== "slack") {
372829
+ throw new OmniError({
372830
+ code: ERROR_CODES.CAPABILITY_NOT_SUPPORTED,
372831
+ message: `Instance ${instanceId} is a ${instance4.channel} instance; these endpoints are Slack-only`,
372832
+ context: { channelType: instance4.channel },
372833
+ recoverable: false
372834
+ });
372835
+ }
372836
+ if (!registry5) {
372837
+ throw new OmniError({
372838
+ code: ERROR_CODES.CHANNEL_NOT_CONNECTED,
372839
+ message: "Channel registry not available",
372840
+ recoverable: false
372841
+ });
372842
+ }
372843
+ const plugin10 = registry5.get("slack");
372844
+ if (!plugin10) {
372845
+ throw new OmniError({
372846
+ code: ERROR_CODES.CHANNEL_NOT_CONNECTED,
372847
+ message: "Slack plugin not registered",
372848
+ recoverable: false
372849
+ });
372850
+ }
372851
+ return plugin10;
372852
+ }
372853
+ var slackRoutes, openDmSchema, searchSchema;
372854
+ var init_slack = __esm(() => {
372855
+ init_dist6();
372856
+ init_src();
372857
+ init_dist2();
372858
+ init_zod();
372859
+ init_api_keys();
372860
+ slackRoutes = new Hono2;
372861
+ openDmSchema = exports_external.object({
372862
+ instanceId: exports_external.string().uuid(),
372863
+ userId: exports_external.string().min(1).describe("Slack user id (U\u2026) to open a DM with")
372864
+ });
372865
+ searchSchema = exports_external.object({
372866
+ instanceId: exports_external.string().uuid(),
372867
+ query: exports_external.string().min(1),
372868
+ count: exports_external.coerce.number().int().min(1).max(100).default(20),
372869
+ page: exports_external.coerce.number().int().min(1).default(1)
372870
+ });
372871
+ slackRoutes.post("/dm/open", zValidator("json", openDmSchema), async (c) => {
372872
+ const { instanceId, userId } = c.req.valid("json");
372873
+ const plugin10 = await getSlackPlugin(c, instanceId);
372874
+ if (typeof plugin10.openDirectMessage !== "function") {
372875
+ throw new OmniError({
372876
+ code: ERROR_CODES.CAPABILITY_NOT_SUPPORTED,
372877
+ message: "Slack plugin does not implement openDirectMessage",
372878
+ recoverable: false
372879
+ });
372880
+ }
372881
+ try {
372882
+ const channelId = await plugin10.openDirectMessage(instanceId, userId);
372883
+ return c.json({ success: true, data: { userId, channelId } });
372884
+ } catch (error3) {
372885
+ throw new OmniError({
372886
+ code: ERROR_CODES.CHANNEL_SEND_FAILED,
372887
+ message: error3 instanceof Error ? error3.message : String(error3),
372888
+ recoverable: true
372889
+ });
372890
+ }
372891
+ });
372892
+ slackRoutes.get("/search", zValidator("query", searchSchema), async (c) => {
372893
+ const { instanceId, query, count: count3, page } = c.req.valid("query");
372894
+ const plugin10 = await getSlackPlugin(c, instanceId);
372895
+ if (typeof plugin10.searchMessages !== "function") {
372896
+ throw new OmniError({
372897
+ code: ERROR_CODES.CAPABILITY_NOT_SUPPORTED,
372898
+ message: "Slack plugin does not implement searchMessages",
372899
+ recoverable: false
372900
+ });
372901
+ }
372902
+ try {
372903
+ const matches = await plugin10.searchMessages(instanceId, query, { count: count3, page });
372904
+ return c.json({
372905
+ data: matches,
372906
+ meta: {
372907
+ count: matches.length,
372908
+ page,
372909
+ scope: "authorizing-user"
372910
+ }
372911
+ });
372912
+ } catch (error3) {
372913
+ throw new OmniError({
372914
+ code: ERROR_CODES.CAPABILITY_NOT_SUPPORTED,
372915
+ message: error3 instanceof Error ? error3.message : String(error3),
372916
+ recoverable: false
372917
+ });
372918
+ }
372919
+ });
372920
+ });
372921
+
372125
372922
  // ../api/src/routes/v2/templates.ts
372126
372923
  function jsonError(message2, code = "BAD_REQUEST", _status = 400) {
372127
372924
  return { error: { code, message: message2 } };
@@ -372146,7 +372943,7 @@ function makeClient(cfg) {
372146
372943
  function variablesToBodyParameters(variables) {
372147
372944
  return Object.entries(variables).map(([k2, v2]) => [Number.parseInt(k2, 10), v2]).filter(([n2]) => Number.isFinite(n2) && n2 >= 1).sort((a, b3) => a[0] - b3[0]).map(([, v2]) => v2);
372148
372945
  }
372149
- var log118, templatesRoutes, instanceAccess3, idParamSchema6, templateIdParamSchema, templateNameParamSchema, listQuerySchema18, createBodySchema, sendTestBodySchema, sendByNameBodySchema;
372946
+ var log118, templatesRoutes, instanceAccess3, idParamSchema6, templateIdParamSchema, templateNameParamSchema, listQuerySchema19, createBodySchema, sendTestBodySchema, sendByNameBodySchema;
372150
372947
  var init_templates3 = __esm(() => {
372151
372948
  init_dist6();
372152
372949
  init_src6();
@@ -372171,7 +372968,7 @@ var init_templates3 = __esm(() => {
372171
372968
  id: exports_external.string().uuid(),
372172
372969
  templateName: exports_external.string().min(1)
372173
372970
  });
372174
- listQuerySchema18 = exports_external.object({
372971
+ listQuerySchema19 = exports_external.object({
372175
372972
  status: MetaTemplateStatusSchema.optional(),
372176
372973
  category: MetaTemplateCategorySchema.optional(),
372177
372974
  language: exports_external.string().min(2).optional(),
@@ -372199,7 +372996,7 @@ var init_templates3 = __esm(() => {
372199
372996
  filename: exports_external.string().optional()
372200
372997
  }).optional()
372201
372998
  });
372202
- templatesRoutes.get("/instances/:id/whatsapp-templates", instanceAccess3, zValidator("param", idParamSchema6), zValidator("query", listQuerySchema18), async (c) => {
372999
+ templatesRoutes.get("/instances/:id/whatsapp-templates", instanceAccess3, zValidator("param", idParamSchema6), zValidator("query", listQuerySchema19), async (c) => {
372203
373000
  const { id: instanceId } = c.req.valid("param");
372204
373001
  const { status, category, language, sync } = c.req.valid("query");
372205
373002
  const services = c.get("services");
@@ -372489,7 +373286,7 @@ async function resolveOpenTurn(services, keyId, turnId, headerInstanceId, header
372489
373286
  }
372490
373287
  return null;
372491
373288
  }
372492
- var turnsRoutes, closeTurnSchema, listQuerySchema19, adminForceCloseSchema, bulkCloseSchema;
373289
+ var turnsRoutes, closeTurnSchema, listQuerySchema20, adminForceCloseSchema, bulkCloseSchema;
372493
373290
  var init_turns2 = __esm(() => {
372494
373291
  init_dist6();
372495
373292
  init_dist2();
@@ -372552,7 +373349,7 @@ var init_turns2 = __esm(() => {
372552
373349
  }
372553
373350
  });
372554
373351
  });
372555
- listQuerySchema19 = exports_external.object({
373352
+ listQuerySchema20 = exports_external.object({
372556
373353
  status: exports_external.enum(["open", "done", "timeout"]).optional(),
372557
373354
  instanceId: exports_external.string().uuid().optional(),
372558
373355
  chatId: exports_external.string().optional(),
@@ -372567,7 +373364,7 @@ var init_turns2 = __esm(() => {
372567
373364
  confirm: exports_external.boolean(),
372568
373365
  reason: exports_external.string().optional()
372569
373366
  });
372570
- turnsRoutes.get("/", zValidator("query", listQuerySchema19), async (c) => {
373367
+ turnsRoutes.get("/", zValidator("query", listQuerySchema20), async (c) => {
372571
373368
  const denied = requireAdmin(c);
372572
373369
  if (denied)
372573
373370
  return denied;
@@ -372798,7 +373595,7 @@ var init_voice2 = __esm(() => {
372798
373595
  });
372799
373596
 
372800
373597
  // ../api/src/routes/v2/webhooks.ts
372801
- var webhooksRoutes, createWebhookSourceSchema, updateWebhookSourceSchema, listQuerySchema20, triggerEventSchema;
373598
+ var webhooksRoutes, createWebhookSourceSchema, updateWebhookSourceSchema, listQuerySchema21, triggerEventSchema;
372802
373599
  var init_webhooks3 = __esm(() => {
372803
373600
  init_dist6();
372804
373601
  init_dist2();
@@ -372812,10 +373609,10 @@ var init_webhooks3 = __esm(() => {
372812
373609
  enabled: exports_external.boolean().default(true).describe("Whether source is enabled")
372813
373610
  });
372814
373611
  updateWebhookSourceSchema = createWebhookSourceSchema.partial();
372815
- listQuerySchema20 = exports_external.object({
373612
+ listQuerySchema21 = exports_external.object({
372816
373613
  enabled: exports_external.coerce.boolean().optional()
372817
373614
  });
372818
- webhooksRoutes.get("/webhook-sources", zValidator("query", listQuerySchema20), async (c) => {
373615
+ webhooksRoutes.get("/webhook-sources", zValidator("query", listQuerySchema21), async (c) => {
372819
373616
  const { enabled } = c.req.valid("query");
372820
373617
  const services = c.get("services");
372821
373618
  const sources = await services.webhooks.list({ enabled });
@@ -373680,7 +374477,9 @@ var init_v2 = __esm(() => {
373680
374477
  init_persons3();
373681
374478
  init_processed_events();
373682
374479
  init_providers4();
374480
+ init_scheduled_messages2();
373683
374481
  init_settings3();
374482
+ init_slack();
373684
374483
  init_templates3();
373685
374484
  init_trust();
373686
374485
  init_turns2();
@@ -373697,6 +374496,8 @@ var init_v2 = __esm(() => {
373697
374496
  v2Routes.route("/instances", instancesRoutes);
373698
374497
  v2Routes.route("/logs", logsRoutes);
373699
374498
  v2Routes.route("/messages", messagesRoutes);
374499
+ v2Routes.route("/scheduled-messages", scheduledMessagesRoutes);
374500
+ v2Routes.route("/slack", slackRoutes);
373700
374501
  v2Routes.route("/events", eventsRoutes);
373701
374502
  v2Routes.route("/journeys", journeysRoutes);
373702
374503
  v2Routes.route("/persons", personsRoutes);
@@ -374813,6 +375614,27 @@ async function downloadMediaFromUrl(ctx, instanceId, messageId, mediaUrl, mimeTy
374813
375614
  return null;
374814
375615
  }
374815
375616
  }
375617
+ async function downloadMediaViaPlugin(ctx, instanceId, messageId, channelType, mediaRef, fallbackMimeType, platformTimestamp, trustedTenantId) {
375618
+ const plugin10 = await (ctx.resolveChannelPlugin ?? getPlugin)(channelType);
375619
+ if (!plugin10?.downloadInboundMedia) {
375620
+ log122.warn("Channel plugin cannot materialize deferred media", { channelType, messageId });
375621
+ return null;
375622
+ }
375623
+ try {
375624
+ const { buffer: buffer3, mimeType } = pluginMediaSchema.parse(await plugin10.downloadInboundMedia(instanceId, mediaRef));
375625
+ const result = await ctx.mediaStorage.storeFromBuffer(instanceId, messageId, buffer3, mimeType || fallbackMimeType, platformTimestamp, trustedTenantId);
375626
+ await runMediaDb(ctx, trustedTenantId, () => ctx.mediaStorage.updateMessageLocalPath(messageId, result.localPath));
375627
+ log122.debug("Downloaded inbound media via plugin", { messageId, channelType, filePath: result.localPath });
375628
+ return result.localPath;
375629
+ } catch (error3) {
375630
+ log122.error("Failed to download inbound media via plugin", {
375631
+ error: String(error3),
375632
+ channelType,
375633
+ messageId
375634
+ });
375635
+ return null;
375636
+ }
375637
+ }
374816
375638
  async function resolveMediaPath2(ctx, instanceId, chatId, externalId, content, mimeType, channelType, trustedTenantId) {
374817
375639
  const maxWaitMs = 5000;
374818
375640
  const pollMs = 250;
@@ -374842,6 +375664,11 @@ async function resolveMediaPath2(ctx, instanceId, chatId, externalId, content, m
374842
375664
  if (!filePath)
374843
375665
  return null;
374844
375666
  }
375667
+ if (!filePath && content.mediaId && channelType) {
375668
+ filePath = await downloadMediaViaPlugin(ctx, instanceId, message2.id, channelType, content.mediaId, mimeType, message2.platformTimestamp ?? undefined, trustedTenantId);
375669
+ if (!filePath)
375670
+ return null;
375671
+ }
374845
375672
  if (!filePath) {
374846
375673
  log122.debug("No media file path available", { externalId });
374847
375674
  return null;
@@ -375139,17 +375966,23 @@ async function setupMediaProcessor(eventBus, db2, services) {
375139
375966
  });
375140
375967
  log122.info("Media processor initialized");
375141
375968
  }
375142
- var log122, PROCESSABLE_MEDIA_TYPES;
375969
+ var log122, PROCESSABLE_MEDIA_TYPES, pluginMediaSchema;
375143
375970
  var init_media_processor = __esm(() => {
375144
375971
  init_src();
375145
375972
  init_src5();
375146
375973
  init_src8();
375147
375974
  init_drizzle_orm();
375975
+ init_zod();
375148
375976
  init_media_storage();
375149
375977
  init_tenant_scope();
375150
375978
  init_worker_tenant_context();
375979
+ init_loader2();
375151
375980
  log122 = createLogger("media-processor");
375152
375981
  PROCESSABLE_MEDIA_TYPES = new Set(["audio", "image", "document", "video"]);
375982
+ pluginMediaSchema = exports_external.object({
375983
+ buffer: exports_external.instanceof(Buffer),
375984
+ mimeType: exports_external.string().min(1).optional()
375985
+ });
375153
375986
  });
375154
375987
 
375155
375988
  // ../api/src/tenancy/inflight-revocation.ts
@@ -375202,29 +376035,6 @@ var init_inflight_revocation = __esm(() => {
375202
376035
  };
375203
376036
  });
375204
376037
 
375205
- // ../api/src/utils/phone.ts
375206
- function isValidE164Phone(phone) {
375207
- const bare = phone.replace(/^\+/, "");
375208
- if (!/^\d+$/.test(bare))
375209
- return false;
375210
- return bare.length >= 7 && bare.length <= 15;
375211
- }
375212
- function isLidFormat(platformUserId) {
375213
- const bare = platformUserId.split("@")[0] || platformUserId;
375214
- return /^\d{14,}$/.test(bare);
375215
- }
375216
- function validateContactPhone(phone, platformUserId) {
375217
- if (!phone)
375218
- return;
375219
- if (!isValidE164Phone(phone))
375220
- return;
375221
- const barePhone = phone.replace(/^\+/, "");
375222
- const barePuid = platformUserId.split("@")[0] || platformUserId;
375223
- if (isLidFormat(platformUserId) && barePhone === barePuid)
375224
- return;
375225
- return phone;
375226
- }
375227
-
375228
376038
  // ../api/src/plugins/sync-worker.ts
375229
376039
  class RateLimiter {
375230
376040
  lastRequest = 0;
@@ -376796,6 +377606,11 @@ async function withCronMonitor(slug, cron2, checkinMargin, maxRuntime, handler)
376796
377606
  await handler();
376797
377607
  }
376798
377608
  }
377609
+ function createScheduledMessageSweeper(services, channelRegistry2) {
377610
+ const sweeper2 = new ScheduledMessageService(services.db, createPluginResolver(services.db, (channel5) => channelRegistry2.get(channel5) ?? undefined));
377611
+ sweeper2.setAuthPlane(services.authPlane.db);
377612
+ return sweeper2;
377613
+ }
376799
377614
  function setupScheduler(services, channelRegistry2) {
376800
377615
  const scheduler = getScheduler();
376801
377616
  scheduler.register({
@@ -376924,6 +377739,29 @@ function setupScheduler(services, channelRegistry2) {
376924
377739
  });
376925
377740
  }
376926
377741
  });
377742
+ if (channelRegistry2) {
377743
+ const scheduledMessages2 = createScheduledMessageSweeper(services, channelRegistry2);
377744
+ scheduler.register({
377745
+ name: "scheduled-message-sweeper",
377746
+ cron: "*/15 * * * * *",
377747
+ runOnStart: false,
377748
+ handler: async () => {
377749
+ await withCronMonitor("scheduled-message-sweeper", "*/15 * * * * *", 1, 1, async () => {
377750
+ const startTime3 = Date.now();
377751
+ try {
377752
+ const stats = await scheduledMessages2.sweep();
377753
+ recordScheduledJob("scheduled-message-sweeper", "success", (Date.now() - startTime3) / 1000);
377754
+ if (stats.scanned > 0) {
377755
+ log127.debug("Scheduled-message sweep tick", { ...stats });
377756
+ }
377757
+ } catch (err2) {
377758
+ recordScheduledJob("scheduled-message-sweeper", "failure", (Date.now() - startTime3) / 1000);
377759
+ throw err2;
377760
+ }
377761
+ });
377762
+ }
377763
+ });
377764
+ }
376927
377765
  scheduler.register({
376928
377766
  name: "unread-count-refresh",
376929
377767
  cron: CronExpressions.EVERY_HOUR,
@@ -376975,6 +377813,7 @@ var init_scheduler2 = __esm(() => {
376975
377813
  init_src();
376976
377814
  init_esm5();
376977
377815
  init_sentry_scrub();
377816
+ init_scheduled_messages();
376978
377817
  init_periodic_tenant_work();
376979
377818
  log127 = createLogger("scheduler:setup");
376980
377819
  });
@@ -382667,6 +383506,7 @@ var src_default2 = plugin3;
382667
383506
 
382668
383507
  // ../channel-hermes/src/plugin.ts
382669
383508
  init_src2();
383509
+ init_src();
382670
383510
 
382671
383511
  // ../channel-hermes/src/capabilities.ts
382672
383512
  init_src2();
@@ -383121,6 +383961,33 @@ async function sendTemplate(client, to, opts, replyTo) {
383121
383961
  payload.context = { message_id: replyTo };
383122
383962
  return client.sendMessage(payload);
383123
383963
  }
383964
+ // ../channel-hermes/src/senders/interactive.ts
383965
+ async function sendPlannedInteractive(client, to, interactive, replyTo) {
383966
+ const payload = {
383967
+ to: toHermesPhone(to),
383968
+ recipient_type: "individual",
383969
+ type: "interactive",
383970
+ interactive
383971
+ };
383972
+ if (replyTo)
383973
+ payload.context = { message_id: replyTo };
383974
+ return client.sendMessage(payload);
383975
+ }
383976
+ async function sendLocationRequest(client, to, bodyText, replyTo) {
383977
+ const payload = {
383978
+ to: toHermesPhone(to),
383979
+ recipient_type: "individual",
383980
+ type: "interactive",
383981
+ interactive: {
383982
+ type: "location_request_message",
383983
+ body: { text: bodyText },
383984
+ action: { name: "send_location" }
383985
+ }
383986
+ };
383987
+ if (replyTo)
383988
+ payload.context = { message_id: replyTo };
383989
+ return client.sendMessage(payload);
383990
+ }
383124
383991
  // ../channel-hermes/src/plugin.ts
383125
383992
  var HERMES_MEDIA_TYPES = new Set(["image", "audio", "video", "document", "sticker"]);
383126
383993
  var downloadGuard2 = createDownloadGuard();
@@ -383200,7 +384067,7 @@ class HermesPlugin extends BaseChannelPlugin {
383200
384067
  if (correlationId)
383201
384068
  this.captureT10(correlationId);
383202
384069
  try {
383203
- const dispatched = await dispatchOutbound(state, message2);
384070
+ const dispatched = await dispatchOutbound(state, message2, this.logger);
383204
384071
  if (!dispatched.ok) {
383205
384072
  return { success: false, error: dispatched.error, retryable: false, timestamp: Date.now() };
383206
384073
  }
@@ -383445,11 +384312,14 @@ class HermesPlugin extends BaseChannelPlugin {
383445
384312
  }
383446
384313
  }
383447
384314
  }
383448
- async function dispatchOutbound(state, message2) {
384315
+ async function dispatchOutbound(state, message2, logger5) {
383449
384316
  const { client } = state;
383450
384317
  const { content, to, replyTo } = message2;
383451
384318
  if (content.type === "text") {
383452
- return { ok: true, response: await sendText2(client, to, content.text ?? "", replyTo) };
384319
+ return dispatchOutboundText(client, message2, logger5);
384320
+ }
384321
+ if (content.type === "location_request") {
384322
+ return { ok: true, response: await sendLocationRequest(client, to, resolveOutboundText(message2), replyTo) };
383453
384323
  }
383454
384324
  if (HERMES_MEDIA_TYPES.has(content.type)) {
383455
384325
  return dispatchOutboundMedia(client, message2);
@@ -383476,6 +384346,30 @@ async function dispatchOutbound(state, message2) {
383476
384346
  }
383477
384347
  return { ok: false, error: `Unsupported content.type=${content.type} for hermes` };
383478
384348
  }
384349
+ function resolveOutboundText(message2) {
384350
+ const formatMode = message2.metadata?.messageFormatMode ?? "convert";
384351
+ const text = message2.content.text ?? "";
384352
+ return formatMode === "passthrough" ? text : markdownToWhatsApp(text);
384353
+ }
384354
+ async function dispatchOutboundText(client, message2, logger5) {
384355
+ const { content, to, replyTo } = message2;
384356
+ const formatted = resolveOutboundText(message2);
384357
+ if (!content.buttons?.length) {
384358
+ return { ok: true, response: await sendText2(client, to, formatted, replyTo) };
384359
+ }
384360
+ const plan = planInteractive(formatted, content.buttons, content.list?.buttonLabel ?? "Options", {
384361
+ sectionTitle: content.list?.sectionTitle,
384362
+ forceList: content.list?.forceList
384363
+ });
384364
+ if (plan.droppedRows > 0) {
384365
+ logger5?.warn("[hermes] interactive list capped at 10 rows \u2014 extra buttons dropped", {
384366
+ to,
384367
+ droppedRows: plan.droppedRows
384368
+ });
384369
+ }
384370
+ const response = plan.interactive ? await sendPlannedInteractive(client, to, plan.interactive, replyTo) : await sendText2(client, to, plan.body, replyTo);
384371
+ return { ok: true, response };
384372
+ }
383479
384373
  async function dispatchOutboundMedia(client, message2) {
383480
384374
  const { content, to, replyTo } = message2;
383481
384375
  const caption = content.caption ?? content.text;
@@ -383641,6 +384535,11 @@ var SLACK_CAPABILITIES = {
383641
384535
  canDeleteMessage: true,
383642
384536
  canReplyToMessage: true,
383643
384537
  canForwardMessage: false,
384538
+ canScheduleMessage: true,
384539
+ maxScheduleAheadMs: 120 * 24 * 60 * 60 * 1000,
384540
+ canGetPermalink: true,
384541
+ canPinMessage: true,
384542
+ canSearchMessages: false,
383644
384543
  canSendContact: false,
383645
384544
  canSendLocation: false,
383646
384545
  canSendSticker: false,
@@ -383694,7 +384593,17 @@ function resolveStreamThrottle(throttleMs) {
383694
384593
  // ../channel-slack/src/connection/bolt-client.ts
383695
384594
  init_types10();
383696
384595
  var import_bolt = __toESM(require_dist16(), 1);
384596
+ var import_web_api = __toESM(require_dist13(), 1);
383697
384597
  var HTTP_MAX_BODY_BYTES = 1024 * 1024;
384598
+ function buildActingClients(options, botClient) {
384599
+ if (options.authMode !== "user")
384600
+ return { actingClient: botClient };
384601
+ if (!options.userToken) {
384602
+ throw new SlackError(SlackErrorCode.CONNECTION_FAILED, "userToken is required when authMode is 'user'");
384603
+ }
384604
+ const userClient = new import_web_api.WebClient(options.userToken);
384605
+ return { actingClient: userClient, userClient };
384606
+ }
383698
384607
  function createBoltApp(options, logger5) {
383699
384608
  const mode = options.mode ?? "socket";
383700
384609
  if (mode === "http") {
@@ -383728,6 +384637,7 @@ function createSocketBoltApp(options, logger5) {
383728
384637
  return {
383729
384638
  app,
383730
384639
  client: app.client,
384640
+ ...buildActingClients(options, app.client),
383731
384641
  botToken: options.botToken,
383732
384642
  mode: "socket"
383733
384643
  };
@@ -383760,6 +384670,7 @@ function createHttpBoltApp(options, logger5) {
383760
384670
  return {
383761
384671
  app,
383762
384672
  client: app.client,
384673
+ ...buildActingClients(options, app.client),
383763
384674
  botToken: options.botToken,
383764
384675
  mode: "http",
383765
384676
  httpPort: options.httpPort,
@@ -383835,11 +384746,22 @@ async function startBoltConnection(connection, logger5) {
383835
384746
  teamId: connection.teamId,
383836
384747
  teamName: connection.teamName
383837
384748
  });
384749
+ if (connection.userClient) {
384750
+ const userAuth = await connection.userClient.auth.test();
384751
+ connection.actingUserId = userAuth.user_id ?? undefined;
384752
+ logger5.info("Acting user identity resolved", {
384753
+ actingUserId: connection.actingUserId,
384754
+ actingUser: userAuth.user
384755
+ });
384756
+ }
383838
384757
  } catch (error) {
383839
384758
  logger5.warn("Failed to resolve bot identity before start \u2014 self-message filtering may be unreliable", {
383840
384759
  error: String(error)
383841
384760
  });
383842
384761
  }
384762
+ if (connection.userClient && !connection.actingUserId) {
384763
+ throw new SlackError(SlackErrorCode.CONNECTION_FAILED, "User mode requires a resolved acting user id, but it could not be determined from the user token. Refusing to start.");
384764
+ }
383843
384765
  if (connection.mode === "http") {
383844
384766
  const port = connection.httpPort ?? 3001;
383845
384767
  try {
@@ -384168,12 +385090,13 @@ function extractMessageMeta(event) {
384168
385090
  ts,
384169
385091
  userId,
384170
385092
  teamId,
384171
- isDm: channelType === "im",
385093
+ isDm: channelType === "im" || channelType === "mpim",
385094
+ isMpim: channelType === "mpim",
384172
385095
  isThreadReply: threadTs !== undefined && threadTs !== ts,
384173
385096
  channelType
384174
385097
  };
384175
385098
  }
384176
- function shouldSkipMessage(msg, botUserId) {
385099
+ function shouldSkipMessage(msg, selfUserIds) {
384177
385100
  if (msg.subtype === "bot_message" || msg.bot_id)
384178
385101
  return true;
384179
385102
  if (msg.subtype === "message_changed" || msg.subtype === "message_deleted")
@@ -384181,7 +385104,7 @@ function shouldSkipMessage(msg, botUserId) {
384181
385104
  const userId = msg.user;
384182
385105
  if (!userId)
384183
385106
  return true;
384184
- if (botUserId && userId === botUserId)
385107
+ if (selfUserIds.some((id) => id && id === userId))
384185
385108
  return true;
384186
385109
  return false;
384187
385110
  }
@@ -384205,6 +385128,7 @@ function buildRawPayload(meta, msg, extra) {
384205
385128
  channelType: meta.channelType,
384206
385129
  teamId: meta.teamId,
384207
385130
  isDm: meta.isDm,
385131
+ isMpim: meta.isMpim === true,
384208
385132
  isThreadReply: meta.isThreadReply,
384209
385133
  files: msg.files,
384210
385134
  ...extra
@@ -384298,11 +385222,12 @@ async function processMessage2(instanceId, msg, currentBotUserId, callbacks, log
384298
385222
  }
384299
385223
  await callbacks.onMessage(instanceId, meta.ts, meta.channelId, userId, content, replyToId, rawPayload, platformTimestamp, meta);
384300
385224
  }
384301
- function setupMessageHandlers2(app, instanceId, botUserId, callbacks, dmPolicyConfig, logger5, filterConfig, reliability) {
385225
+ function setupMessageHandlers2(app, instanceId, botUserId, callbacks, dmPolicyConfig, logger5, filterConfig, reliability, actingUserId) {
384302
385226
  const resolveBotUserId = () => typeof botUserId === "function" ? botUserId() : botUserId;
385227
+ const resolveActingUserId = () => typeof actingUserId === "function" ? actingUserId() : actingUserId;
384303
385228
  app.message(async ({ message: message2 }) => {
384304
385229
  const msg = message2;
384305
- if (shouldSkipMessage(msg, resolveBotUserId()))
385230
+ if (shouldSkipMessage(msg, [resolveBotUserId(), resolveActingUserId()]))
384306
385231
  return;
384307
385232
  const userId = msg.user;
384308
385233
  const meta = extractMessageMeta(msg);
@@ -384454,16 +385379,52 @@ async function uploadFileFromUrl(client, options, logger5) {
384454
385379
 
384455
385380
  // ../channel-slack/src/markdown.ts
384456
385381
  var MAX_SLACK_MESSAGE_LENGTH = 4000;
385382
+ function escapeMrkdwn(text) {
385383
+ return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
385384
+ }
385385
+ function segment(text) {
385386
+ const segments = [];
385387
+ const pattern = /(```[\s\S]*?```|`[^`\n]*`)/g;
385388
+ let lastIndex = 0;
385389
+ for (const match of text.matchAll(pattern)) {
385390
+ const start = match.index ?? 0;
385391
+ if (start > lastIndex) {
385392
+ segments.push({ text: text.slice(lastIndex, start), isCode: false });
385393
+ }
385394
+ segments.push({ text: match[0], isCode: true });
385395
+ lastIndex = start + match[0].length;
385396
+ }
385397
+ if (lastIndex < text.length) {
385398
+ segments.push({ text: text.slice(lastIndex), isCode: false });
385399
+ }
385400
+ return segments;
385401
+ }
385402
+ function unescapeUrl(url) {
385403
+ return url.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">");
385404
+ }
385405
+ function convertProse(text) {
385406
+ const BOLD = "\x00B\x00";
385407
+ let out = escapeMrkdwn(text);
385408
+ out = out.replace(/^&gt;\s?/gm, "> ");
385409
+ out = out.replace(/^#{1,6}\s+(.+)$/gm, `${BOLD}$1${BOLD}`);
385410
+ out = out.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, (_m, alt, url) => `<${unescapeUrl(url)}|${alt}>`);
385411
+ out = out.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, label, url) => `<${unescapeUrl(url)}|${label}>`);
385412
+ out = out.replace(/^(-{3,}|\*{3,}|_{3,})$/gm, "\u2014\u2014\u2014");
385413
+ out = out.replace(/^(\s*)[-*+]\s+/gm, "$1\u2022 ");
385414
+ out = out.replace(/\*\*(.+?)\*\*/g, `${BOLD}$1${BOLD}`);
385415
+ out = out.replace(/(^|[\s(])\*(?!\s)([^*\n]+?)\*(?=[\s).,;:!?]|$)/g, "$1_$2_");
385416
+ out = out.split(BOLD).join("*");
385417
+ out = out.replace(/~~(.+?)~~/g, "~$1~");
385418
+ return out;
385419
+ }
384457
385420
  function markdownToMrkdwn(markdown) {
384458
- let text = markdown;
384459
- text = text.replace(/^#{1,6}\s+(.+)$/gm, "*$1*");
384460
- text = text.replace(/\*\*(.+?)\*\*/g, "*$1*");
384461
- text = text.replace(/~~(.+?)~~/g, "~$1~");
384462
- text = text.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, "<$2|$1>");
384463
- text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "<$2|$1>");
384464
- text = text.replace(/^>\s?/gm, "> ");
384465
- text = text.replace(/^(-{3,}|\*{3,}|_{3,})$/gm, "\u2014\u2014\u2014");
384466
- return text;
385421
+ return segment(markdown).map((s) => s.isCode ? s.text : convertProse(s.text)).join("");
385422
+ }
385423
+ function findOpenFence(text) {
385424
+ const fences = text.match(/```[^\n]*/g);
385425
+ if (!fences || fences.length % 2 === 0)
385426
+ return null;
385427
+ return fences[fences.length - 1]?.slice(3).trim() ?? "";
384467
385428
  }
384468
385429
  function chunkMessage2(text, maxLength = MAX_SLACK_MESSAGE_LENGTH) {
384469
385430
  if (text.length <= maxLength) {
@@ -384471,21 +385432,37 @@ function chunkMessage2(text, maxLength = MAX_SLACK_MESSAGE_LENGTH) {
384471
385432
  }
384472
385433
  const chunks = [];
384473
385434
  let remaining = text;
385435
+ let carryFence = null;
384474
385436
  while (remaining.length > 0) {
384475
- if (remaining.length <= maxLength) {
384476
- chunks.push(remaining);
385437
+ const prefix = carryFence === null ? "" : `\`\`\`${carryFence}
385438
+ `;
385439
+ if (prefix.length + remaining.length <= maxLength) {
385440
+ chunks.push(prefix + remaining);
384477
385441
  break;
384478
385442
  }
384479
- let splitIndex = remaining.lastIndexOf(`
384480
- `, maxLength);
384481
- if (splitIndex <= 0 || splitIndex < maxLength * 0.5) {
384482
- splitIndex = remaining.lastIndexOf(" ", maxLength);
384483
- }
384484
- if (splitIndex <= 0 || splitIndex < maxLength * 0.5) {
384485
- splitIndex = maxLength;
384486
- }
384487
- chunks.push(remaining.substring(0, splitIndex));
384488
- remaining = remaining.substring(splitIndex).trimStart();
385443
+ const split = (budget) => {
385444
+ let at = remaining.lastIndexOf(`
385445
+ `, budget);
385446
+ if (at <= 0 || at < budget * 0.5)
385447
+ at = remaining.lastIndexOf(" ", budget);
385448
+ if (at <= 0 || at < budget * 0.5)
385449
+ at = budget;
385450
+ return at;
385451
+ };
385452
+ const plainBudget = Math.max(1, maxLength - prefix.length);
385453
+ let splitIndex = split(plainBudget);
385454
+ let head = remaining.slice(0, splitIndex);
385455
+ let openFence = findOpenFence(prefix + head);
385456
+ if (openFence !== null) {
385457
+ const fencedBudget = Math.max(1, maxLength - prefix.length - 4);
385458
+ splitIndex = split(fencedBudget);
385459
+ head = remaining.slice(0, splitIndex);
385460
+ openFence = findOpenFence(prefix + head);
385461
+ }
385462
+ chunks.push(openFence === null ? prefix + head : `${prefix + head}
385463
+ \`\`\``);
385464
+ carryFence = openFence;
385465
+ remaining = remaining.slice(splitIndex).trimStart();
384489
385466
  }
384490
385467
  return chunks;
384491
385468
  }
@@ -384827,6 +385804,55 @@ async function sendTextMessage2(client, options, logger5) {
384827
385804
  }
384828
385805
  return lastTs;
384829
385806
  }
385807
+ var MAX_SCHEDULE_AHEAD_MS = 120 * 24 * 60 * 60 * 1000;
385808
+ async function scheduleTextMessage(client, options, logger5) {
385809
+ const formattedText = options.formatMode === "passthrough" ? options.text : markdownToMrkdwn(options.text);
385810
+ if (formattedText.length > MAX_SLACK_MESSAGE_LENGTH) {
385811
+ throw new SlackError(SlackErrorCode.SEND_FAILED, `Scheduled message is ${formattedText.length} chars, over Slack's ${MAX_SLACK_MESSAGE_LENGTH} limit. Chunking is not applied when scheduling \u2014 each chunk would get its own handle and a later cancel could half-fire.`);
385812
+ }
385813
+ const leadMs = options.postAt.getTime() - Date.now();
385814
+ if (leadMs <= 0) {
385815
+ throw new SlackError(SlackErrorCode.SEND_FAILED, `postAt is in the past (${options.postAt.toISOString()})`);
385816
+ }
385817
+ if (leadMs > MAX_SCHEDULE_AHEAD_MS) {
385818
+ throw new SlackError(SlackErrorCode.SEND_FAILED, `postAt is ${Math.round(leadMs / 86400000)} days out; Slack accepts at most 120.`);
385819
+ }
385820
+ try {
385821
+ const result = await client.chat.scheduleMessage({
385822
+ channel: options.channelId,
385823
+ text: formattedText,
385824
+ post_at: Math.floor(options.postAt.getTime() / 1000),
385825
+ thread_ts: options.threadTs,
385826
+ reply_broadcast: options.replyBroadcast,
385827
+ username: options.username,
385828
+ icon_url: options.iconUrl,
385829
+ icon_emoji: options.iconEmoji
385830
+ });
385831
+ const scheduledId = result.scheduled_message_id;
385832
+ if (!scheduledId) {
385833
+ throw new SlackError(SlackErrorCode.SEND_FAILED, "chat.scheduleMessage returned no scheduled_message_id \u2014 cannot cancel later");
385834
+ }
385835
+ return scheduledId;
385836
+ } catch (error) {
385837
+ if (error instanceof SlackError)
385838
+ throw error;
385839
+ const message2 = error instanceof Error ? error.message : String(error);
385840
+ logger5.error("Failed to schedule message", { error: message2, channelId: options.channelId });
385841
+ throw new SlackError(SlackErrorCode.SEND_FAILED, `Failed to schedule message: ${message2}`);
385842
+ }
385843
+ }
385844
+ async function cancelScheduledSlackMessage(client, channelId, scheduledMessageId, logger5) {
385845
+ try {
385846
+ await client.chat.deleteScheduledMessage({
385847
+ channel: channelId,
385848
+ scheduled_message_id: scheduledMessageId
385849
+ });
385850
+ } catch (error) {
385851
+ const message2 = error instanceof Error ? error.message : String(error);
385852
+ logger5.error("Failed to cancel scheduled message", { error: message2, channelId, scheduledMessageId });
385853
+ throw new SlackError(SlackErrorCode.SEND_FAILED, `Failed to cancel scheduled message: ${message2}`);
385854
+ }
385855
+ }
384830
385856
  async function editSlackMessage(client, channelId, ts, newText, formatMode, logger5) {
384831
385857
  const formattedText = formatMode === "passthrough" ? newText : markdownToMrkdwn(newText);
384832
385858
  try {
@@ -384859,19 +385885,27 @@ init_types10();
384859
385885
  var downloadGuard4 = createDownloadGuard();
384860
385886
  function resolveSlackTokens(slackConfig, rawOptions, rawCredentials) {
384861
385887
  const botToken = slackConfig.botToken ?? rawOptions.token ?? rawCredentials.botToken ?? rawCredentials.token;
385888
+ const userToken = slackConfig.userToken ?? rawCredentials.userToken;
385889
+ const authMode = slackConfig.authMode ?? "bot";
384862
385890
  const appToken = slackConfig.appToken ?? rawCredentials.appToken;
384863
385891
  const signingSecret = slackConfig.signingSecret ?? rawCredentials.signingSecret;
384864
385892
  const mode = slackConfig.mode ?? "socket";
384865
385893
  if (!botToken) {
384866
385894
  throw new SlackError(SlackErrorCode.INVALID_TOKEN, "botToken (xoxb-...) is required");
384867
385895
  }
385896
+ if (authMode === "user" && !userToken) {
385897
+ throw new SlackError(SlackErrorCode.INVALID_TOKEN, "userToken (xoxp-...) is required when authMode is 'user' \u2014 without it every action would silently go out as the bot");
385898
+ }
385899
+ if (userToken && !userToken.startsWith("xoxp-")) {
385900
+ throw new SlackError(SlackErrorCode.INVALID_TOKEN, "userToken must be a user token (xoxp-...); got a token with a different prefix");
385901
+ }
384868
385902
  if (mode === "socket" && !appToken) {
384869
385903
  throw new SlackError(SlackErrorCode.INVALID_TOKEN, "appToken (xapp-...) is required for Socket Mode");
384870
385904
  }
384871
385905
  if (mode === "http" && !signingSecret) {
384872
385906
  throw new SlackError(SlackErrorCode.INVALID_TOKEN, "signingSecret is required for HTTP mode");
384873
385907
  }
384874
- return { botToken, appToken, signingSecret, mode };
385908
+ return { botToken, userToken, authMode, appToken, signingSecret, mode };
384875
385909
  }
384876
385910
 
384877
385911
  class SlackPlugin extends BaseChannelPlugin {
@@ -384950,6 +385984,8 @@ class SlackPlugin extends BaseChannelPlugin {
384950
385984
  }
384951
385985
  connection = createBoltApp({
384952
385986
  botToken: resolved.botToken,
385987
+ userToken: resolved.userToken,
385988
+ authMode: resolved.authMode,
384953
385989
  appToken: resolved.appToken,
384954
385990
  signingSecret: resolved.signingSecret,
384955
385991
  retryConfig: slackConfig.retryConfig,
@@ -385078,7 +386114,7 @@ class SlackPlugin extends BaseChannelPlugin {
385078
386114
  const streamMode = resolveStreamMode(slackConfig.streamMode);
385079
386115
  const throttleMs = resolveStreamThrottle(slackConfig.streamThrottleMs);
385080
386116
  const base2 = streamMode === "native" ? createNativeStreamSender({
385081
- client: connection.client,
386117
+ client: connection.actingClient,
385082
386118
  channelId: chatId,
385083
386119
  threadTs,
385084
386120
  throttleMs,
@@ -385088,7 +386124,7 @@ class SlackPlugin extends BaseChannelPlugin {
385088
386124
  formatMode: options?.formatMode ?? "convert",
385089
386125
  logger: this.logger
385090
386126
  }) : createSlackStreamSender({
385091
- client: connection.client,
386127
+ client: connection.actingClient,
385092
386128
  channelId: chatId,
385093
386129
  threadTs,
385094
386130
  streamMode,
@@ -385135,12 +386171,12 @@ class SlackPlugin extends BaseChannelPlugin {
385135
386171
  const status = shouldClear ? "" : options?.status ?? (type === "recording" ? "is recording..." : "is typing...");
385136
386172
  const timerKey = this.presenceStatusTimerKey(instanceId, chatId, threadTs);
385137
386173
  const delivered = status === "" ? await clearTypingStatus({
385138
- client: connection.client,
386174
+ client: connection.actingClient,
385139
386175
  channelId: chatId,
385140
386176
  threadTs,
385141
386177
  logger: this.logger
385142
386178
  }) : await setSlackThreadStatus({
385143
- client: connection.client,
386179
+ client: connection.actingClient,
385144
386180
  channelId: chatId,
385145
386181
  threadTs,
385146
386182
  status,
@@ -385156,7 +386192,7 @@ class SlackPlugin extends BaseChannelPlugin {
385156
386192
  return;
385157
386193
  this.presenceStatusTimers.delete(timerKey);
385158
386194
  clearTypingStatus({
385159
- client: connection.client,
386195
+ client: connection.actingClient,
385160
386196
  channelId: chatId,
385161
386197
  threadTs,
385162
386198
  logger: this.logger
@@ -385193,21 +386229,105 @@ class SlackPlugin extends BaseChannelPlugin {
385193
386229
  }
385194
386230
  async editMessage(instanceId, channelId, messageTs, newText) {
385195
386231
  const connection = this.getConnection(instanceId);
385196
- await editSlackMessage(connection.client, channelId, messageTs, newText, "convert", this.logger);
386232
+ await editSlackMessage(connection.actingClient, channelId, messageTs, newText, "convert", this.logger);
385197
386233
  }
385198
386234
  async deleteMessage(instanceId, channelId, messageTs) {
385199
386235
  const connection = this.getConnection(instanceId);
385200
- await deleteSlackMessage(connection.client, channelId, messageTs, this.logger);
386236
+ await deleteSlackMessage(connection.actingClient, channelId, messageTs, this.logger);
386237
+ }
386238
+ async scheduleMessage(instanceId, message2, sendAt) {
386239
+ const connection = this.getConnection(instanceId);
386240
+ const config2 = this.slackConfigs.get(instanceId);
386241
+ if (message2.content.type !== "text" || !message2.content.text) {
386242
+ throw new SlackError(SlackErrorCode.SEND_FAILED, `Only text messages can be scheduled (got '${message2.content.type}') \u2014 chat.scheduleMessage carries no attachment.`);
386243
+ }
386244
+ const threadTs = this.resolveThreadTs(config2?.replyToMode ?? "all", message2.replyTo, message2.threadId);
386245
+ return scheduleTextMessage(connection.actingClient, {
386246
+ channelId: message2.to,
386247
+ text: message2.content.text,
386248
+ threadTs,
386249
+ replyBroadcast: message2.metadata?.isThreadBroadcast === true,
386250
+ username: config2?.defaultUsername,
386251
+ iconUrl: config2?.defaultIconUrl,
386252
+ iconEmoji: config2?.defaultIconEmoji,
386253
+ formatMode: message2.metadata?.messageFormatMode,
386254
+ postAt: sendAt
386255
+ }, this.logger);
386256
+ }
386257
+ async openDirectMessage(instanceId, userId) {
386258
+ const connection = this.getConnection(instanceId);
386259
+ try {
386260
+ const result = await connection.actingClient.conversations.open({ users: userId });
386261
+ const channelId = result.channel?.id;
386262
+ if (!channelId) {
386263
+ throw new SlackError(SlackErrorCode.SEND_FAILED, `conversations.open returned no channel for user ${userId}`);
386264
+ }
386265
+ return channelId;
386266
+ } catch (error) {
386267
+ if (error instanceof SlackError)
386268
+ throw error;
386269
+ const message2 = error instanceof Error ? error.message : String(error);
386270
+ throw new SlackError(SlackErrorCode.SEND_FAILED, `Failed to open DM with ${userId}: ${message2}`);
386271
+ }
386272
+ }
386273
+ async searchMessages(instanceId, query, options = {}) {
386274
+ const connection = this.getConnection(instanceId);
386275
+ const config2 = this.slackConfigs.get(instanceId);
386276
+ if (config2?.authMode !== "user" || !connection.userClient) {
386277
+ throw new SlackError(SlackErrorCode.SEND_FAILED, "search.messages needs a user token (search:read); this instance runs in 'bot' auth mode");
386278
+ }
386279
+ try {
386280
+ const result = await connection.userClient.search.messages({
386281
+ query,
386282
+ count: options.count ?? 20,
386283
+ page: options.page ?? 1
386284
+ });
386285
+ const matches = result.messages?.matches ?? [];
386286
+ return matches.map((raw) => {
386287
+ const m2 = raw;
386288
+ return {
386289
+ channelId: m2.channel?.id,
386290
+ ts: m2.ts,
386291
+ text: m2.text,
386292
+ permalink: m2.permalink,
386293
+ username: m2.username
386294
+ };
386295
+ });
386296
+ } catch (error) {
386297
+ const message2 = error instanceof Error ? error.message : String(error);
386298
+ throw new SlackError(SlackErrorCode.SEND_FAILED, `Search failed: ${message2}`);
386299
+ }
386300
+ }
386301
+ async cancelScheduledMessage(instanceId, channelId, scheduledId) {
386302
+ const connection = this.getConnection(instanceId);
386303
+ await cancelScheduledSlackMessage(connection.actingClient, channelId, scheduledId, this.logger);
386304
+ }
386305
+ async getPermalink(instanceId, channelId, messageTs) {
386306
+ const connection = this.getConnection(instanceId);
386307
+ try {
386308
+ const result = await connection.actingClient.chat.getPermalink({
386309
+ channel: channelId,
386310
+ message_ts: messageTs
386311
+ });
386312
+ return result.permalink ?? null;
386313
+ } catch (error) {
386314
+ this.logger.warn("Failed to resolve permalink", {
386315
+ error: error instanceof Error ? error.message : String(error),
386316
+ channelId,
386317
+ messageTs
386318
+ });
386319
+ return null;
386320
+ }
385201
386321
  }
385202
386322
  async addReaction(instanceId, channelId, messageTs, emoji) {
385203
386323
  const connection = this.getConnection(instanceId);
385204
386324
  const { addReaction: addReaction3 } = await Promise.resolve().then(() => (init_tools(), exports_tools));
385205
- await addReaction3(connection.client, channelId, messageTs, emoji, this.logger);
386325
+ await addReaction3(connection.actingClient, channelId, messageTs, emoji, this.logger);
385206
386326
  }
385207
386327
  async removeReaction(instanceId, channelId, messageTs, emoji) {
385208
386328
  const connection = this.getConnection(instanceId);
385209
386329
  const { removeReaction: removeReaction3 } = await Promise.resolve().then(() => (init_tools(), exports_tools));
385210
- await removeReaction3(connection.client, channelId, messageTs, emoji, this.logger);
386330
+ await removeReaction3(connection.actingClient, channelId, messageTs, emoji, this.logger);
385211
386331
  }
385212
386332
  async getProfile(instanceId) {
385213
386333
  const connection = this.getConnection(instanceId);
@@ -385226,7 +386346,7 @@ class SlackPlugin extends BaseChannelPlugin {
385226
386346
  async fetchUserProfile(instanceId, userId) {
385227
386347
  const connection = this.getConnection(instanceId);
385228
386348
  try {
385229
- const result = await connection.client.users.info({ user: userId });
386349
+ const result = await connection.actingClient.users.info({ user: userId });
385230
386350
  const user = result.user;
385231
386351
  if (!user)
385232
386352
  return {};
@@ -385286,11 +386406,7 @@ class SlackPlugin extends BaseChannelPlugin {
385286
386406
  if (!channelId || !threadTs)
385287
386407
  return { totalFetched: 0, messages: [] };
385288
386408
  const botUserId = connection.botUserId;
385289
- const botToken = this.slackConfigs.get(instanceId)?.botToken;
385290
- if (!botToken) {
385291
- this.logger.warn("fetchHistory: no botToken for instance", { instanceId });
385292
- return { totalFetched: 0, messages: [] };
385293
- }
386409
+ const botToken = connection.botToken;
385294
386410
  const limit = options.limit ?? 200;
385295
386411
  const messages = await this.paginateThreadHistory(connection, channelId, threadTs, botUserId, botToken, limit);
385296
386412
  return { totalFetched: messages.length, messages };
@@ -385299,7 +386415,7 @@ class SlackPlugin extends BaseChannelPlugin {
385299
386415
  const messages = [];
385300
386416
  let cursor;
385301
386417
  do {
385302
- const response = await connection.client.conversations.replies({
386418
+ const response = await connection.actingClient.conversations.replies({
385303
386419
  channel: channelId,
385304
386420
  ts: threadTs,
385305
386421
  limit: Math.min(200, maxMessages - messages.length),
@@ -385372,7 +386488,7 @@ class SlackPlugin extends BaseChannelPlugin {
385372
386488
  const connection = this.getConnection(instanceId);
385373
386489
  const slackName = SlackPlugin.EMOJI_TO_SLACK[emoji] ?? emoji.replace(/^:|:$/g, "");
385374
386490
  try {
385375
- await connection.client.reactions.add({ channel: chatId, timestamp: messageId, name: slackName });
386491
+ await connection.actingClient.reactions.add({ channel: chatId, timestamp: messageId, name: slackName });
385376
386492
  } catch (err2) {
385377
386493
  this.logger.warn("react: failed to add reaction", { chatId, messageId, emoji, error: String(err2) });
385378
386494
  }
@@ -385381,7 +386497,7 @@ class SlackPlugin extends BaseChannelPlugin {
385381
386497
  const connection = this.getConnection(instanceId);
385382
386498
  const slackName = SlackPlugin.EMOJI_TO_SLACK[emoji] ?? emoji.replace(/^:|:$/g, "");
385383
386499
  try {
385384
- await connection.client.reactions.remove({ channel: chatId, timestamp: messageId, name: slackName });
386500
+ await connection.actingClient.reactions.remove({ channel: chatId, timestamp: messageId, name: slackName });
385385
386501
  } catch (err2) {
385386
386502
  this.logger.warn("unreact: failed to remove reaction", { chatId, messageId, emoji, error: String(err2) });
385387
386503
  }
@@ -385393,7 +386509,7 @@ class SlackPlugin extends BaseChannelPlugin {
385393
386509
  const emojiName = typeof ackEmoji === "string" ? ackEmoji.replace(/^:|:$/g, "") : null;
385394
386510
  if (!emojiName)
385395
386511
  return;
385396
- connection.client.reactions.add({ channel: channelId, timestamp: messageTs, name: emojiName }).catch((err2) => {
386512
+ connection.actingClient.reactions.add({ channel: channelId, timestamp: messageTs, name: emojiName }).catch((err2) => {
385397
386513
  this.logger.warn("ack reaction: failed to add", { channelId, messageTs, emoji: emojiName, error: String(err2) });
385398
386514
  });
385399
386515
  if (config2.removeAckAfterReply !== false) {
@@ -385412,7 +386528,7 @@ class SlackPlugin extends BaseChannelPlugin {
385412
386528
  if (!emojiName)
385413
386529
  continue;
385414
386530
  this.pendingAckReactions.delete(key);
385415
- connection.client.reactions.remove({ channel: channelId, timestamp: ts, name: emojiName }).catch((err2) => {
386531
+ connection.actingClient.reactions.remove({ channel: channelId, timestamp: ts, name: emojiName }).catch((err2) => {
385416
386532
  this.logger.warn("ack reaction: failed to remove", { channelId, ts, emoji: emojiName, error: String(err2) });
385417
386533
  });
385418
386534
  break;
@@ -385426,7 +386542,7 @@ class SlackPlugin extends BaseChannelPlugin {
385426
386542
  if (!resolvedThread)
385427
386543
  return;
385428
386544
  await clearTypingStatus({
385429
- client: connection.client,
386545
+ client: connection.actingClient,
385430
386546
  channelId,
385431
386547
  threadTs: resolvedThread,
385432
386548
  logger: this.logger
@@ -385472,14 +386588,16 @@ class SlackPlugin extends BaseChannelPlugin {
385472
386588
  async buildEnrichedPayload(instanceId, from, rawPayload) {
385473
386589
  const displayName = await this.resolveUserDisplayName(instanceId, from);
385474
386590
  const isDm = rawPayload.isDm;
385475
- const chatName = isDm ? displayName : undefined;
386591
+ const isMpim = rawPayload.isMpim === true;
386592
+ const isOneToOne = isDm && !isMpim;
386593
+ const chatName = isOneToOne ? displayName : undefined;
385476
386594
  return {
385477
386595
  ...rawPayload,
385478
386596
  displayName,
385479
386597
  senderName: displayName,
385480
386598
  pushName: displayName,
385481
386599
  chatName,
385482
- isGroup: !isDm
386600
+ isGroup: !isOneToOne
385483
386601
  };
385484
386602
  }
385485
386603
  async dispatchMessageFromDebounce(instanceId, args) {
@@ -385510,7 +386628,7 @@ class SlackPlugin extends BaseChannelPlugin {
385510
386628
  },
385511
386629
  onDmRejected: async (_instId, channelId, _userId, message2) => {
385512
386630
  try {
385513
- await sendTextMessage2(connection.client, {
386631
+ await sendTextMessage2(connection.actingClient, {
385514
386632
  channelId,
385515
386633
  text: message2,
385516
386634
  formatMode: "passthrough"
@@ -385527,7 +386645,7 @@ class SlackPlugin extends BaseChannelPlugin {
385527
386645
  channelAllowlist: config2.channelAllowlist,
385528
386646
  channelBlocklist: config2.channelBlocklist,
385529
386647
  channels: config2.channels
385530
- }, reliability);
386648
+ }, reliability, () => connection.actingUserId);
385531
386649
  setupReactionHandlers2(connection.app, instanceId, () => connection.botUserId, {
385532
386650
  onReaction: async (instId, messageId, chatId, userId, emoji, action) => {
385533
386651
  await this.handleReactionReceived(instId, messageId, chatId, userId, emoji, action);
@@ -385567,7 +386685,7 @@ class SlackPlugin extends BaseChannelPlugin {
385567
386685
  const formatMode = message2.metadata?.messageFormatMode ?? "convert";
385568
386686
  const replyToMode = config2.replyToMode ?? "all";
385569
386687
  const threadTs = this.resolveThreadTs(replyToMode, message2.replyTo, message2.threadId);
385570
- return sendTextMessage2(connection.client, {
386688
+ return sendTextMessage2(connection.actingClient, {
385571
386689
  channelId,
385572
386690
  text: message2.content.text ?? "",
385573
386691
  threadTs,
@@ -385585,7 +386703,7 @@ class SlackPlugin extends BaseChannelPlugin {
385585
386703
  if (message2.metadata?.base64) {
385586
386704
  const buffer2 = Buffer.from(message2.metadata.base64, "base64");
385587
386705
  const filename = message2.content.filename || `file-${Date.now()}`;
385588
- return uploadFile(connection.client, {
386706
+ return uploadFile(connection.actingClient, {
385589
386707
  channelId,
385590
386708
  content: buffer2,
385591
386709
  filename,
@@ -385596,7 +386714,7 @@ class SlackPlugin extends BaseChannelPlugin {
385596
386714
  if (!message2.content.mediaUrl) {
385597
386715
  throw new SlackError(SlackErrorCode.SEND_FAILED, "Media URL or base64 required");
385598
386716
  }
385599
- return uploadFileFromUrl(connection.client, {
386717
+ return uploadFileFromUrl(connection.actingClient, {
385600
386718
  channelId,
385601
386719
  url: message2.content.mediaUrl,
385602
386720
  filename: message2.content.filename || `file-${Date.now()}`,
@@ -385611,7 +386729,7 @@ class SlackPlugin extends BaseChannelPlugin {
385611
386729
  throw new SlackError(SlackErrorCode.SEND_FAILED, "Reaction requires emoji and target message");
385612
386730
  }
385613
386731
  const { addReaction: addReaction3 } = await Promise.resolve().then(() => (init_tools(), exports_tools));
385614
- await addReaction3(connection.client, channelId, targetTs, emoji, this.logger);
386732
+ await addReaction3(connection.actingClient, channelId, targetTs, emoji, this.logger);
385615
386733
  return targetTs;
385616
386734
  }
385617
386735
  async handleInboundFiles(instanceId, externalId, chatId, from, content, replyToId, rawPayload, platformTimestamp) {
@@ -385637,6 +386755,7 @@ class SlackPlugin extends BaseChannelPlugin {
385637
386755
  const timings = platformTimestamp ? this.captureInboundTimings(platformTimestamp) : undefined;
385638
386756
  const senderName = typeof rawPayload.senderName === "string" ? rawPayload.senderName : typeof rawPayload.displayName === "string" ? rawPayload.displayName : undefined;
385639
386757
  const chatName = typeof rawPayload.chatName === "string" ? rawPayload.chatName : undefined;
386758
+ const threadTs = typeof rawPayload.threadTs === "string" ? rawPayload.threadTs : undefined;
385640
386759
  const correlationId = await this.emitMessageReceived({
385641
386760
  instanceId,
385642
386761
  externalId,
@@ -385645,7 +386764,7 @@ class SlackPlugin extends BaseChannelPlugin {
385645
386764
  senderName,
385646
386765
  chatName,
385647
386766
  content,
385648
- replyToId,
386767
+ threadId: threadTs,
385649
386768
  rawPayload,
385650
386769
  timings
385651
386770
  });
@@ -386995,14 +388114,14 @@ var protectedSplitters = {
386995
388114
  expandable_blockquote: trySplitBlockquote,
386996
388115
  plain: () => null
386997
388116
  };
386998
- function splitLongProtectedSegment(segment, maxLength) {
386999
- if (segment.value.length <= maxLength)
387000
- return [segment.value];
387001
- const splitter = protectedSplitters[segment.type];
387002
- const result = splitter(segment.value, maxLength);
388117
+ function splitLongProtectedSegment(segment2, maxLength) {
388118
+ if (segment2.value.length <= maxLength)
388119
+ return [segment2.value];
388120
+ const splitter = protectedSplitters[segment2.type];
388121
+ const result = splitter(segment2.value, maxLength);
387003
388122
  if (result)
387004
388123
  return result;
387005
- return splitPlainByBoundaries(segment.value, maxLength);
388124
+ return splitPlainByBoundaries(segment2.value, maxLength);
387006
388125
  }
387007
388126
  function findNextProtected(html, from) {
387008
388127
  const patterns = [
@@ -387086,12 +388205,12 @@ class ChunkAccumulator {
387086
388205
  return this.chunks.length > 0 ? this.chunks : [""];
387087
388206
  }
387088
388207
  }
387089
- function processPlainSegment(acc, segment, maxLength) {
387090
- const plainParts = splitPlainHtmlPreservingTags(segment.value, maxLength);
388208
+ function processPlainSegment(acc, segment2, maxLength) {
388209
+ const plainParts = splitPlainHtmlPreservingTags(segment2.value, maxLength);
387091
388210
  acc.appendParts(plainParts);
387092
388211
  }
387093
- function processProtectedSegment(acc, segment, maxLength) {
387094
- const protectedParts = splitLongProtectedSegment(segment, maxLength);
388212
+ function processProtectedSegment(acc, segment2, maxLength) {
388213
+ const protectedParts = splitLongProtectedSegment(segment2, maxLength);
387095
388214
  for (const part of protectedParts) {
387096
388215
  if (part.length > maxLength) {
387097
388216
  const hardParts = splitPlainByBoundaries(part, maxLength);
@@ -387106,11 +388225,11 @@ function splitHtmlMessage(html, maxLength = 4096) {
387106
388225
  return [html];
387107
388226
  const segments = segmentHtml(html);
387108
388227
  const acc = new ChunkAccumulator(maxLength);
387109
- for (const segment of segments) {
387110
- if (segment.type === "plain") {
387111
- processPlainSegment(acc, segment, maxLength);
388228
+ for (const segment2 of segments) {
388229
+ if (segment2.type === "plain") {
388230
+ processPlainSegment(acc, segment2, maxLength);
387112
388231
  } else {
387113
- processProtectedSegment(acc, segment, maxLength);
388232
+ processProtectedSegment(acc, segment2, maxLength);
387114
388233
  }
387115
388234
  }
387116
388235
  return acc.result();
@@ -477531,6 +478650,7 @@ for (const [i, DOUBLE_BYTE_TOKEN] of DOUBLE_BYTE_TOKENS.entries()) {
477531
478650
 
477532
478651
  // ../../node_modules/.bun/baileys@vendor+baileys-v7.0.0-rc10.tgz+89588f7e3fe8b2e6/node_modules/baileys/lib/WABinary/jid-utils.js
477533
478652
  var S_WHATSAPP_NET = "@s.whatsapp.net";
478653
+ var SERVER_JID = "server@c.us";
477534
478654
  var PSA_WID = "0@c.us";
477535
478655
  var WAJIDDomains;
477536
478656
  (function(WAJIDDomains2) {
@@ -490763,6 +491883,181 @@ function buildAckStanza(node, errorCode, meId) {
490763
491883
  }
490764
491884
  return stanza;
490765
491885
  }
491886
+ // ../../node_modules/.bun/baileys@vendor+baileys-v7.0.0-rc10.tgz+89588f7e3fe8b2e6/node_modules/baileys/lib/Utils/passkey.js
491887
+ var BASE64URL_PATTERN = /^[A-Za-z0-9_-]+$/;
491888
+ var PASSKEY_HANDOFF_INFO = "shortcake-passkey-handoff-v1";
491889
+ var PAIRING_ENCRYPTION_INFO = "Pairing Information Encryption Key";
491890
+ var assertBase64Url = (value, field, nullable = false) => {
491891
+ if (nullable && value === null) {
491892
+ return null;
491893
+ }
491894
+ if (typeof value !== "string" || !value || !BASE64URL_PATTERN.test(value)) {
491895
+ throw new Error(`Invalid ${field} in passkey payload`);
491896
+ }
491897
+ return value;
491898
+ };
491899
+ var parseAllowedCredentials = (value) => {
491900
+ if (value === undefined) {
491901
+ return;
491902
+ }
491903
+ if (!Array.isArray(value)) {
491904
+ throw new Error("Invalid allowCredentials in passkey request");
491905
+ }
491906
+ return value.map((credential, index) => {
491907
+ if (!credential || typeof credential !== "object") {
491908
+ throw new Error(`Invalid allowCredentials[${index}] in passkey request`);
491909
+ }
491910
+ const candidate = credential;
491911
+ if (typeof candidate.type !== "string" || !candidate.type) {
491912
+ throw new Error(`Invalid allowCredentials[${index}].type in passkey request`);
491913
+ }
491914
+ if (candidate.transports !== undefined && (!Array.isArray(candidate.transports) || candidate.transports.some((item) => typeof item !== "string"))) {
491915
+ throw new Error(`Invalid allowCredentials[${index}].transports in passkey request`);
491916
+ }
491917
+ return {
491918
+ id: assertBase64Url(candidate.id, `allowCredentials[${index}].id`),
491919
+ type: candidate.type,
491920
+ ...candidate.transports ? { transports: candidate.transports } : {}
491921
+ };
491922
+ });
491923
+ };
491924
+ var parsePasskeyRequestOptions = (content) => {
491925
+ let parsed;
491926
+ try {
491927
+ parsed = JSON.parse(content.toString("utf8"));
491928
+ } catch {
491929
+ throw new Error("WhatsApp sent malformed passkey request JSON");
491930
+ }
491931
+ if (!parsed || typeof parsed !== "object") {
491932
+ throw new Error("WhatsApp sent an invalid passkey request");
491933
+ }
491934
+ const value = parsed;
491935
+ if (typeof value.rpId !== "string" || value.rpId !== "whatsapp.com") {
491936
+ throw new Error("WhatsApp sent an unexpected passkey relying party");
491937
+ }
491938
+ if (value.timeout !== undefined && (typeof value.timeout !== "number" || value.timeout <= 0)) {
491939
+ throw new Error("WhatsApp sent an invalid passkey timeout");
491940
+ }
491941
+ if (value.userVerification !== undefined && typeof value.userVerification !== "string") {
491942
+ throw new Error("WhatsApp sent an invalid passkey user verification mode");
491943
+ }
491944
+ if (value.extensions !== undefined && (!value.extensions || typeof value.extensions !== "object")) {
491945
+ throw new Error("WhatsApp sent invalid passkey extensions");
491946
+ }
491947
+ return {
491948
+ challenge: assertBase64Url(value.challenge, "challenge"),
491949
+ rpId: value.rpId,
491950
+ ...value.timeout === undefined ? {} : { timeout: value.timeout },
491951
+ ...value.allowCredentials === undefined ? {} : { allowCredentials: parseAllowedCredentials(value.allowCredentials) },
491952
+ ...value.userVerification === undefined ? {} : { userVerification: value.userVerification },
491953
+ ...value.extensions === undefined ? {} : { extensions: value.extensions }
491954
+ };
491955
+ };
491956
+ var parsePasskeyCredentialResponse = (input) => {
491957
+ if (!input || typeof input !== "object") {
491958
+ throw new Error("Invalid passkey credential response");
491959
+ }
491960
+ const value = input;
491961
+ if (value.type !== "public-key") {
491962
+ throw new Error("Invalid passkey credential type");
491963
+ }
491964
+ if (!value.response || typeof value.response !== "object") {
491965
+ throw new Error("Invalid passkey authenticator response");
491966
+ }
491967
+ const response = value.response;
491968
+ return {
491969
+ id: assertBase64Url(value.id, "credential id"),
491970
+ rawId: assertBase64Url(value.rawId, "raw credential id"),
491971
+ type: value.type,
491972
+ response: {
491973
+ clientDataJSON: assertBase64Url(response.clientDataJSON, "clientDataJSON"),
491974
+ authenticatorData: assertBase64Url(response.authenticatorData, "authenticatorData"),
491975
+ signature: assertBase64Url(response.signature, "signature"),
491976
+ userHandle: assertBase64Url(response.userHandle, "userHandle", true)
491977
+ }
491978
+ };
491979
+ };
491980
+ var getPasskeyDeviceType = (browser) => {
491981
+ const browserName = browser[1].toLowerCase();
491982
+ if (browserName.includes("firefox"))
491983
+ return proto.DeviceProps.PlatformType.FIREFOX;
491984
+ if (browserName.includes("edge"))
491985
+ return proto.DeviceProps.PlatformType.EDGE;
491986
+ if (browserName.includes("opera"))
491987
+ return proto.DeviceProps.PlatformType.OPERA;
491988
+ if (browserName.includes("safari"))
491989
+ return proto.DeviceProps.PlatformType.SAFARI;
491990
+ return proto.DeviceProps.PlatformType.CHROME;
491991
+ };
491992
+ var derivePasskeyHandoffKey = (advSecretKey) => Buffer.from(hkdf(Buffer.from(advSecretKey, "base64"), 32, { info: PASSKEY_HANDOFF_INFO }));
491993
+ var buildPasskeyPrologue = ({ credential, pairingRef, browser, handoffKey, keyPair = Curve.generateKeyPair(), companionNonce }) => {
491994
+ if (companionNonce.length !== 32) {
491995
+ throw new Error("Passkey companion nonce must contain 32 bytes");
491996
+ }
491997
+ const deviceType = getPasskeyDeviceType(browser);
491998
+ const identity = Buffer.from(proto.CompanionEphemeralIdentity.encode({
491999
+ publicKey: keyPair.public,
492000
+ deviceType,
492001
+ ref: pairingRef
492002
+ }).finish());
492003
+ const commitment = sha256(Buffer.concat([identity, companionNonce]));
492004
+ const payload = Buffer.from(proto.ProloguePayload.encode({
492005
+ companionEphemeralIdentity: identity,
492006
+ commitment: { hash: commitment }
492007
+ }).finish());
492008
+ const content = [
492009
+ { tag: "credential_id", attrs: {}, content: Buffer.from(credential.rawId, "base64url") },
492010
+ { tag: "webauthn_assertion", attrs: {}, content: Buffer.from(JSON.stringify(credential), "utf8") },
492011
+ { tag: "prologue_payload", attrs: {}, content: payload }
492012
+ ];
492013
+ if (handoffKey) {
492014
+ content.push({ tag: "pairing_handoff_proof", attrs: {}, content: hmacSign(payload, handoffKey) });
492015
+ }
492016
+ return {
492017
+ content,
492018
+ payload,
492019
+ cache: { keyPair, companionNonce, pairingRef, deviceType },
492020
+ skipHandoffUX: Boolean(handoffKey)
492021
+ };
492022
+ };
492023
+ var continuePasskeyPairing = ({ cache: cache2, primaryIdentity }) => {
492024
+ const publicKey = primaryIdentity.publicKey ? Buffer.from(primaryIdentity.publicKey) : undefined;
492025
+ const nonce = primaryIdentity.nonce ? Buffer.from(primaryIdentity.nonce) : undefined;
492026
+ if (!publicKey || publicKey.length !== 32) {
492027
+ throw new Error("Invalid primary passkey public key");
492028
+ }
492029
+ if (!nonce || nonce.length !== 32) {
492030
+ throw new Error("Invalid primary passkey nonce");
492031
+ }
492032
+ const sharedSecret = Curve.sharedKey(cache2.keyPair.private, publicKey);
492033
+ const salt = `Companion Pairing ${cache2.deviceType} with ref ${cache2.pairingRef}`;
492034
+ const encryptionKey = Buffer.from(hkdf(sharedSecret, 32, { salt: Buffer.from(salt, "utf8"), info: PAIRING_ENCRYPTION_INFO }));
492035
+ const digest = sha256(Buffer.concat([cache2.companionNonce, publicKey]));
492036
+ const codeBytes = Buffer.alloc(5);
492037
+ for (let index = 0;index < codeBytes.length; index += 1) {
492038
+ codeBytes[index] = nonce[index] ^ digest[index];
492039
+ }
492040
+ const encodedCode = bytesToCrockford(codeBytes);
492041
+ return {
492042
+ cache: { ...cache2, encryptionKey },
492043
+ code: `${encodedCode.slice(0, 4)}-${encodedCode.slice(4)}`
492044
+ };
492045
+ };
492046
+ var buildEncryptedPasskeyPairingRequest = ({ cache: cache2, noisePublicKey, identityPublicKey, advSecretKey, iv }) => {
492047
+ if (!cache2.encryptionKey) {
492048
+ throw new Error("Passkey confirmation is not ready");
492049
+ }
492050
+ if (iv.length !== 12) {
492051
+ throw new Error("Passkey encryption IV must contain 12 bytes");
492052
+ }
492053
+ const request = Buffer.from(proto.PairingRequest.encode({
492054
+ companionPublicKey: noisePublicKey,
492055
+ companionIdentityKey: identityPublicKey,
492056
+ advSecret: Buffer.from(advSecretKey, "base64")
492057
+ }).finish());
492058
+ const encryptedPayload = aesEncryptGCM(request, cache2.encryptionKey, iv, Buffer.alloc(0));
492059
+ return Buffer.from(proto.EncryptedPairingRequest.encode({ encryptedPayload, iv }).finish());
492060
+ };
490766
492061
  // ../../node_modules/.bun/baileys@vendor+baileys-v7.0.0-rc10.tgz+89588f7e3fe8b2e6/node_modules/baileys/lib/Signal/Group/sender-key-name.js
490767
492062
  function isNull(str) {
490768
492063
  return str === null || str === "";
@@ -490814,14 +492109,14 @@ class SenderKeyName2 {
490814
492109
  }
490815
492110
 
490816
492111
  // ../../node_modules/.bun/baileys@vendor+baileys-v7.0.0-rc10.tgz+89588f7e3fe8b2e6/node_modules/baileys/lib/Signal/Group/sender-chain-key.js
490817
- var import_crypto18 = __toESM(require_crypto(), 1);
492112
+ var import_crypto19 = __toESM(require_crypto(), 1);
490818
492113
 
490819
492114
  // ../../node_modules/.bun/baileys@vendor+baileys-v7.0.0-rc10.tgz+89588f7e3fe8b2e6/node_modules/baileys/lib/Signal/Group/sender-message-key.js
490820
- var import_crypto17 = __toESM(require_crypto(), 1);
492115
+ var import_crypto18 = __toESM(require_crypto(), 1);
490821
492116
 
490822
492117
  class SenderMessageKey {
490823
492118
  constructor(iteration, seed) {
490824
- const derivative = import_crypto17.deriveSecrets(seed, Buffer.alloc(32), Buffer.from("WhisperGroup"));
492119
+ const derivative = import_crypto18.deriveSecrets(seed, Buffer.alloc(32), Buffer.from("WhisperGroup"));
490825
492120
  const keys = new Uint8Array(32);
490826
492121
  keys.set(new Uint8Array(derivative[0].slice(16)));
490827
492122
  keys.set(new Uint8Array(derivative[1].slice(0, 16)), 16);
@@ -490865,7 +492160,7 @@ class SenderChainKey {
490865
492160
  return this.chainKey;
490866
492161
  }
490867
492162
  getDerivative(seed, key) {
490868
- return import_crypto18.calculateMAC(key, seed);
492163
+ return import_crypto19.calculateMAC(key, seed);
490869
492164
  }
490870
492165
  }
490871
492166
 
@@ -491102,7 +492397,7 @@ class GroupSessionBuilder2 {
491102
492397
  }
491103
492398
  }
491104
492399
  // ../../node_modules/.bun/baileys@vendor+baileys-v7.0.0-rc10.tgz+89588f7e3fe8b2e6/node_modules/baileys/lib/Signal/Group/group_cipher.js
491105
- var import_crypto19 = __toESM(require_crypto(), 1);
492400
+ var import_crypto20 = __toESM(require_crypto(), 1);
491106
492401
 
491107
492402
  // ../../node_modules/.bun/baileys@vendor+baileys-v7.0.0-rc10.tgz+89588f7e3fe8b2e6/node_modules/baileys/lib/Signal/Group/sender-key-message.js
491108
492403
  var import_curve2 = __toESM(require_curve2(), 1);
@@ -491227,14 +492522,14 @@ class GroupCipher2 {
491227
492522
  }
491228
492523
  async getPlainText(iv, key, ciphertext) {
491229
492524
  try {
491230
- return import_crypto19.decrypt(key, ciphertext, iv);
492525
+ return import_crypto20.decrypt(key, ciphertext, iv);
491231
492526
  } catch (e) {
491232
492527
  throw new Error("InvalidMessageException");
491233
492528
  }
491234
492529
  }
491235
492530
  async getCipherText(iv, key, plaintext) {
491236
492531
  try {
491237
- return import_crypto19.encrypt(key, plaintext, iv);
492532
+ return import_crypto20.encrypt(key, plaintext, iv);
491238
492533
  } catch (e) {
491239
492534
  throw new Error("InvalidMessageException");
491240
492535
  }
@@ -493922,6 +495217,9 @@ var makeSocket = (config2) => {
493922
495217
  let epoch = 1;
493923
495218
  let keepAliveReq;
493924
495219
  let qrTimer;
495220
+ let passkeyHandoff;
495221
+ let passkeyLinkingCache;
495222
+ let passkeySkipHandoffUX = false;
493925
495223
  let closed = false;
493926
495224
  const socketEndHandlers = [];
493927
495225
  const onUnexpectedError = (err2, msg) => {
@@ -494277,6 +495575,156 @@ var makeSocket = (config2) => {
494277
495575
  const ciphered = aesEncryptCTR(authState.creds.pairingEphemeralKeyPair.public, key, randomIv);
494278
495576
  return Buffer.concat([salt, randomIv, ciphered]);
494279
495577
  }
495578
+ const getPasskeyRequestOptions = async (notification) => {
495579
+ let optionsNode = notification ? getBinaryNodeChild(notification, "passkey_request_options") : undefined;
495580
+ if (!optionsNode) {
495581
+ const response = await query({
495582
+ tag: "iq",
495583
+ attrs: {
495584
+ to: S_WHATSAPP_NET,
495585
+ type: "get",
495586
+ xmlns: "md"
495587
+ },
495588
+ content: [{ tag: "passkey_request_options", attrs: {} }]
495589
+ });
495590
+ optionsNode = getBinaryNodeChild(response, "passkey_request_options");
495591
+ }
495592
+ if (!optionsNode?.content || Array.isArray(optionsNode.content)) {
495593
+ throw new Error("Passkey request did not contain public key options");
495594
+ }
495595
+ return parsePasskeyRequestOptions(Buffer.from(optionsNode.content));
495596
+ };
495597
+ const handlePasskeyRequest = async (notification) => {
495598
+ if (notification.attrs.from !== S_WHATSAPP_NET && notification.attrs.from !== SERVER_JID) {
495599
+ logger5.warn({ from: notification.attrs.from }, "ignored passkey request from unexpected sender");
495600
+ return;
495601
+ }
495602
+ try {
495603
+ const publicKey = await getPasskeyRequestOptions(notification);
495604
+ clearTimeout(qrTimer);
495605
+ passkeyHandoff = {
495606
+ key: derivePasskeyHandoffKey(creds.advSecretKey),
495607
+ createdAt: Date.now()
495608
+ };
495609
+ creds.advSecretKey = randomBytes9(32).toString("base64");
495610
+ ev.emit("creds.update", { advSecretKey: creds.advSecretKey });
495611
+ ev.emit("connection.update", {
495612
+ qr: undefined,
495613
+ passkey: { state: "request", publicKey }
495614
+ });
495615
+ } catch (error) {
495616
+ const message2 = error instanceof Error ? error.message : "Unable to process passkey request";
495617
+ logger5.warn({ err: error }, "failed to process passkey request");
495618
+ ev.emit("connection.update", {
495619
+ passkey: { state: "error", phase: "request", message: message2 }
495620
+ });
495621
+ }
495622
+ };
495623
+ const sendPasskeyResponse = async (input) => {
495624
+ const credential = parsePasskeyCredentialResponse(input);
495625
+ const refResponse = await query({
495626
+ tag: "iq",
495627
+ attrs: {
495628
+ to: S_WHATSAPP_NET,
495629
+ type: "get",
495630
+ xmlns: "md"
495631
+ },
495632
+ content: [{ tag: "ref", attrs: {} }]
495633
+ });
495634
+ const refNode = getBinaryNodeChild(refResponse, "ref");
495635
+ if (!refNode?.content || Array.isArray(refNode.content)) {
495636
+ throw new Error("WhatsApp did not provide a companion reference");
495637
+ }
495638
+ const handoffKey = passkeyHandoff && Date.now() - passkeyHandoff.createdAt < 5 * 60000 ? passkeyHandoff.key : undefined;
495639
+ const prologue = buildPasskeyPrologue({
495640
+ credential,
495641
+ pairingRef: Buffer.from(refNode.content).toString("utf8"),
495642
+ browser,
495643
+ handoffKey,
495644
+ companionNonce: randomBytes9(32)
495645
+ });
495646
+ passkeyLinkingCache = prologue.cache;
495647
+ passkeySkipHandoffUX = prologue.skipHandoffUX;
495648
+ await query({
495649
+ tag: "iq",
495650
+ attrs: {
495651
+ to: S_WHATSAPP_NET,
495652
+ type: "set",
495653
+ xmlns: "md"
495654
+ },
495655
+ content: [{ tag: "passkey_prologue", attrs: {}, content: prologue.content }]
495656
+ });
495657
+ passkeyHandoff = undefined;
495658
+ };
495659
+ const handlePasskeyContinuation = async (notification) => {
495660
+ if (notification.attrs.from !== S_WHATSAPP_NET && notification.attrs.from !== SERVER_JID) {
495661
+ logger5.warn({ from: notification.attrs.from }, "ignored passkey continuation from unexpected sender");
495662
+ return;
495663
+ }
495664
+ try {
495665
+ if (!passkeyLinkingCache) {
495666
+ throw new Error("Passkey continuation arrived without an active ceremony");
495667
+ }
495668
+ const identityNode = getBinaryNodeChild(notification, "primary_ephemeral_identity");
495669
+ if (!identityNode?.content || Array.isArray(identityNode.content)) {
495670
+ throw new Error("Passkey continuation did not contain a primary identity");
495671
+ }
495672
+ const primaryIdentity = proto.PrimaryEphemeralIdentity.decode(Buffer.from(identityNode.content));
495673
+ const continuation = continuePasskeyPairing({ cache: passkeyLinkingCache, primaryIdentity });
495674
+ passkeyLinkingCache = continuation.cache;
495675
+ await query({
495676
+ tag: "iq",
495677
+ attrs: {
495678
+ to: S_WHATSAPP_NET,
495679
+ type: "set",
495680
+ xmlns: "md"
495681
+ },
495682
+ content: [
495683
+ {
495684
+ tag: "companion_nonce",
495685
+ attrs: {},
495686
+ content: passkeyLinkingCache.companionNonce
495687
+ }
495688
+ ]
495689
+ });
495690
+ ev.emit("connection.update", {
495691
+ passkey: {
495692
+ state: "confirmation",
495693
+ code: continuation.code,
495694
+ skipHandoffUX: passkeySkipHandoffUX
495695
+ }
495696
+ });
495697
+ } catch (error) {
495698
+ const message2 = error instanceof Error ? error.message : "Unable to continue passkey pairing";
495699
+ logger5.warn({ err: error }, "failed to process passkey continuation");
495700
+ ev.emit("connection.update", {
495701
+ passkey: { state: "error", phase: "continuation", message: message2 }
495702
+ });
495703
+ }
495704
+ };
495705
+ const sendPasskeyConfirmation = async () => {
495706
+ if (!passkeyLinkingCache) {
495707
+ throw new Error("No passkey confirmation is pending");
495708
+ }
495709
+ const encryptedRequest = buildEncryptedPasskeyPairingRequest({
495710
+ cache: passkeyLinkingCache,
495711
+ noisePublicKey: creds.noiseKey.public,
495712
+ identityPublicKey: creds.signedIdentityKey.public,
495713
+ advSecretKey: creds.advSecretKey,
495714
+ iv: randomBytes9(12)
495715
+ });
495716
+ await query({
495717
+ tag: "iq",
495718
+ attrs: {
495719
+ to: S_WHATSAPP_NET,
495720
+ type: "set",
495721
+ xmlns: "md"
495722
+ },
495723
+ content: [{ tag: "encrypted_pairing_request", attrs: {}, content: encryptedRequest }]
495724
+ });
495725
+ passkeyLinkingCache = undefined;
495726
+ passkeySkipHandoffUX = false;
495727
+ };
494280
495728
  const sendWAMBuffer = (wamBuffer) => {
494281
495729
  return query({
494282
495730
  tag: "iq",
@@ -494306,6 +495754,12 @@ var makeSocket = (config2) => {
494306
495754
  ws.on("error", mapWebSocketError(end));
494307
495755
  ws.on("close", () => void end(new import_boom14.Boom("Connection Terminated", { statusCode: DisconnectReason.connectionClosed })));
494308
495756
  ws.on("CB:xmlstreamend", () => void end(new import_boom14.Boom("Connection Terminated by Server", { statusCode: DisconnectReason.connectionClosed })));
495757
+ ws.on("CB:notification,type:passkey_prologue_request", (notification) => {
495758
+ handlePasskeyRequest(notification);
495759
+ });
495760
+ ws.on("CB:notification,type:crsc_continuation", (notification) => {
495761
+ handlePasskeyContinuation(notification);
495762
+ });
494309
495763
  ws.on("CB:iq,type:set,pair-device", async (stanza) => {
494310
495764
  const iq = {
494311
495765
  tag: "iq",
@@ -494533,6 +495987,8 @@ var makeSocket = (config2) => {
494533
495987
  digestKeyBundle,
494534
495988
  rotateSignedPreKey,
494535
495989
  requestPairingCode,
495990
+ sendPasskeyResponse,
495991
+ sendPasskeyConfirmation,
494536
495992
  updateServerTimeOffset,
494537
495993
  sendUnifiedSession,
494538
495994
  wamBuffer: publicWAMBuffer,
@@ -500063,6 +501519,7 @@ var MAX_QR_ATTEMPTS = 3;
500063
501519
  var qrCycleAttempts = new Map;
500064
501520
  var MAX_QR_CYCLES = 2;
500065
501521
  var activeQrCodes = new Map;
501522
+ var passkeyInstances = new Set;
500066
501523
  var connectionTimeouts = new Map;
500067
501524
  var CONNECTION_TIMEOUT_MS = 45000;
500068
501525
  var authenticatedInstances = new Set;
@@ -500159,6 +501616,7 @@ async function handleConnectionClose(plugin7, instanceId, lastDisconnect, config
500159
501616
  qrCodeAttempts.delete(instanceId);
500160
501617
  qrCycleAttempts.delete(instanceId);
500161
501618
  authenticatedInstances.delete(instanceId);
501619
+ passkeyInstances.delete(instanceId);
500162
501620
  cancelPendingReconnect(instanceId);
500163
501621
  await plugin7.handleDisconnected(instanceId, "Logged out from WhatsApp", false);
500164
501622
  return;
@@ -500206,6 +501664,7 @@ async function handleConnectionOpen(plugin7, instanceId, sock) {
500206
501664
  qrCodeAttempts.delete(instanceId);
500207
501665
  qrCycleAttempts.delete(instanceId);
500208
501666
  activeQrCodes.delete(instanceId);
501667
+ passkeyInstances.delete(instanceId);
500209
501668
  clearConnectionTimeout(instanceId);
500210
501669
  authenticatedInstances.add(instanceId);
500211
501670
  log51.info("Connection opened", { instanceId });
@@ -500213,8 +501672,16 @@ async function handleConnectionOpen(plugin7, instanceId, sock) {
500213
501672
  }
500214
501673
  function setupConnectionHandlers2(sock, plugin7, instanceId, onReconnect, clearAuthAndReconnect, config2 = DEFAULT_RECONNECT_CONFIG2) {
500215
501674
  sock.ev.on("connection.update", async (update) => {
500216
- const { connection, lastDisconnect, qr } = update;
500217
- if (qr) {
501675
+ const { connection, lastDisconnect, passkey: passkey2, qr } = update;
501676
+ if (passkey2) {
501677
+ passkeyInstances.add(instanceId);
501678
+ activeQrCodes.delete(instanceId);
501679
+ qrCodeAttempts.delete(instanceId);
501680
+ qrCycleAttempts.delete(instanceId);
501681
+ clearConnectionTimeout(instanceId);
501682
+ await plugin7.handlePasskeyUpdate(instanceId, passkey2);
501683
+ }
501684
+ if (qr && !passkey2 && !passkeyInstances.has(instanceId)) {
500218
501685
  const shouldContinue = await handleQrCode(plugin7, instanceId, qr, clearAuthAndReconnect);
500219
501686
  if (!shouldContinue) {
500220
501687
  return;
@@ -500241,6 +501708,7 @@ function resetConnectionState2(instanceId) {
500241
501708
  qrCodeAttempts.delete(instanceId);
500242
501709
  qrCycleAttempts.delete(instanceId);
500243
501710
  activeQrCodes.delete(instanceId);
501711
+ passkeyInstances.delete(instanceId);
500244
501712
  authenticatedInstances.delete(instanceId);
500245
501713
  clearConnectionTimeout(instanceId);
500246
501714
  cancelPendingReconnect(instanceId);
@@ -500432,6 +501900,77 @@ function getMediaSize(msg) {
500432
501900
  var log52 = createLogger("whatsapp:messages");
500433
501901
  var fallbackDedupeCache3 = createInboundDedupeCache();
500434
501902
  var downloadGuard6 = createDownloadGuard({ maxSizeBytes: getWhatsAppMediaDownloadMaxBytes() });
501903
+ function joinLines(...parts) {
501904
+ const text = parts.filter((p2) => typeof p2 === "string" && p2.trim().length > 0).join(`
501905
+ `);
501906
+ return text.length > 0 ? text : undefined;
501907
+ }
501908
+ function bracketButtons(labels) {
501909
+ const clean = labels.filter((l) => typeof l === "string" && l.trim().length > 0);
501910
+ return clean.length > 0 ? clean.map((l) => `[${l.trim()}]`).join(" ") : undefined;
501911
+ }
501912
+ function extractListMessageText(list) {
501913
+ const rows = (list.sections ?? []).flatMap((s) => (s.rows ?? []).map((r) => r.title ? `\u2022 ${r.title}` : ""));
501914
+ return joinLines(list.title, list.description, ...rows, list.footerText);
501915
+ }
501916
+ function extractButtonsMessageText(bm) {
501917
+ const labels = bracketButtons((bm.buttons ?? []).map((b2) => b2.buttonText?.displayText));
501918
+ return joinLines(bm.contentText ?? bm.text, bm.footerText, labels);
501919
+ }
501920
+ function extractInteractiveMessageText(im) {
501921
+ const labels = bracketButtons((im.nativeFlowMessage?.buttons ?? []).map((b2) => b2.name));
501922
+ return joinLines(im.header?.title, im.header?.subtitle, im.body?.text, im.footer?.text, labels);
501923
+ }
501924
+ function extractTemplateMessageText(tpl) {
501925
+ const hydrated = tpl.hydratedTemplate ?? tpl.hydratedFourRowTemplate;
501926
+ if (hydrated) {
501927
+ const labels = bracketButtons((hydrated.hydratedButtons ?? []).map((b2) => b2.quickReplyButton?.displayText ?? b2.urlButton?.displayText ?? b2.callButton?.displayText));
501928
+ return joinLines(hydrated.hydratedTitleText, hydrated.hydratedContentText, hydrated.hydratedFooterText, labels);
501929
+ }
501930
+ return tpl.interactiveMessageTemplate ? extractInteractiveMessageText(tpl.interactiveMessageTemplate) : undefined;
501931
+ }
501932
+ function extractInteractiveMediaHeader(container, caption) {
501933
+ if (container.imageMessage) {
501934
+ return {
501935
+ type: "image",
501936
+ caption: caption ?? container.imageMessage.caption ?? undefined,
501937
+ mimeType: container.imageMessage.mimetype ?? "image/jpeg",
501938
+ mediaUrl: container.imageMessage.url ?? undefined
501939
+ };
501940
+ }
501941
+ if (container.videoMessage) {
501942
+ return {
501943
+ type: "video",
501944
+ caption: caption ?? container.videoMessage.caption ?? undefined,
501945
+ mimeType: container.videoMessage.mimetype ?? "video/mp4",
501946
+ mediaUrl: container.videoMessage.url ?? undefined
501947
+ };
501948
+ }
501949
+ if (container.documentMessage) {
501950
+ return {
501951
+ type: "document",
501952
+ filename: container.documentMessage.fileName ?? undefined,
501953
+ caption: caption ?? container.documentMessage.caption ?? undefined,
501954
+ mimeType: container.documentMessage.mimetype ?? "application/octet-stream",
501955
+ mediaUrl: container.documentMessage.url ?? undefined
501956
+ };
501957
+ }
501958
+ return null;
501959
+ }
501960
+ function extractButtonsMessage(bm) {
501961
+ const text = extractButtonsMessageText(bm);
501962
+ return extractInteractiveMediaHeader(bm, text) ?? { type: "text", text };
501963
+ }
501964
+ function extractInteractiveMessage(im) {
501965
+ const text = extractInteractiveMessageText(im);
501966
+ return extractInteractiveMediaHeader(im.header ?? {}, text) ?? { type: "text", text };
501967
+ }
501968
+ function extractTemplateMessage(tpl) {
501969
+ const text = extractTemplateMessageText(tpl);
501970
+ const hydrated = tpl.hydratedTemplate ?? tpl.hydratedFourRowTemplate;
501971
+ const media = hydrated ? extractInteractiveMediaHeader(hydrated, text) : null;
501972
+ return media ?? { type: "text", text };
501973
+ }
500435
501974
  var contentExtractors = [
500436
501975
  {
500437
501976
  check: (m2) => !!m2.conversation,
@@ -500635,6 +502174,25 @@ var contentExtractors = [
500635
502174
  text: m2.buttonsResponseMessage?.selectedDisplayText ?? m2.buttonsResponseMessage?.selectedButtonId ?? undefined
500636
502175
  })
500637
502176
  },
502177
+ {
502178
+ check: (m2) => !!m2.listMessage,
502179
+ extract: (m2) => ({
502180
+ type: "text",
502181
+ text: m2.listMessage ? extractListMessageText(m2.listMessage) : undefined
502182
+ })
502183
+ },
502184
+ {
502185
+ check: (m2) => !!m2.buttonsMessage,
502186
+ extract: (m2) => m2.buttonsMessage ? extractButtonsMessage(m2.buttonsMessage) : null
502187
+ },
502188
+ {
502189
+ check: (m2) => !!m2.interactiveMessage,
502190
+ extract: (m2) => m2.interactiveMessage ? extractInteractiveMessage(m2.interactiveMessage) : null
502191
+ },
502192
+ {
502193
+ check: (m2) => !!m2.templateMessage,
502194
+ extract: (m2) => m2.templateMessage ? extractTemplateMessage(m2.templateMessage) : null
502195
+ },
500638
502196
  {
500639
502197
  check: (m2) => !!m2.senderKeyDistributionMessage,
500640
502198
  extract: () => null
@@ -501439,9 +502997,9 @@ function splitWhatsAppMessage(text, maxLength = DEFAULT_MAX_LENGTH2) {
501439
502997
  flushCurrent();
501440
502998
  current = piece;
501441
502999
  };
501442
- for (const segment of segments) {
501443
- if (segment.type === "text") {
501444
- const paragraphUnits = splitByDelimiter(segment.value, `
503000
+ for (const segment2 of segments) {
503001
+ if (segment2.type === "text") {
503002
+ const paragraphUnits = splitByDelimiter(segment2.value, `
501445
503003
 
501446
503004
  `);
501447
503005
  const textPieces = packUnits(paragraphUnits, maxLength);
@@ -501450,12 +503008,12 @@ function splitWhatsAppMessage(text, maxLength = DEFAULT_MAX_LENGTH2) {
501450
503008
  }
501451
503009
  continue;
501452
503010
  }
501453
- if (segment.value.length <= maxLength) {
501454
- appendPiece(segment.value);
503011
+ if (segment2.value.length <= maxLength) {
503012
+ appendPiece(segment2.value);
501455
503013
  continue;
501456
503014
  }
501457
503015
  flushCurrent();
501458
- const splitCode = splitHugeCodeBlock(segment.value, maxLength);
503016
+ const splitCode = splitHugeCodeBlock(segment2.value, maxLength);
501459
503017
  for (const piece of splitCode) {
501460
503018
  appendPiece(piece);
501461
503019
  }
@@ -502071,6 +503629,7 @@ class WhatsAppPlugin extends BaseChannelPlugin {
502071
503629
  version = "1.0.0";
502072
503630
  capabilities = WHATSAPP_CAPABILITIES;
502073
503631
  sockets = new Map;
503632
+ passkeyStates = new Map;
502074
503633
  pluginConfig = {};
502075
503634
  historySyncCallbacks = new Map;
502076
503635
  historyPushFetchCount = new Map;
@@ -502436,6 +503995,7 @@ class WhatsAppPlugin extends BaseChannelPlugin {
502436
503995
  async onInitialize(_context) {}
502437
503996
  async connect(instanceId, config2) {
502438
503997
  if (config2.options?.forceNewQr === true) {
503998
+ this.passkeyStates.delete(instanceId);
502439
503999
  const existingSocket = this.sockets.get(instanceId);
502440
504000
  if (existingSocket) {
502441
504001
  existingSocket.ev.removeAllListeners("connection.update");
@@ -502529,6 +504089,7 @@ class WhatsAppPlugin extends BaseChannelPlugin {
502529
504089
  await this.emitInstanceDisconnected(instanceId, "User requested disconnect");
502530
504090
  }
502531
504091
  clearInstanceCaches(instanceId) {
504092
+ this.passkeyStates.delete(instanceId);
502532
504093
  this.groupMetadataCache.delete(instanceId);
502533
504094
  this.groupsCache.delete(instanceId);
502534
504095
  this.contactsCache.delete(instanceId);
@@ -502572,6 +504133,91 @@ class WhatsAppPlugin extends BaseChannelPlugin {
502572
504133
  throw new WhatsAppError(ErrorCode2.PAIRING_FAILED, `Failed to request pairing code: ${message2}`);
502573
504134
  }
502574
504135
  }
504136
+ getPasskeyState(instanceId) {
504137
+ return this.passkeyStates.get(instanceId) ?? null;
504138
+ }
504139
+ async submitPasskeyResponse(instanceId, credential) {
504140
+ const sock = this.sockets.get(instanceId);
504141
+ if (!sock) {
504142
+ throw new WhatsAppError(ErrorCode2.NOT_CONNECTED, `Instance ${instanceId} is not connected.`);
504143
+ }
504144
+ const current = this.passkeyStates.get(instanceId);
504145
+ if (current?.state !== "request") {
504146
+ throw new WhatsAppError(ErrorCode2.PAIRING_FAILED, "No passkey authentication is pending.");
504147
+ }
504148
+ this.passkeyStates.set(instanceId, { state: "confirming", requestedAt: current.requestedAt });
504149
+ try {
504150
+ await sock.sendPasskeyResponse(credential);
504151
+ } catch (error) {
504152
+ this.passkeyStates.set(instanceId, current);
504153
+ throw error;
504154
+ }
504155
+ }
504156
+ async confirmPasskey(instanceId) {
504157
+ const sock = this.sockets.get(instanceId);
504158
+ if (!sock) {
504159
+ throw new WhatsAppError(ErrorCode2.NOT_CONNECTED, `Instance ${instanceId} is not connected.`);
504160
+ }
504161
+ const current = this.passkeyStates.get(instanceId);
504162
+ if (current?.state !== "confirmation") {
504163
+ throw new WhatsAppError(ErrorCode2.PAIRING_FAILED, "No passkey confirmation is pending.");
504164
+ }
504165
+ this.passkeyStates.set(instanceId, { state: "confirming", requestedAt: current.requestedAt });
504166
+ try {
504167
+ await sock.sendPasskeyConfirmation();
504168
+ } catch (error) {
504169
+ this.passkeyStates.set(instanceId, current);
504170
+ throw error;
504171
+ }
504172
+ }
504173
+ async handlePasskeyUpdate(instanceId, update) {
504174
+ const requestedAt = new Date().toISOString();
504175
+ if (update.state === "request") {
504176
+ this.passkeyStates.set(instanceId, { state: "request", publicKey: update.publicKey, requestedAt });
504177
+ return;
504178
+ }
504179
+ if (update.state === "error") {
504180
+ this.passkeyStates.set(instanceId, {
504181
+ state: "error",
504182
+ phase: update.phase,
504183
+ message: update.message,
504184
+ requestedAt
504185
+ });
504186
+ return;
504187
+ }
504188
+ if (update.skipHandoffUX) {
504189
+ this.passkeyStates.set(instanceId, { state: "confirming", requestedAt });
504190
+ const sock = this.sockets.get(instanceId);
504191
+ if (!sock) {
504192
+ this.passkeyStates.set(instanceId, {
504193
+ state: "error",
504194
+ phase: "continuation",
504195
+ message: "WhatsApp connection ended before passkey confirmation.",
504196
+ requestedAt
504197
+ });
504198
+ return;
504199
+ }
504200
+ try {
504201
+ await sock.sendPasskeyConfirmation();
504202
+ } catch (error) {
504203
+ const message2 = error instanceof Error ? error.message : "Unable to confirm passkey pairing.";
504204
+ this.passkeyStates.set(instanceId, {
504205
+ state: "error",
504206
+ phase: "continuation",
504207
+ message: message2,
504208
+ requestedAt
504209
+ });
504210
+ this.logger.warn("Failed to auto-confirm passkey pairing", { instanceId, error: message2 });
504211
+ }
504212
+ return;
504213
+ }
504214
+ this.passkeyStates.set(instanceId, {
504215
+ state: "confirmation",
504216
+ code: update.code,
504217
+ requiresUserConfirmation: true,
504218
+ requestedAt
504219
+ });
504220
+ }
502575
504221
  buildQuotedOptions(message2, jid) {
502576
504222
  if (!message2.replyTo)
502577
504223
  return;
@@ -503496,6 +505142,7 @@ class WhatsAppPlugin extends BaseChannelPlugin {
503496
505142
  }
503497
505143
  }
503498
505144
  async handleConnected(instanceId, sock) {
505145
+ this.passkeyStates.delete(instanceId);
503499
505146
  let profileName;
503500
505147
  let profilePicUrl;
503501
505148
  let ownerIdentifier;