@automagik/omni 2.260804.3 → 2.260830.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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(),
@@ -138240,11 +138242,11 @@ var require_applyToDefaults = __commonJS((exports, module) => {
138240
138242
  return Merge(copy, source, { nullOverride, mergeArrays: false });
138241
138243
  };
138242
138244
  internals.reachCopy = function(dst, src, path) {
138243
- for (const segment of path) {
138244
- if (!(segment in src)) {
138245
+ for (const segment2 of path) {
138246
+ if (!(segment2 in src)) {
138245
138247
  return;
138246
138248
  }
138247
- const val = src[segment];
138249
+ const val = src[segment2];
138248
138250
  if (typeof val !== "object" || val === null) {
138249
138251
  return;
138250
138252
  }
@@ -138253,11 +138255,11 @@ var require_applyToDefaults = __commonJS((exports, module) => {
138253
138255
  const value = src;
138254
138256
  let ref = dst;
138255
138257
  for (let i = 0;i < path.length - 1; ++i) {
138256
- const segment = path[i];
138257
- if (typeof ref[segment] !== "object") {
138258
- ref[segment] = {};
138258
+ const segment2 = path[i];
138259
+ if (typeof ref[segment2] !== "object") {
138260
+ ref[segment2] = {};
138259
138261
  }
138260
- ref = ref[segment];
138262
+ ref = ref[segment2];
138261
138263
  }
138262
138264
  ref[path[path.length - 1]] = value;
138263
138265
  };
@@ -156556,11 +156558,11 @@ var require_applyToDefaults2 = __commonJS((exports, module) => {
156556
156558
  return Merge(copy, source, { nullOverride, mergeArrays: false });
156557
156559
  };
156558
156560
  internals.reachCopy = function(dst, src, path) {
156559
- for (const segment of path) {
156560
- if (!(segment in src)) {
156561
+ for (const segment2 of path) {
156562
+ if (!(segment2 in src)) {
156561
156563
  return;
156562
156564
  }
156563
- const val = src[segment];
156565
+ const val = src[segment2];
156564
156566
  if (typeof val !== "object" || val === null) {
156565
156567
  return;
156566
156568
  }
@@ -156569,11 +156571,11 @@ var require_applyToDefaults2 = __commonJS((exports, module) => {
156569
156571
  const value = src;
156570
156572
  let ref = dst;
156571
156573
  for (let i = 0;i < path.length - 1; ++i) {
156572
- const segment = path[i];
156573
- if (typeof ref[segment] !== "object") {
156574
- ref[segment] = {};
156574
+ const segment2 = path[i];
156575
+ if (typeof ref[segment2] !== "object") {
156576
+ ref[segment2] = {};
156575
156577
  }
156576
- ref = ref[segment];
156578
+ ref = ref[segment2];
156577
156579
  }
156578
156580
  ref[path[path.length - 1]] = value;
156579
156581
  };
@@ -164339,6 +164341,9 @@ __export(exports_schema, {
164339
164341
  settingValueTypes: () => settingValueTypes,
164340
164342
  settingChangeHistoryRelations: () => settingChangeHistoryRelations,
164341
164343
  settingChangeHistory: () => settingChangeHistory,
164344
+ scheduledMessages: () => scheduledMessages,
164345
+ scheduledMessageStatuses: () => scheduledMessageStatuses,
164346
+ scheduledMessageDeliveryModes: () => scheduledMessageDeliveryModes,
164342
164347
  ruleTypes: () => ruleTypes,
164343
164348
  replyFilterMode: () => replyFilterMode,
164344
164349
  providerSchemas: () => providerSchemas,
@@ -164439,7 +164444,7 @@ __export(exports_schema, {
164439
164444
  accessRules: () => accessRules,
164440
164445
  accessModes: () => accessModes
164441
164446
  });
164442
- 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;
164443
164448
  var init_schema2 = __esm(() => {
164444
164449
  init_events();
164445
164450
  init_types5();
@@ -164514,6 +164519,8 @@ var init_schema2 = __esm(() => {
164514
164519
  ];
164515
164520
  messageStatuses = ["active", "edited", "deleted", "expired"];
164516
164521
  deliveryStatuses = ["pending", "sent", "delivered", "read", "failed"];
164522
+ scheduledMessageDeliveryModes = ["platform", "local"];
164523
+ scheduledMessageStatuses = ["pending", "sending", "sent", "canceled", "failed"];
164517
164524
  jobStatuses = ["pending", "running", "completed", "failed", "cancelled"];
164518
164525
  providerSchemas = [
164519
164526
  "agno",
@@ -164716,6 +164723,8 @@ var init_schema2 = __esm(() => {
164716
164723
  guildConfigOverrides: jsonb("guild_config_overrides").$type(),
164717
164724
  discordPresence: jsonb("discord_presence").$type(),
164718
164725
  slackBotToken: text("slack_bot_token"),
164726
+ slackUserToken: text("slack_user_token"),
164727
+ slackAuthMode: varchar("slack_auth_mode", { length: 10 }),
164719
164728
  slackAppToken: text("slack_app_token"),
164720
164729
  slackSigningSecret: text("slack_signing_secret"),
164721
164730
  telegramBotToken: text("telegram_bot_token"),
@@ -164860,6 +164869,31 @@ var init_schema2 = __esm(() => {
164860
164869
  }, (t) => ({
164861
164870
  instanceUnique: uniqueIndex("idx_wa_flow_keys_instance").on(t.instanceId)
164862
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
+ }));
164863
164897
  persons = pgTable("persons", {
164864
164898
  id: uuid("id").primaryKey().defaultRandom(),
164865
164899
  displayName: varchar("display_name", { length: 255 }),
@@ -165044,6 +165078,15 @@ var init_schema2 = __esm(() => {
165044
165078
  replyToExternalId: varchar("reply_to_external_id", { length: 255 }),
165045
165079
  quotedText: text("quoted_text"),
165046
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 }),
165047
165090
  forwardedFromMessageId: uuid("forwarded_from_message_id"),
165048
165091
  forwardedFromExternalId: varchar("forwarded_from_external_id", { length: 255 }),
165049
165092
  forwardCount: integer("forward_count").notNull().default(0),
@@ -165081,6 +165124,8 @@ var init_schema2 = __esm(() => {
165081
165124
  platformTimestampIdx: index("messages_platform_timestamp_idx").on(table3.platformTimestamp),
165082
165125
  replyToIdx: index("messages_reply_to_idx").on(table3.replyToMessageId),
165083
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),
165084
165129
  hasMediaIdx: index("messages_has_media_idx").on(table3.hasMedia),
165085
165130
  originalEventIdx: index("messages_original_event_idx").on(table3.originalEventId)
165086
165131
  }));
@@ -169982,6 +170027,9 @@ __export(exports_src3, {
169982
170027
  settingChangeHistoryRelations: () => settingChangeHistoryRelations,
169983
170028
  settingChangeHistory: () => settingChangeHistory,
169984
170029
  scrubDdlCredential: () => scrubDdlCredential,
170030
+ scheduledMessages: () => scheduledMessages,
170031
+ scheduledMessageStatuses: () => scheduledMessageStatuses,
170032
+ scheduledMessageDeliveryModes: () => scheduledMessageDeliveryModes,
169985
170033
  ruleTypes: () => ruleTypes,
169986
170034
  roleAttributeViolations: () => roleAttributeViolations,
169987
170035
  revertTenantRlsEnforcement: () => revertTenantRlsEnforcement,
@@ -171390,6 +171438,7 @@ var init_plugin = __esm(() => {
171390
171438
  type: content.type,
171391
171439
  text: content.text ?? content.caption,
171392
171440
  mediaUrl: content.mediaUrl,
171441
+ mediaId: content.mediaId,
171393
171442
  mimeType: content.mimeType,
171394
171443
  isVoiceNote: content.isVoiceNote
171395
171444
  },
@@ -242836,7 +242885,7 @@ var init_sentry_scrub = __esm(() => {
242836
242885
  var require_package7 = __commonJS((exports, module) => {
242837
242886
  module.exports = {
242838
242887
  name: "@omni/api",
242839
- version: "2.260804.3",
242888
+ version: "2.260830.2",
242840
242889
  type: "module",
242841
242890
  exports: {
242842
242891
  ".": {
@@ -278805,7 +278854,7 @@ var require_channelz = __commonJS((exports) => {
278805
278854
  return (0, net_1.isIPv6)(ipAddress) && ipAddress.toLowerCase().startsWith("::ffff:") && (0, net_1.isIPv4)(ipAddress.substring(7));
278806
278855
  }
278807
278856
  function ipv4AddressStringToBuffer(ipAddress) {
278808
- 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))));
278809
278858
  }
278810
278859
  function ipAddressStringToBuffer(ipAddress) {
278811
278860
  if ((0, net_1.isIPv4)(ipAddress)) {
@@ -289942,21 +289991,21 @@ var splitPath2 = (path2) => {
289942
289991
  const segments = path2.split("/");
289943
289992
  const results = [];
289944
289993
  let basePath = "";
289945
- segments.forEach((segment) => {
289946
- if (segment !== "" && !/\:/.test(segment)) {
289947
- basePath += "/" + segment;
289948
- } else if (/\:/.test(segment)) {
289949
- if (/\?/.test(segment)) {
289994
+ segments.forEach((segment2) => {
289995
+ if (segment2 !== "" && !/\:/.test(segment2)) {
289996
+ basePath += "/" + segment2;
289997
+ } else if (/\:/.test(segment2)) {
289998
+ if (/\?/.test(segment2)) {
289950
289999
  if (results.length === 0 && basePath === "") {
289951
290000
  results.push("/");
289952
290001
  } else {
289953
290002
  results.push(basePath);
289954
290003
  }
289955
- const optionalSegment = segment.replace("?", "");
290004
+ const optionalSegment = segment2.replace("?", "");
289956
290005
  basePath += "/" + optionalSegment;
289957
290006
  results.push(basePath);
289958
290007
  } else {
289959
- basePath += "/" + segment;
290008
+ basePath += "/" + segment2;
289960
290009
  }
289961
290010
  }
289962
290011
  });
@@ -291488,11 +291537,11 @@ var defaultJoin = (...paths) => {
291488
291537
  result = result.replace(/(?<=\/)\/+/g, "");
291489
291538
  const segments = result.split("/");
291490
291539
  const resolved = [];
291491
- for (const segment of segments) {
291492
- if (segment === ".." && resolved.length > 0 && resolved.at(-1) !== "..") {
291540
+ for (const segment2 of segments) {
291541
+ if (segment2 === ".." && resolved.length > 0 && resolved.at(-1) !== "..") {
291493
291542
  resolved.pop();
291494
- } else if (segment !== ".") {
291495
- resolved.push(segment);
291543
+ } else if (segment2 !== ".") {
291544
+ resolved.push(segment2);
291496
291545
  }
291497
291546
  }
291498
291547
  return resolved.join("/") || ".";
@@ -293281,9 +293330,13 @@ var init_profiles = __esm(() => {
293281
293330
  });
293282
293331
 
293283
293332
  // ../api/src/middleware/output-redactor.ts
293333
+ import { createHash as createHash12 } from "crypto";
293284
293334
  function escapeRegex(literal) {
293285
293335
  return literal.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
293286
293336
  }
293337
+ function hashPattern(source) {
293338
+ return createHash12("sha256").update(source).digest("hex").slice(0, 12);
293339
+ }
293287
293340
  function compilePatterns(literals) {
293288
293341
  const out = [];
293289
293342
  for (const literal of literals) {
@@ -293453,7 +293506,7 @@ async function emitRedactionEvents(c, apiKey, presetKey, hits) {
293453
293506
  keyId: apiKey.id,
293454
293507
  profile: apiKey.profile ?? null,
293455
293508
  presetKey,
293456
- pattern: hit.pattern,
293509
+ patternHash: hashPattern(hit.pattern),
293457
293510
  field: hit.field,
293458
293511
  count: hit.count,
293459
293512
  route: c.req.path,
@@ -293462,7 +293515,7 @@ async function emitRedactionEvents(c, apiKey, presetKey, hits) {
293462
293515
  } catch (err2) {
293463
293516
  log60.warn("output-redactor: failed to publish secret.redacted", {
293464
293517
  keyId: apiKey.id,
293465
- pattern: hit.pattern,
293518
+ patternHash: hashPattern(hit.pattern),
293466
293519
  error: String(err2)
293467
293520
  });
293468
293521
  }
@@ -293653,6 +293706,9 @@ var init_scopes = __esm(() => {
293653
293706
  "GET /instances/:id/status": "instances:read",
293654
293707
  "GET /instances/:id/qr": "instances:read",
293655
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",
293656
293712
  "POST /instances/:id/connect": "instances:write",
293657
293713
  "POST /instances/:id/disconnect": "instances:write",
293658
293714
  "POST /instances/:id/restart": "instances:write",
@@ -294377,17 +294433,18 @@ var init_route_ownership = __esm(() => {
294377
294433
  "DELETE /api/v2/follow-up/chats/:id",
294378
294434
  "DELETE /api/v2/follow-up/instances/:id",
294379
294435
  "DELETE /api/v2/instances/:id",
294380
- "DELETE /api/v2/instances/:id/whatsapp-flows/:flowId",
294381
294436
  "DELETE /api/v2/instances/:id/block",
294382
294437
  "DELETE /api/v2/instances/:id/guilds/:guildId/config",
294383
294438
  "DELETE /api/v2/instances/:id/profile/picture",
294384
294439
  "DELETE /api/v2/instances/:id/whatsapp-business/connection",
294440
+ "DELETE /api/v2/instances/:id/whatsapp-flows/:flowId",
294385
294441
  "DELETE /api/v2/instances/:id/whatsapp-templates/:templateId",
294386
294442
  "DELETE /api/v2/instances/:instanceId/routes/:id",
294387
294443
  "DELETE /api/v2/messages/:id",
294388
294444
  "DELETE /api/v2/messages/:id/reactions",
294389
294445
  "DELETE /api/v2/messages/:id/star",
294390
294446
  "DELETE /api/v2/providers/:id",
294447
+ "DELETE /api/v2/scheduled-messages/:id",
294391
294448
  "DELETE /api/v2/settings/:key",
294392
294449
  "DELETE /api/v2/webhook-sources/:id",
294393
294450
  "GET /api/v2",
@@ -294456,6 +294513,7 @@ var init_route_ownership = __esm(() => {
294456
294513
  "GET /api/v2/instances/:id/guilds/:guildId/audit",
294457
294514
  "GET /api/v2/instances/:id/guilds/:guildId/config",
294458
294515
  "GET /api/v2/instances/:id/pairing-requests",
294516
+ "GET /api/v2/instances/:id/passkey",
294459
294517
  "GET /api/v2/instances/:id/privacy",
294460
294518
  "GET /api/v2/instances/:id/qr",
294461
294519
  "GET /api/v2/instances/:id/status",
@@ -294480,6 +294538,7 @@ var init_route_ownership = __esm(() => {
294480
294538
  "GET /api/v2/media/:instanceId/*",
294481
294539
  "GET /api/v2/messages",
294482
294540
  "GET /api/v2/messages/:id",
294541
+ "GET /api/v2/messages/:id/permalink",
294483
294542
  "GET /api/v2/messages/by-external",
294484
294543
  "GET /api/v2/messages/tts/voices",
294485
294544
  "GET /api/v2/payload-config",
@@ -294495,9 +294554,12 @@ var init_route_ownership = __esm(() => {
294495
294554
  "GET /api/v2/providers/:id/teams",
294496
294555
  "GET /api/v2/providers/:id/workflows",
294497
294556
  "GET /api/v2/routes/metrics",
294557
+ "GET /api/v2/scheduled-messages",
294558
+ "GET /api/v2/scheduled-messages/:id",
294498
294559
  "GET /api/v2/settings",
294499
294560
  "GET /api/v2/settings/:key",
294500
294561
  "GET /api/v2/settings/:key/history",
294562
+ "GET /api/v2/slack/search",
294501
294563
  "GET /api/v2/turns",
294502
294564
  "GET /api/v2/turns/:id",
294503
294565
  "GET /api/v2/turns/stats",
@@ -294589,6 +294651,8 @@ var init_route_ownership = __esm(() => {
294589
294651
  "POST /api/v2/instances/:id/groups/join",
294590
294652
  "POST /api/v2/instances/:id/logout",
294591
294653
  "POST /api/v2/instances/:id/pair",
294654
+ "POST /api/v2/instances/:id/passkey/confirm",
294655
+ "POST /api/v2/instances/:id/passkey/response",
294592
294656
  "POST /api/v2/instances/:id/pairing-requests/:requestId/action",
294593
294657
  "POST /api/v2/instances/:id/replay",
294594
294658
  "POST /api/v2/instances/:id/restart",
@@ -294643,6 +294707,8 @@ var init_route_ownership = __esm(() => {
294643
294707
  "POST /api/v2/persons/unlink",
294644
294708
  "POST /api/v2/providers",
294645
294709
  "POST /api/v2/providers/:id/health",
294710
+ "POST /api/v2/scheduled-messages",
294711
+ "POST /api/v2/slack/dm/open",
294646
294712
  "POST /api/v2/turns/:id/close",
294647
294713
  "POST /api/v2/turns/close",
294648
294714
  "POST /api/v2/turns/close-all",
@@ -294661,9 +294727,9 @@ var init_route_ownership = __esm(() => {
294661
294727
  "PUT /api/v2/instances/:id/presence",
294662
294728
  "PUT /api/v2/instances/:id/profile/name",
294663
294729
  "PUT /api/v2/instances/:id/profile/picture",
294730
+ "PUT /api/v2/instances/:id/profile/status",
294664
294731
  "PUT /api/v2/instances/:id/whatsapp-business/profile",
294665
294732
  "PUT /api/v2/instances/:id/whatsapp-flows/:flowId",
294666
- "PUT /api/v2/instances/:id/profile/status",
294667
294733
  "PUT /api/v2/payload-config/:eventType",
294668
294734
  "PUT /api/v2/settings/:key"
294669
294735
  ];
@@ -316521,10 +316587,10 @@ var import_p_retry, import_google_auth_library, _defaultBaseGeminiUrl = undefine
316521
316587
  invalidSegments.sort((a, b3) => a.start - b3.start);
316522
316588
  if (invalidSegments.length > 0) {
316523
316589
  let lastEnd = 0;
316524
- const underline = invalidSegments.reduce((acc, segment) => {
316525
- const spaces = " ".repeat(segment.start - lastEnd);
316526
- const arrows = "^".repeat(segment.length);
316527
- 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;
316528
316594
  return acc + spaces + arrows;
316529
316595
  }, "");
316530
316596
  throw new GeminiNextGenAPIClientError(`Path parameters result in path with invalid segments:
@@ -322500,10 +322566,10 @@ function extractSegments(buffer3) {
322500
322566
  const splitIndex = remaining.indexOf(`
322501
322567
 
322502
322568
  `);
322503
- const segment = remaining.slice(0, splitIndex).trim();
322569
+ const segment2 = remaining.slice(0, splitIndex).trim();
322504
322570
  remaining = remaining.slice(splitIndex + 2);
322505
- if (segment)
322506
- segments.push(segment);
322571
+ if (segment2)
322572
+ segments.push(segment2);
322507
322573
  }
322508
322574
  return [segments, remaining];
322509
322575
  }
@@ -322518,8 +322584,8 @@ async function* processStreamChunks(streamGenerator, enableSplit) {
322518
322584
  `)) {
322519
322585
  const [segments, remaining] = extractSegments(buffer3);
322520
322586
  buffer3 = remaining;
322521
- for (const segment of segments) {
322522
- yield segment;
322587
+ for (const segment2 of segments) {
322588
+ yield segment2;
322523
322589
  }
322524
322590
  }
322525
322591
  if (chunk2.isComplete && buffer3.trim()) {
@@ -327231,12 +327297,12 @@ var is_array, hex_table, limit = 1024, encode3 = (str, _defaultEncoder, charset,
327231
327297
  }
327232
327298
  let out = "";
327233
327299
  for (let j2 = 0;j2 < string.length; j2 += limit) {
327234
- const segment = string.length >= limit ? string.slice(j2, j2 + limit) : string;
327300
+ const segment2 = string.length >= limit ? string.slice(j2, j2 + limit) : string;
327235
327301
  const arr = [];
327236
- for (let i = 0;i < segment.length; ++i) {
327237
- let c = segment.charCodeAt(i);
327302
+ for (let i = 0;i < segment2.length; ++i) {
327303
+ let c = segment2.charCodeAt(i);
327238
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)) {
327239
- arr[arr.length] = segment.charAt(i);
327305
+ arr[arr.length] = segment2.charAt(i);
327240
327306
  continue;
327241
327307
  }
327242
327308
  if (c < 128) {
@@ -327252,7 +327318,7 @@ var is_array, hex_table, limit = 1024, encode3 = (str, _defaultEncoder, charset,
327252
327318
  continue;
327253
327319
  }
327254
327320
  i += 1;
327255
- c = 65536 + ((c & 1023) << 10 | segment.charCodeAt(i) & 1023);
327321
+ c = 65536 + ((c & 1023) << 10 | segment2.charCodeAt(i) & 1023);
327256
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];
327257
327323
  }
327258
327324
  out += arr.join("");
@@ -346313,6 +346379,7 @@ var init_instances = __esm(() => {
346313
346379
  SEALED_CREDENTIAL_COLUMNS = [
346314
346380
  "discordBotToken",
346315
346381
  "slackBotToken",
346382
+ "slackUserToken",
346316
346383
  "slackAppToken",
346317
346384
  "slackSigningSecret",
346318
346385
  "telegramBotToken",
@@ -346468,7 +346535,7 @@ class MessageService {
346468
346535
  return result;
346469
346536
  }
346470
346537
  async hasBotRepliedInThread(chatId, threadExternalId) {
346471
- 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);
346472
346539
  return !!result;
346473
346540
  }
346474
346541
  async getByExternalId(chatId, externalId) {
@@ -346557,6 +346624,8 @@ class MessageService {
346557
346624
  quotedSenderName: options.quotedSenderName,
346558
346625
  isForwarded: options.isForwarded ?? false,
346559
346626
  forwardedFromExternalId: options.forwardedFromExternalId,
346627
+ threadExternalId: options.threadExternalId,
346628
+ isThreadBroadcast: options.isThreadBroadcast ?? false,
346560
346629
  rawPayload: options.rawPayload,
346561
346630
  originalEventId: options.originalEventId,
346562
346631
  latestEventId: options.originalEventId
@@ -346760,6 +346829,19 @@ class MessageService {
346760
346829
  const [replyToMessage] = await this.db.select({ id: messages2.id }).from(messages2).where(and2(eq(messages2.chatId, chatId), eq(messages2.externalId, replyToExternalId))).limit(1);
346761
346830
  return replyToMessage?.id ?? null;
346762
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
+ }
346763
346845
  async updateReplyToReference(id, replyToMessageId) {
346764
346846
  await this.db.update(messages2).set({ replyToMessageId, updatedAt: new Date }).where(eq(messages2.id, id));
346765
346847
  }
@@ -346941,7 +347023,92 @@ var init_payload_store2 = __esm(() => {
346941
347023
  log97 = createLogger("payload-store");
346942
347024
  });
346943
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
+
346944
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
+
346945
347112
  class PersonService {
346946
347113
  pool;
346947
347114
  eventBus;
@@ -347034,8 +347201,8 @@ class PersonService {
347034
347201
  `);
347035
347202
  return result;
347036
347203
  }
347037
- async getById(id) {
347038
- 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);
347039
347206
  if (!result) {
347040
347207
  throw new NotFoundError("Person", id);
347041
347208
  }
@@ -347101,8 +347268,8 @@ class PersonService {
347101
347268
  const [mapping] = await this.db.select({ phoneId: chatIdMappings.phoneId }).from(chatIdMappings).where(and2(eq(chatIdMappings.instanceId, instanceId), eq(chatIdMappings.lidId, lidId))).limit(1);
347102
347269
  return mapping?.phoneId ?? null;
347103
347270
  }
347104
- async findPersonByPhone(phone) {
347105
- 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);
347106
347273
  return existing ? { personId: existing.id, wasLinked: true } : null;
347107
347274
  }
347108
347275
  async findPersonToLink(linkOptions) {
@@ -347135,9 +347302,9 @@ class PersonService {
347135
347302
  return { wasLinked: false };
347136
347303
  return this.createPersonWithConflictHandling(linkOptions);
347137
347304
  }
347138
- async createPersonWithConflictHandling(linkOptions) {
347305
+ async createPersonWithConflictHandling(linkOptions, handle = this.db) {
347139
347306
  try {
347140
- const [newPerson] = await this.db.insert(persons).values({
347307
+ const [newPerson] = await handle.insert(persons).values({
347141
347308
  displayName: linkOptions.displayName,
347142
347309
  primaryPhone: linkOptions.matchByPhone,
347143
347310
  primaryEmail: linkOptions.matchByEmail
@@ -347145,17 +347312,17 @@ class PersonService {
347145
347312
  if (newPerson)
347146
347313
  return { personId: newPerson.id, wasLinked: false };
347147
347314
  if (linkOptions.matchByPhone) {
347148
- return await this.findPersonByPhone(linkOptions.matchByPhone) ?? { wasLinked: false };
347315
+ return await this.findPersonByPhone(linkOptions.matchByPhone, handle) ?? { wasLinked: false };
347149
347316
  }
347150
347317
  } catch {
347151
347318
  if (linkOptions.matchByPhone) {
347152
- return await this.findPersonByPhone(linkOptions.matchByPhone) ?? { wasLinked: false };
347319
+ return await this.findPersonByPhone(linkOptions.matchByPhone, handle) ?? { wasLinked: false };
347153
347320
  }
347154
347321
  }
347155
347322
  return { wasLinked: false };
347156
347323
  }
347157
- async updateExistingIdentity(existing, data) {
347158
- const [updated] = await this.db.update(platformIdentities).set({
347324
+ async updateExistingIdentity(existing, data, handle = this.db) {
347325
+ const [updated] = await handle.update(platformIdentities).set({
347159
347326
  platformUsername: data.platformUsername ?? existing.platformUsername,
347160
347327
  profilePicUrl: data.profilePicUrl ?? existing.profilePicUrl,
347161
347328
  profileData: data.profileData ?? existing.profileData,
@@ -347167,23 +347334,24 @@ class PersonService {
347167
347334
  }
347168
347335
  let person2 = null;
347169
347336
  if (updated.personId) {
347170
- person2 = await this.getById(updated.personId);
347337
+ person2 = await this.getById(updated.personId, handle);
347171
347338
  }
347172
347339
  return { identity: updated, person: person2, isNew: false, wasLinked: false };
347173
347340
  }
347174
- async findOrCreateIdentity(data, linkOptions) {
347175
- const instanceId = data.instanceId;
347341
+ async findOrCreateIdentity(rawData, rawLinkOptions) {
347342
+ const instanceId = rawData.instanceId;
347176
347343
  if (!instanceId) {
347177
347344
  throw new Error("instanceId is required");
347178
347345
  }
347346
+ const { data, linkOptions } = canonicalizeIdentityInput(rawData, rawLinkOptions);
347179
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);
347180
347348
  if (existing[0]) {
347181
347349
  return this.updateExistingIdentity(existing[0], data);
347182
347350
  }
347183
347351
  let personId = data.personId;
347184
347352
  let wasLinked = false;
347353
+ let resolvedLinkOptions = linkOptions;
347185
347354
  if (!personId && linkOptions) {
347186
- let resolvedLinkOptions = linkOptions;
347187
347355
  if (!linkOptions.matchByPhone && data.platformUserId.endsWith("@lid") && instanceId) {
347188
347356
  const phoneFromLid = await this.resolvePhoneFromLid(instanceId, data.platformUserId);
347189
347357
  if (phoneFromLid) {
@@ -347191,31 +347359,63 @@ class PersonService {
347191
347359
  resolvedLinkOptions = { ...linkOptions, matchByPhone: phoneNumber };
347192
347360
  }
347193
347361
  }
347194
- const linkResult = await this.findPersonToLink({ ...resolvedLinkOptions, instanceId });
347362
+ const linkResult = await this.findPersonToLink({ ...resolvedLinkOptions, instanceId, createPerson: false });
347195
347363
  personId = linkResult.personId;
347196
347364
  wasLinked = linkResult.wasLinked;
347197
347365
  }
347198
347366
  const matchType = linkOptions?.matchByPhone ? "phone" : linkOptions?.matchByEmail ? "email" : "platform_id";
347199
347367
  const linkedBy = wasLinked ? `${matchType}_match` : personId ? "initial" : undefined;
347200
347368
  const linkReason = wasLinked ? `Matched by ${matchType}` : undefined;
347201
- const [created] = await this.db.insert(platformIdentities).values({
347202
- ...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,
347203
347376
  personId,
347377
+ wasLinked,
347204
347378
  linkedBy,
347379
+ linkReason,
347205
347380
  confidence: wasLinked ? 90 : 100,
347206
- linkReason
347207
- }).returning();
347208
- if (!created) {
347209
- throw new Error("Failed to create identity");
347210
- }
347211
- let person2 = null;
347212
- if (personId) {
347213
- person2 = await this.getById(personId);
347214
- }
347215
- 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
+ });
347216
347415
  }
347217
347416
  async getIdentityByPlatformId(channel5, instanceId, platformUserId) {
347218
- 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);
347219
347419
  return identity || null;
347220
347420
  }
347221
347421
  async listIdentitiesByInstance(instanceId, options = {}) {
@@ -347392,6 +347592,7 @@ var init_persons = __esm(() => {
347392
347592
  init_src5();
347393
347593
  init_drizzle_orm();
347394
347594
  init_tenant_scope();
347595
+ init_canonical_handle();
347395
347596
  });
347396
347597
 
347397
347598
  // ../api/src/lib/agent-key-name.ts
@@ -348363,16 +348564,6 @@ function buildSentChatPreview(payload) {
348363
348564
  const badge = payload.content.type !== "text" ? MEDIA_BADGES2[payload.content.type] ?? `[${payload.content.type}]` : "";
348364
348565
  return badge ? text3 ? `${badge} ${text3}` : badge : text3;
348365
348566
  }
348366
- function extractPhoneFromSender(senderId, channel5) {
348367
- if (!channel5.startsWith("whatsapp"))
348368
- return;
348369
- const bare = senderId.split("@")[0] || senderId;
348370
- if (!/^\d+$/.test(bare))
348371
- return;
348372
- if (bare.length < 7 || bare.length > 15)
348373
- return;
348374
- return `+${bare}`;
348375
- }
348376
348567
  async function processSenderIdentity(services, payload, metadata, channel5, trustedTenantId) {
348377
348568
  if (metadata.platformIdentityId) {
348378
348569
  return { personId: metadata.personId, platformIdentityId: metadata.platformIdentityId };
@@ -348380,11 +348571,15 @@ async function processSenderIdentity(services, payload, metadata, channel5, trus
348380
348571
  if (!payload.from) {
348381
348572
  return { personId: metadata.personId, platformIdentityId: undefined };
348382
348573
  }
348574
+ if (isPersonlessChannel(channel5)) {
348575
+ return { personId: metadata.personId, platformIdentityId: metadata.platformIdentityId };
348576
+ }
348383
348577
  const displayName = truncate4(payload.senderName ?? payload.rawPayload?.pushName, 255);
348384
- const platformUserId = truncate4(payload.from, 255) ?? payload.from;
348578
+ const canonical = canonicalizeHandle(channel5, payload.from);
348579
+ const platformUserId = truncate4(canonical.platformUserId, 255) ?? canonical.platformUserId;
348385
348580
  const isLidAddressed = payload.rawPayload?.addressingMode === "lid" || payload.rawPayload?.senderIsLid === true;
348386
348581
  const resolvedPhone = isLidAddressed ? payload.rawPayload?.resolvedSenderPhone : undefined;
348387
- const phoneNumber = isLidAddressed ? resolvedPhone ? `+${resolvedPhone}` : undefined : extractPhoneFromSender(platformUserId, channel5);
348582
+ const phoneNumber = isLidAddressed ? resolvedPhone ? `+${resolvedPhone}` : undefined : canonical.phone;
348388
348583
  const { identity, person: person2, isNew } = await services.persons.findOrCreateIdentity({ channel: channel5, instanceId: metadata.instanceId, platformUserId, platformUsername: displayName }, {
348389
348584
  createPerson: true,
348390
348585
  displayName,
@@ -348559,6 +348754,22 @@ async function resolveOrCreateChat(services, instanceId, chatExternalId, chatTyp
348559
348754
  canonicalId
348560
348755
  });
348561
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
+ }
348562
348773
  async function handleMessageReceived(services, payload, metadata, eventTimestamp, trustedTenantId, identity) {
348563
348774
  const channel5 = metadata.channelType ?? "whatsapp";
348564
348775
  const isHistorySync = metadata.ingestMode === "history-sync";
@@ -348602,12 +348813,15 @@ async function handleMessageReceived(services, payload, metadata, eventTimestamp
348602
348813
  replyToExternalId: truncate4(payload.replyToId, 255),
348603
348814
  quotedText: quotedMessage?.conversation,
348604
348815
  quotedSenderName: truncate4(quotedMessage?.pushName, 255),
348816
+ threadExternalId: truncate4(payload.threadId, 255),
348817
+ isThreadBroadcast: rawPayload?.isThreadBroadcast === true,
348605
348818
  isForwarded: !!(rawPayload?.isForwarded || rawPayload?.forwardingScore),
348606
348819
  rawPayload
348607
348820
  });
348608
348821
  await maybeRecordMessageEdit(services, created, rawPayload, message2.id, sanitizeText(payload.content.text) ?? undefined, platformTimestamp ?? new Date(eventTimestamp), payload.from);
348609
348822
  if (created) {
348610
348823
  log103.debug("Created message", { externalId: payload.externalId, chatId: chat2.id });
348824
+ await linkReplyTarget(services, chat2.id, message2.id, payload.replyToId);
348611
348825
  }
348612
348826
  await maybeRecordParticipantActivity(services, chat2.id, payload.from);
348613
348827
  maybeUpdateRecency(services, metadata.instanceId, chat2.id, rawPayload, payload, platformTimestamp ?? new Date(eventTimestamp), trustedTenantId);
@@ -348705,7 +348919,8 @@ async function setupMessagePersistence(eventBus, services) {
348705
348919
  mediaLocalPath: sentContent.mediaLocalPath,
348706
348920
  mediaMetadata: sentContent.mediaMetadata,
348707
348921
  rawPayload: sentContent.rawPayload,
348708
- replyToExternalId: truncate4(payload.replyToId, 255)
348922
+ replyToExternalId: truncate4(payload.replyToId, 255),
348923
+ threadExternalId: truncate4(payload.threadId, 255)
348709
348924
  });
348710
348925
  return { chat: chat3, message: message3, created: created2 };
348711
348926
  });
@@ -348903,6 +349118,7 @@ var init_message_persistence = __esm(() => {
348903
349118
  init_esm5();
348904
349119
  init_sentry_scrub();
348905
349120
  init_worker_tenant_context();
349121
+ init_canonical_handle();
348906
349122
  init_loader2();
348907
349123
  log103 = createLogger("message-persistence");
348908
349124
  CONTENT_TYPE_MAP = {
@@ -349038,12 +349254,12 @@ var init_session_storage = __esm(() => {
349038
349254
  });
349039
349255
 
349040
349256
  // ../api/src/plugins/agent-dispatcher.ts
349041
- import { createHash as createHash12 } from "crypto";
349257
+ import { createHash as createHash13 } from "crypto";
349042
349258
  import { unlink, writeFile as writeFile5 } from "fs/promises";
349043
349259
  import { tmpdir as tmpdir12 } from "os";
349044
349260
  import { join as join22, resolve as resolve3 } from "path";
349045
349261
  function sha256Digest(value) {
349046
- return `sha256:${createHash12("sha256").update(value).digest("hex")}`;
349262
+ return `sha256:${createHash13("sha256").update(value).digest("hex")}`;
349047
349263
  }
349048
349264
  function redactLifecycleText(value) {
349049
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]");
@@ -365905,6 +366121,10 @@ function sanitizeInstance(instance4) {
365905
366121
  function applySlackProfileMetadata(opts, metadata) {
365906
366122
  if (!metadata)
365907
366123
  return;
366124
+ if (metadata.mode)
366125
+ opts.mode = metadata.mode;
366126
+ if (metadata.httpPort)
366127
+ opts.httpPort = metadata.httpPort;
365908
366128
  if (metadata.replyToMode)
365909
366129
  opts.replyToMode = metadata.replyToMode;
365910
366130
  if (metadata.streamMode)
@@ -365918,6 +366138,12 @@ function applySlackConnectOptions(opts, instance4, overrides) {
365918
366138
  const botToken = overrides?.slackBotToken ?? instance4.slackBotToken ?? opts.token;
365919
366139
  if (botToken)
365920
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;
365921
366147
  const appToken = overrides?.slackAppToken ?? instance4.slackAppToken;
365922
366148
  if (appToken)
365923
366149
  opts.appToken = appToken;
@@ -365942,8 +366168,13 @@ function applyTelegramConnectionOptions(options, input) {
365942
366168
  options.telegramReactionLevel = input.telegramReactionLevel;
365943
366169
  }
365944
366170
  function applySlackConnectionOptions(options, input) {
366171
+ applySlackProfileMetadata(options, input.profileMetadata);
365945
366172
  if (input.token)
365946
366173
  options.botToken = input.token;
366174
+ if (input.slackUserToken)
366175
+ options.userToken = input.slackUserToken;
366176
+ if (input.slackAuthMode)
366177
+ options.authMode = input.slackAuthMode;
365947
366178
  if (input.slackAppToken)
365948
366179
  options.appToken = input.slackAppToken;
365949
366180
  if (input.slackSigningSecret)
@@ -366054,6 +366285,10 @@ function buildTokenPersistUpdates(channel5, body) {
366054
366285
  if (channel5 === "slack") {
366055
366286
  if (body.slackBotToken)
366056
366287
  updates.slackBotToken = body.slackBotToken;
366288
+ if (body.slackUserToken)
366289
+ updates.slackUserToken = body.slackUserToken;
366290
+ if (body.slackAuthMode)
366291
+ updates.slackAuthMode = body.slackAuthMode;
366057
366292
  if (body.slackAppToken)
366058
366293
  updates.slackAppToken = body.slackAppToken;
366059
366294
  if (body.slackSigningSecret)
@@ -366113,6 +366348,9 @@ async function handleAgentKeyProvisioning(services, instanceId, newAgentId, oldA
366113
366348
  }
366114
366349
  }
366115
366350
  }
366351
+ function supportsPasskey(plugin10) {
366352
+ return "getPasskeyState" in plugin10 && "submitPasskeyResponse" in plugin10 && "confirmPasskey" in plugin10;
366353
+ }
366116
366354
  function buildConnectConnectionOptions(instance4, body, forceNewQr) {
366117
366355
  const connectToken = body.token ?? persistedTokenForChannel(instance4);
366118
366356
  return buildInstanceConnectionOptions({
@@ -366120,8 +366358,11 @@ function buildConnectConnectionOptions(instance4, body, forceNewQr) {
366120
366358
  forceNewQr,
366121
366359
  token: connectToken,
366122
366360
  telegramReactionLevel: instance4.telegramReactionLevel,
366361
+ slackUserToken: body.slackUserToken ?? instance4.slackUserToken,
366362
+ slackAuthMode: body.slackAuthMode ?? instance4.slackAuthMode,
366123
366363
  slackAppToken: body.slackAppToken ?? instance4.slackAppToken,
366124
366364
  slackSigningSecret: body.slackSigningSecret ?? instance4.slackSigningSecret,
366365
+ profileMetadata: instance4.profileMetadata,
366125
366366
  whatsapp: body.whatsapp,
366126
366367
  gupshupCallbackUrl: instance4.gupshupCallbackUrl,
366127
366368
  gupshupAuthToken: instance4.gupshupAuthToken,
@@ -366279,7 +366520,7 @@ function getCacheKey(instanceId, guildId) {
366279
366520
  function invalidateGuildCache(instanceId, guildId) {
366280
366521
  guildConfigCache.delete(getCacheKey(instanceId, guildId));
366281
366522
  }
366282
- 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;
366283
366524
  var init_instances3 = __esm(() => {
366284
366525
  init_dist6();
366285
366526
  init_src();
@@ -366346,6 +366587,8 @@ var init_instances3 = __esm(() => {
366346
366587
  agentGatePrompt: exports_external.string().nullable().default(null).describe("Custom prompt for response gate (null = use default)"),
366347
366588
  telegramBotToken: exports_external.string().optional().nullable().describe("Telegram bot token (persisted for reconnection)"),
366348
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"),
366349
366592
  slackBotToken: exports_external.string().optional().nullable().describe("Slack bot token (persisted for reconnection)"),
366350
366593
  slackAppToken: exports_external.string().optional().nullable().describe("Slack app token (persisted for reconnection)"),
366351
366594
  slackSigningSecret: exports_external.string().optional().nullable().describe("Slack signing secret (persisted for reconnection)"),
@@ -366377,6 +366620,7 @@ var init_instances3 = __esm(() => {
366377
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).`)
366378
366621
  });
366379
366622
  updateInstanceSchema = createInstanceSchema.partial().extend({
366623
+ profileMetadata: exports_external.record(exports_external.unknown()).nullable().optional(),
366380
366624
  agentId: exports_external.string().uuid().nullable().optional(),
366381
366625
  agentErrorMessages: exports_external.array(exports_external.string()).nullable().optional(),
366382
366626
  agentReplyFilter: agentReplyFilterSchema.nullable().optional(),
@@ -366384,6 +366628,8 @@ var init_instances3 = __esm(() => {
366384
366628
  telegramBotToken: exports_external.string().nullable().optional(),
366385
366629
  discordBotToken: exports_external.string().nullable().optional(),
366386
366630
  slackBotToken: exports_external.string().nullable().optional(),
366631
+ slackUserToken: exports_external.string().nullable().optional(),
366632
+ slackAuthMode: exports_external.enum(["bot", "user"]).nullable().optional(),
366387
366633
  slackAppToken: exports_external.string().nullable().optional(),
366388
366634
  gupshupCallbackUrl: exports_external.string().nullable().optional(),
366389
366635
  gupshupAuthToken: exports_external.string().nullable().optional(),
@@ -366418,6 +366664,7 @@ var init_instances3 = __esm(() => {
366418
366664
  "telegramBotToken",
366419
366665
  "discordBotToken",
366420
366666
  "slackBotToken",
366667
+ "slackUserToken",
366421
366668
  "slackAppToken",
366422
366669
  "slackSigningSecret",
366423
366670
  "gupshupAuthToken",
@@ -366509,8 +366756,11 @@ var init_instances3 = __esm(() => {
366509
366756
  forceNewQr: true,
366510
366757
  token: connectToken,
366511
366758
  telegramReactionLevel: instance4.telegramReactionLevel,
366759
+ slackUserToken: instance4.slackUserToken,
366760
+ slackAuthMode: instance4.slackAuthMode,
366512
366761
  slackAppToken: instance4.slackAppToken,
366513
366762
  slackSigningSecret: instance4.slackSigningSecret,
366763
+ profileMetadata: instance4.profileMetadata,
366514
366764
  gupshupCallbackUrl: instance4.gupshupCallbackUrl,
366515
366765
  gupshupAuthToken: instance4.gupshupAuthToken,
366516
366766
  gupshupEventId: instance4.gupshupEventId,
@@ -366656,6 +366906,18 @@ var init_instances3 = __esm(() => {
366656
366906
  pairingCodeSchema = exports_external.object({
366657
366907
  phoneNumber: exports_external.string().min(10).max(20).describe("Phone number in international format (e.g., +5511999999999)")
366658
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
+ });
366659
366921
  instancesRoutes.post("/:id/pair", instanceAccess2, zValidator("json", pairingCodeSchema), async (c) => {
366660
366922
  const id = c.req.param("id");
366661
366923
  const { phoneNumber } = c.req.valid("json");
@@ -366691,9 +366953,59 @@ var init_instances3 = __esm(() => {
366691
366953
  return c.json({ error: { code: "PAIRING_FAILED", message: message2 } }, 500);
366692
366954
  }
366693
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
+ });
366694
367004
  connectInstanceSchema = exports_external.object({
366695
367005
  token: exports_external.string().optional().describe("Bot token for Discord/Telegram instances"),
366696
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"),
366697
367009
  slackAppToken: exports_external.string().optional().describe("Slack app-level token (xapp-...)"),
366698
367010
  slackSigningSecret: exports_external.string().optional().describe("Slack signing secret"),
366699
367011
  forceNewQr: exports_external.boolean().optional().describe("Force new QR code for WhatsApp (re-authentication)"),
@@ -366787,11 +367099,6 @@ var init_instances3 = __esm(() => {
366787
367099
  restartOptions.token = restartToken;
366788
367100
  if (instance4.channel === "telegram") {
366789
367101
  restartOptions.telegramReactionLevel = instance4.telegramReactionLevel;
366790
- } else if (instance4.channel === "slack") {
366791
- if (restartToken)
366792
- restartOptions.botToken = restartToken;
366793
- if (instance4.slackAppToken)
366794
- restartOptions.appToken = instance4.slackAppToken;
366795
367102
  }
366796
367103
  if (instance4.channel === "slack") {
366797
367104
  applySlackConnectOptions(restartOptions, instance4);
@@ -369496,8 +369803,8 @@ var init_media2 = __esm(() => {
369496
369803
  return c.json({ error: { code: "INVALID_PATH", message: "No path specified" } }, 400);
369497
369804
  }
369498
369805
  const pathSegments = path3.split("/");
369499
- for (const segment of pathSegments) {
369500
- if (!isValidPathComponent(segment)) {
369806
+ for (const segment2 of pathSegments) {
369807
+ if (!isValidPathComponent(segment2)) {
369501
369808
  return c.json({ error: { code: "INVALID_PATH", message: "Invalid path" } }, 400);
369502
369809
  }
369503
369810
  }
@@ -369685,6 +369992,13 @@ async function resolveRecipient(to, channelType, services) {
369685
369992
  recoverable: false
369686
369993
  });
369687
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
+ }
369688
370002
  async function getPluginForInstance2(services, channelRegistry2, instanceId, requiredCapability) {
369689
370003
  const instance4 = await services.instances.getById(instanceId);
369690
370004
  if (!channelRegistry2) {
@@ -369850,7 +370164,7 @@ async function computeCloseContactTerminalState(db2, chatUuid, outcome, auditRow
369850
370164
  return { terminal: false, escalated: false, closeUntil };
369851
370165
  }
369852
370166
  function hasPresenceStatusSender(plugin10) {
369853
- return typeof plugin10 === "object" && plugin10 !== null && "sendPresenceStatus" in plugin10 && typeof plugin10.sendPresenceStatus === "function";
370167
+ return typeof plugin10 === "object" && plugin10 !== null && typeof plugin10.sendPresenceStatus === "function";
369854
370168
  }
369855
370169
  async function verifyMessageInstanceOwnership(services, message2, instanceId) {
369856
370170
  if (!message2.chatId)
@@ -369873,7 +370187,7 @@ async function resolveChannelMessageId(services, messageId, instanceId) {
369873
370187
  log117.debug("Resolved internal UUID to external ID", { messageId, externalId: message2.externalId });
369874
370188
  return message2.externalId;
369875
370189
  }
369876
- 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;
369877
370191
  var init_messages5 = __esm(() => {
369878
370192
  init_dist6();
369879
370193
  init_src2();
@@ -369916,6 +370230,42 @@ var init_messages5 = __esm(() => {
369916
370230
  document: "application/octet-stream"
369917
370231
  };
369918
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
+ });
369919
370269
  MessageSourceSchema = exports_external.enum(["realtime", "sync", "api", "import"]);
369920
370270
  MessageTypeSchema = exports_external.enum([
369921
370271
  "text",
@@ -370928,7 +371278,7 @@ var init_messages5 = __esm(() => {
370928
371278
  stability: data.stability,
370929
371279
  similarityBoost: data.similarityBoost
370930
371280
  });
370931
- if ("sendTyping" in plugin10 && typeof plugin10.sendTyping === "function") {
371281
+ if (typeof plugin10.sendTyping === "function") {
370932
371282
  const presenceDuration = data.presenceDelay ?? Math.min(ttsResult.durationMs, 15000);
370933
371283
  try {
370934
371284
  await plugin10.sendTyping(data.instanceId, resolvedTo, presenceDuration);
@@ -371105,7 +371455,7 @@ var init_messages5 = __esm(() => {
371105
371455
  status,
371106
371456
  loadingMessages
371107
371457
  }) : await (async () => {
371108
- if (!("sendTyping" in plugin10) || typeof plugin10.sendTyping !== "function") {
371458
+ if (typeof plugin10.sendTyping !== "function") {
371109
371459
  throw new OmniError({
371110
371460
  code: ERROR_CODES.CAPABILITY_NOT_SUPPORTED,
371111
371461
  message: `Channel ${instance4.channel} plugin does not implement sendTyping`,
@@ -371157,7 +371507,7 @@ var init_messages5 = __esm(() => {
371157
371507
  });
371158
371508
  }
371159
371509
  const { instance: instance4, plugin: plugin10 } = await getPluginForInstance2(services, channelRegistry2, instanceId, "canReceiveReadReceipts");
371160
- if (!("markAsRead" in plugin10) || typeof plugin10.markAsRead !== "function") {
371510
+ if (typeof plugin10.markAsRead !== "function") {
371161
371511
  throw new OmniError({
371162
371512
  code: ERROR_CODES.CAPABILITY_NOT_SUPPORTED,
371163
371513
  message: `Channel ${instance4.channel} plugin does not implement markAsRead`,
@@ -371183,7 +371533,7 @@ var init_messages5 = __esm(() => {
371183
371533
  const channelRegistry2 = c.get("channelRegistry");
371184
371534
  checkInstanceAccess2(c.get("apiKey"), instanceId);
371185
371535
  const { instance: instance4, plugin: plugin10 } = await getPluginForInstance2(services, channelRegistry2, instanceId, "canReceiveReadReceipts");
371186
- if (!("markAsRead" in plugin10) || typeof plugin10.markAsRead !== "function") {
371536
+ if (typeof plugin10.markAsRead !== "function") {
371187
371537
  throw new OmniError({
371188
371538
  code: ERROR_CODES.CAPABILITY_NOT_SUPPORTED,
371189
371539
  message: `Channel ${instance4.channel} plugin does not implement markAsRead`,
@@ -371419,7 +371769,7 @@ var init_messages5 = __esm(() => {
371419
371769
  recoverable: false
371420
371770
  });
371421
371771
  }
371422
- if (!("editMessage" in plugin10) || typeof plugin10.editMessage !== "function") {
371772
+ if (typeof plugin10.editMessage !== "function") {
371423
371773
  throw new OmniError({
371424
371774
  code: ERROR_CODES.CAPABILITY_NOT_SUPPORTED,
371425
371775
  message: `Channel ${instance4.channel} plugin does not implement editMessage`,
@@ -371475,7 +371825,7 @@ var init_messages5 = __esm(() => {
371475
371825
  recoverable: false
371476
371826
  });
371477
371827
  }
371478
- if (!("deleteMessage" in plugin10) || typeof plugin10.deleteMessage !== "function") {
371828
+ if (typeof plugin10.deleteMessage !== "function") {
371479
371829
  throw new OmniError({
371480
371830
  code: ERROR_CODES.CAPABILITY_NOT_SUPPORTED,
371481
371831
  message: `Channel ${instance4.channel} plugin does not implement deleteMessage`,
@@ -371518,7 +371868,7 @@ var init_messages5 = __esm(() => {
371518
371868
  recoverable: false
371519
371869
  });
371520
371870
  }
371521
- if (!("starMessage" in plugin10) || typeof plugin10.starMessage !== "function") {
371871
+ if (typeof plugin10.starMessage !== "function") {
371522
371872
  throw new OmniError({
371523
371873
  code: ERROR_CODES.CAPABILITY_NOT_SUPPORTED,
371524
371874
  message: `Channel ${instance4.channel} does not support starring messages`,
@@ -371527,6 +371877,7 @@ var init_messages5 = __esm(() => {
371527
371877
  });
371528
371878
  }
371529
371879
  await plugin10.starMessage(instanceId, channelId, messageId, true, fromMe);
371880
+ await persistStarState(services, instanceId, channelId, messageId, true);
371530
371881
  return c.json({
371531
371882
  success: true,
371532
371883
  data: { messageId, starred: true }
@@ -371555,7 +371906,7 @@ var init_messages5 = __esm(() => {
371555
371906
  recoverable: false
371556
371907
  });
371557
371908
  }
371558
- if (!("starMessage" in plugin10) || typeof plugin10.starMessage !== "function") {
371909
+ if (typeof plugin10.starMessage !== "function") {
371559
371910
  throw new OmniError({
371560
371911
  code: ERROR_CODES.CAPABILITY_NOT_SUPPORTED,
371561
371912
  message: `Channel ${instance4.channel} does not support starring messages`,
@@ -371564,6 +371915,7 @@ var init_messages5 = __esm(() => {
371564
371915
  });
371565
371916
  }
371566
371917
  await plugin10.starMessage(instanceId, channelId, messageId, false, fromMe);
371918
+ await persistStarState(services, instanceId, channelId, messageId, false);
371567
371919
  return c.json({
371568
371920
  success: true,
371569
371921
  data: { messageId, starred: false }
@@ -372029,15 +372381,345 @@ var init_providers4 = __esm(() => {
372029
372381
  });
372030
372382
  });
372031
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
+
372032
372714
  // ../api/src/routes/v2/settings.ts
372033
- var settingsRoutes, listQuerySchema17, setSettingSchema, bulkUpdateSchema, historyQuerySchema;
372715
+ var settingsRoutes, listQuerySchema18, setSettingSchema, bulkUpdateSchema, historyQuerySchema;
372034
372716
  var init_settings3 = __esm(() => {
372035
372717
  init_dist6();
372036
372718
  init_dist2();
372037
372719
  init_zod();
372038
372720
  init_date_query();
372039
372721
  settingsRoutes = new Hono2;
372040
- listQuerySchema17 = exports_external.object({
372722
+ listQuerySchema18 = exports_external.object({
372041
372723
  category: exports_external.string().optional()
372042
372724
  });
372043
372725
  setSettingSchema = exports_external.object({
@@ -372052,7 +372734,7 @@ var init_settings3 = __esm(() => {
372052
372734
  limit: exports_external.coerce.number().int().min(1).max(100).default(50),
372053
372735
  since: optionalDateParam("since")
372054
372736
  });
372055
- settingsRoutes.get("/", zValidator("query", listQuerySchema17), async (c) => {
372737
+ settingsRoutes.get("/", zValidator("query", listQuerySchema18), async (c) => {
372056
372738
  const { category } = c.req.valid("query");
372057
372739
  const services = c.get("services");
372058
372740
  const settings = await services.settings.list(category);
@@ -372127,6 +372809,116 @@ var init_settings3 = __esm(() => {
372127
372809
  });
372128
372810
  });
372129
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
+
372130
372922
  // ../api/src/routes/v2/templates.ts
372131
372923
  function jsonError(message2, code = "BAD_REQUEST", _status = 400) {
372132
372924
  return { error: { code, message: message2 } };
@@ -372151,7 +372943,7 @@ function makeClient(cfg) {
372151
372943
  function variablesToBodyParameters(variables) {
372152
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);
372153
372945
  }
372154
- var log118, templatesRoutes, instanceAccess3, idParamSchema6, templateIdParamSchema, templateNameParamSchema, listQuerySchema18, createBodySchema, sendTestBodySchema, sendByNameBodySchema;
372946
+ var log118, templatesRoutes, instanceAccess3, idParamSchema6, templateIdParamSchema, templateNameParamSchema, listQuerySchema19, createBodySchema, sendTestBodySchema, sendByNameBodySchema;
372155
372947
  var init_templates3 = __esm(() => {
372156
372948
  init_dist6();
372157
372949
  init_src6();
@@ -372176,7 +372968,7 @@ var init_templates3 = __esm(() => {
372176
372968
  id: exports_external.string().uuid(),
372177
372969
  templateName: exports_external.string().min(1)
372178
372970
  });
372179
- listQuerySchema18 = exports_external.object({
372971
+ listQuerySchema19 = exports_external.object({
372180
372972
  status: MetaTemplateStatusSchema.optional(),
372181
372973
  category: MetaTemplateCategorySchema.optional(),
372182
372974
  language: exports_external.string().min(2).optional(),
@@ -372204,7 +372996,7 @@ var init_templates3 = __esm(() => {
372204
372996
  filename: exports_external.string().optional()
372205
372997
  }).optional()
372206
372998
  });
372207
- 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) => {
372208
373000
  const { id: instanceId } = c.req.valid("param");
372209
373001
  const { status, category, language, sync } = c.req.valid("query");
372210
373002
  const services = c.get("services");
@@ -372494,7 +373286,7 @@ async function resolveOpenTurn(services, keyId, turnId, headerInstanceId, header
372494
373286
  }
372495
373287
  return null;
372496
373288
  }
372497
- var turnsRoutes, closeTurnSchema, listQuerySchema19, adminForceCloseSchema, bulkCloseSchema;
373289
+ var turnsRoutes, closeTurnSchema, listQuerySchema20, adminForceCloseSchema, bulkCloseSchema;
372498
373290
  var init_turns2 = __esm(() => {
372499
373291
  init_dist6();
372500
373292
  init_dist2();
@@ -372557,7 +373349,7 @@ var init_turns2 = __esm(() => {
372557
373349
  }
372558
373350
  });
372559
373351
  });
372560
- listQuerySchema19 = exports_external.object({
373352
+ listQuerySchema20 = exports_external.object({
372561
373353
  status: exports_external.enum(["open", "done", "timeout"]).optional(),
372562
373354
  instanceId: exports_external.string().uuid().optional(),
372563
373355
  chatId: exports_external.string().optional(),
@@ -372572,7 +373364,7 @@ var init_turns2 = __esm(() => {
372572
373364
  confirm: exports_external.boolean(),
372573
373365
  reason: exports_external.string().optional()
372574
373366
  });
372575
- turnsRoutes.get("/", zValidator("query", listQuerySchema19), async (c) => {
373367
+ turnsRoutes.get("/", zValidator("query", listQuerySchema20), async (c) => {
372576
373368
  const denied = requireAdmin(c);
372577
373369
  if (denied)
372578
373370
  return denied;
@@ -372803,7 +373595,7 @@ var init_voice2 = __esm(() => {
372803
373595
  });
372804
373596
 
372805
373597
  // ../api/src/routes/v2/webhooks.ts
372806
- var webhooksRoutes, createWebhookSourceSchema, updateWebhookSourceSchema, listQuerySchema20, triggerEventSchema;
373598
+ var webhooksRoutes, createWebhookSourceSchema, updateWebhookSourceSchema, listQuerySchema21, triggerEventSchema;
372807
373599
  var init_webhooks3 = __esm(() => {
372808
373600
  init_dist6();
372809
373601
  init_dist2();
@@ -372817,10 +373609,10 @@ var init_webhooks3 = __esm(() => {
372817
373609
  enabled: exports_external.boolean().default(true).describe("Whether source is enabled")
372818
373610
  });
372819
373611
  updateWebhookSourceSchema = createWebhookSourceSchema.partial();
372820
- listQuerySchema20 = exports_external.object({
373612
+ listQuerySchema21 = exports_external.object({
372821
373613
  enabled: exports_external.coerce.boolean().optional()
372822
373614
  });
372823
- webhooksRoutes.get("/webhook-sources", zValidator("query", listQuerySchema20), async (c) => {
373615
+ webhooksRoutes.get("/webhook-sources", zValidator("query", listQuerySchema21), async (c) => {
372824
373616
  const { enabled } = c.req.valid("query");
372825
373617
  const services = c.get("services");
372826
373618
  const sources = await services.webhooks.list({ enabled });
@@ -373685,7 +374477,9 @@ var init_v2 = __esm(() => {
373685
374477
  init_persons3();
373686
374478
  init_processed_events();
373687
374479
  init_providers4();
374480
+ init_scheduled_messages2();
373688
374481
  init_settings3();
374482
+ init_slack();
373689
374483
  init_templates3();
373690
374484
  init_trust();
373691
374485
  init_turns2();
@@ -373702,6 +374496,8 @@ var init_v2 = __esm(() => {
373702
374496
  v2Routes.route("/instances", instancesRoutes);
373703
374497
  v2Routes.route("/logs", logsRoutes);
373704
374498
  v2Routes.route("/messages", messagesRoutes);
374499
+ v2Routes.route("/scheduled-messages", scheduledMessagesRoutes);
374500
+ v2Routes.route("/slack", slackRoutes);
373705
374501
  v2Routes.route("/events", eventsRoutes);
373706
374502
  v2Routes.route("/journeys", journeysRoutes);
373707
374503
  v2Routes.route("/persons", personsRoutes);
@@ -374818,6 +375614,27 @@ async function downloadMediaFromUrl(ctx, instanceId, messageId, mediaUrl, mimeTy
374818
375614
  return null;
374819
375615
  }
374820
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
+ }
374821
375638
  async function resolveMediaPath2(ctx, instanceId, chatId, externalId, content, mimeType, channelType, trustedTenantId) {
374822
375639
  const maxWaitMs = 5000;
374823
375640
  const pollMs = 250;
@@ -374847,6 +375664,11 @@ async function resolveMediaPath2(ctx, instanceId, chatId, externalId, content, m
374847
375664
  if (!filePath)
374848
375665
  return null;
374849
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
+ }
374850
375672
  if (!filePath) {
374851
375673
  log122.debug("No media file path available", { externalId });
374852
375674
  return null;
@@ -375144,17 +375966,23 @@ async function setupMediaProcessor(eventBus, db2, services) {
375144
375966
  });
375145
375967
  log122.info("Media processor initialized");
375146
375968
  }
375147
- var log122, PROCESSABLE_MEDIA_TYPES;
375969
+ var log122, PROCESSABLE_MEDIA_TYPES, pluginMediaSchema;
375148
375970
  var init_media_processor = __esm(() => {
375149
375971
  init_src();
375150
375972
  init_src5();
375151
375973
  init_src8();
375152
375974
  init_drizzle_orm();
375975
+ init_zod();
375153
375976
  init_media_storage();
375154
375977
  init_tenant_scope();
375155
375978
  init_worker_tenant_context();
375979
+ init_loader2();
375156
375980
  log122 = createLogger("media-processor");
375157
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
+ });
375158
375986
  });
375159
375987
 
375160
375988
  // ../api/src/tenancy/inflight-revocation.ts
@@ -375207,29 +376035,6 @@ var init_inflight_revocation = __esm(() => {
375207
376035
  };
375208
376036
  });
375209
376037
 
375210
- // ../api/src/utils/phone.ts
375211
- function isValidE164Phone(phone) {
375212
- const bare = phone.replace(/^\+/, "");
375213
- if (!/^\d+$/.test(bare))
375214
- return false;
375215
- return bare.length >= 7 && bare.length <= 15;
375216
- }
375217
- function isLidFormat(platformUserId) {
375218
- const bare = platformUserId.split("@")[0] || platformUserId;
375219
- return /^\d{14,}$/.test(bare);
375220
- }
375221
- function validateContactPhone(phone, platformUserId) {
375222
- if (!phone)
375223
- return;
375224
- if (!isValidE164Phone(phone))
375225
- return;
375226
- const barePhone = phone.replace(/^\+/, "");
375227
- const barePuid = platformUserId.split("@")[0] || platformUserId;
375228
- if (isLidFormat(platformUserId) && barePhone === barePuid)
375229
- return;
375230
- return phone;
375231
- }
375232
-
375233
376038
  // ../api/src/plugins/sync-worker.ts
375234
376039
  class RateLimiter {
375235
376040
  lastRequest = 0;
@@ -376801,6 +377606,11 @@ async function withCronMonitor(slug, cron2, checkinMargin, maxRuntime, handler)
376801
377606
  await handler();
376802
377607
  }
376803
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
+ }
376804
377614
  function setupScheduler(services, channelRegistry2) {
376805
377615
  const scheduler = getScheduler();
376806
377616
  scheduler.register({
@@ -376929,6 +377739,29 @@ function setupScheduler(services, channelRegistry2) {
376929
377739
  });
376930
377740
  }
376931
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
+ }
376932
377765
  scheduler.register({
376933
377766
  name: "unread-count-refresh",
376934
377767
  cron: CronExpressions.EVERY_HOUR,
@@ -376980,6 +377813,7 @@ var init_scheduler2 = __esm(() => {
376980
377813
  init_src();
376981
377814
  init_esm5();
376982
377815
  init_sentry_scrub();
377816
+ init_scheduled_messages();
376983
377817
  init_periodic_tenant_work();
376984
377818
  log127 = createLogger("scheduler:setup");
376985
377819
  });
@@ -383701,6 +384535,11 @@ var SLACK_CAPABILITIES = {
383701
384535
  canDeleteMessage: true,
383702
384536
  canReplyToMessage: true,
383703
384537
  canForwardMessage: false,
384538
+ canScheduleMessage: true,
384539
+ maxScheduleAheadMs: 120 * 24 * 60 * 60 * 1000,
384540
+ canGetPermalink: true,
384541
+ canPinMessage: true,
384542
+ canSearchMessages: false,
383704
384543
  canSendContact: false,
383705
384544
  canSendLocation: false,
383706
384545
  canSendSticker: false,
@@ -383754,7 +384593,17 @@ function resolveStreamThrottle(throttleMs) {
383754
384593
  // ../channel-slack/src/connection/bolt-client.ts
383755
384594
  init_types10();
383756
384595
  var import_bolt = __toESM(require_dist16(), 1);
384596
+ var import_web_api = __toESM(require_dist13(), 1);
383757
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
+ }
383758
384607
  function createBoltApp(options, logger5) {
383759
384608
  const mode = options.mode ?? "socket";
383760
384609
  if (mode === "http") {
@@ -383788,6 +384637,7 @@ function createSocketBoltApp(options, logger5) {
383788
384637
  return {
383789
384638
  app,
383790
384639
  client: app.client,
384640
+ ...buildActingClients(options, app.client),
383791
384641
  botToken: options.botToken,
383792
384642
  mode: "socket"
383793
384643
  };
@@ -383820,6 +384670,7 @@ function createHttpBoltApp(options, logger5) {
383820
384670
  return {
383821
384671
  app,
383822
384672
  client: app.client,
384673
+ ...buildActingClients(options, app.client),
383823
384674
  botToken: options.botToken,
383824
384675
  mode: "http",
383825
384676
  httpPort: options.httpPort,
@@ -383895,11 +384746,22 @@ async function startBoltConnection(connection, logger5) {
383895
384746
  teamId: connection.teamId,
383896
384747
  teamName: connection.teamName
383897
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
+ }
383898
384757
  } catch (error) {
383899
384758
  logger5.warn("Failed to resolve bot identity before start \u2014 self-message filtering may be unreliable", {
383900
384759
  error: String(error)
383901
384760
  });
383902
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
+ }
383903
384765
  if (connection.mode === "http") {
383904
384766
  const port = connection.httpPort ?? 3001;
383905
384767
  try {
@@ -384228,12 +385090,13 @@ function extractMessageMeta(event) {
384228
385090
  ts,
384229
385091
  userId,
384230
385092
  teamId,
384231
- isDm: channelType === "im",
385093
+ isDm: channelType === "im" || channelType === "mpim",
385094
+ isMpim: channelType === "mpim",
384232
385095
  isThreadReply: threadTs !== undefined && threadTs !== ts,
384233
385096
  channelType
384234
385097
  };
384235
385098
  }
384236
- function shouldSkipMessage(msg, botUserId) {
385099
+ function shouldSkipMessage(msg, selfUserIds) {
384237
385100
  if (msg.subtype === "bot_message" || msg.bot_id)
384238
385101
  return true;
384239
385102
  if (msg.subtype === "message_changed" || msg.subtype === "message_deleted")
@@ -384241,7 +385104,7 @@ function shouldSkipMessage(msg, botUserId) {
384241
385104
  const userId = msg.user;
384242
385105
  if (!userId)
384243
385106
  return true;
384244
- if (botUserId && userId === botUserId)
385107
+ if (selfUserIds.some((id) => id && id === userId))
384245
385108
  return true;
384246
385109
  return false;
384247
385110
  }
@@ -384265,6 +385128,7 @@ function buildRawPayload(meta, msg, extra) {
384265
385128
  channelType: meta.channelType,
384266
385129
  teamId: meta.teamId,
384267
385130
  isDm: meta.isDm,
385131
+ isMpim: meta.isMpim === true,
384268
385132
  isThreadReply: meta.isThreadReply,
384269
385133
  files: msg.files,
384270
385134
  ...extra
@@ -384358,11 +385222,12 @@ async function processMessage2(instanceId, msg, currentBotUserId, callbacks, log
384358
385222
  }
384359
385223
  await callbacks.onMessage(instanceId, meta.ts, meta.channelId, userId, content, replyToId, rawPayload, platformTimestamp, meta);
384360
385224
  }
384361
- function setupMessageHandlers2(app, instanceId, botUserId, callbacks, dmPolicyConfig, logger5, filterConfig, reliability) {
385225
+ function setupMessageHandlers2(app, instanceId, botUserId, callbacks, dmPolicyConfig, logger5, filterConfig, reliability, actingUserId) {
384362
385226
  const resolveBotUserId = () => typeof botUserId === "function" ? botUserId() : botUserId;
385227
+ const resolveActingUserId = () => typeof actingUserId === "function" ? actingUserId() : actingUserId;
384363
385228
  app.message(async ({ message: message2 }) => {
384364
385229
  const msg = message2;
384365
- if (shouldSkipMessage(msg, resolveBotUserId()))
385230
+ if (shouldSkipMessage(msg, [resolveBotUserId(), resolveActingUserId()]))
384366
385231
  return;
384367
385232
  const userId = msg.user;
384368
385233
  const meta = extractMessageMeta(msg);
@@ -384514,16 +385379,52 @@ async function uploadFileFromUrl(client, options, logger5) {
384514
385379
 
384515
385380
  // ../channel-slack/src/markdown.ts
384516
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
+ }
384517
385420
  function markdownToMrkdwn(markdown) {
384518
- let text = markdown;
384519
- text = text.replace(/^#{1,6}\s+(.+)$/gm, "*$1*");
384520
- text = text.replace(/\*\*(.+?)\*\*/g, "*$1*");
384521
- text = text.replace(/~~(.+?)~~/g, "~$1~");
384522
- text = text.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, "<$2|$1>");
384523
- text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "<$2|$1>");
384524
- text = text.replace(/^>\s?/gm, "> ");
384525
- text = text.replace(/^(-{3,}|\*{3,}|_{3,})$/gm, "\u2014\u2014\u2014");
384526
- 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() ?? "";
384527
385428
  }
384528
385429
  function chunkMessage2(text, maxLength = MAX_SLACK_MESSAGE_LENGTH) {
384529
385430
  if (text.length <= maxLength) {
@@ -384531,21 +385432,37 @@ function chunkMessage2(text, maxLength = MAX_SLACK_MESSAGE_LENGTH) {
384531
385432
  }
384532
385433
  const chunks = [];
384533
385434
  let remaining = text;
385435
+ let carryFence = null;
384534
385436
  while (remaining.length > 0) {
384535
- if (remaining.length <= maxLength) {
384536
- chunks.push(remaining);
385437
+ const prefix = carryFence === null ? "" : `\`\`\`${carryFence}
385438
+ `;
385439
+ if (prefix.length + remaining.length <= maxLength) {
385440
+ chunks.push(prefix + remaining);
384537
385441
  break;
384538
385442
  }
384539
- let splitIndex = remaining.lastIndexOf(`
384540
- `, maxLength);
384541
- if (splitIndex <= 0 || splitIndex < maxLength * 0.5) {
384542
- splitIndex = remaining.lastIndexOf(" ", maxLength);
384543
- }
384544
- if (splitIndex <= 0 || splitIndex < maxLength * 0.5) {
384545
- splitIndex = maxLength;
384546
- }
384547
- chunks.push(remaining.substring(0, splitIndex));
384548
- 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();
384549
385466
  }
384550
385467
  return chunks;
384551
385468
  }
@@ -384887,6 +385804,55 @@ async function sendTextMessage2(client, options, logger5) {
384887
385804
  }
384888
385805
  return lastTs;
384889
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
+ }
384890
385856
  async function editSlackMessage(client, channelId, ts, newText, formatMode, logger5) {
384891
385857
  const formattedText = formatMode === "passthrough" ? newText : markdownToMrkdwn(newText);
384892
385858
  try {
@@ -384919,19 +385885,27 @@ init_types10();
384919
385885
  var downloadGuard4 = createDownloadGuard();
384920
385886
  function resolveSlackTokens(slackConfig, rawOptions, rawCredentials) {
384921
385887
  const botToken = slackConfig.botToken ?? rawOptions.token ?? rawCredentials.botToken ?? rawCredentials.token;
385888
+ const userToken = slackConfig.userToken ?? rawCredentials.userToken;
385889
+ const authMode = slackConfig.authMode ?? "bot";
384922
385890
  const appToken = slackConfig.appToken ?? rawCredentials.appToken;
384923
385891
  const signingSecret = slackConfig.signingSecret ?? rawCredentials.signingSecret;
384924
385892
  const mode = slackConfig.mode ?? "socket";
384925
385893
  if (!botToken) {
384926
385894
  throw new SlackError(SlackErrorCode.INVALID_TOKEN, "botToken (xoxb-...) is required");
384927
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
+ }
384928
385902
  if (mode === "socket" && !appToken) {
384929
385903
  throw new SlackError(SlackErrorCode.INVALID_TOKEN, "appToken (xapp-...) is required for Socket Mode");
384930
385904
  }
384931
385905
  if (mode === "http" && !signingSecret) {
384932
385906
  throw new SlackError(SlackErrorCode.INVALID_TOKEN, "signingSecret is required for HTTP mode");
384933
385907
  }
384934
- return { botToken, appToken, signingSecret, mode };
385908
+ return { botToken, userToken, authMode, appToken, signingSecret, mode };
384935
385909
  }
384936
385910
 
384937
385911
  class SlackPlugin extends BaseChannelPlugin {
@@ -385010,6 +385984,8 @@ class SlackPlugin extends BaseChannelPlugin {
385010
385984
  }
385011
385985
  connection = createBoltApp({
385012
385986
  botToken: resolved.botToken,
385987
+ userToken: resolved.userToken,
385988
+ authMode: resolved.authMode,
385013
385989
  appToken: resolved.appToken,
385014
385990
  signingSecret: resolved.signingSecret,
385015
385991
  retryConfig: slackConfig.retryConfig,
@@ -385138,7 +386114,7 @@ class SlackPlugin extends BaseChannelPlugin {
385138
386114
  const streamMode = resolveStreamMode(slackConfig.streamMode);
385139
386115
  const throttleMs = resolveStreamThrottle(slackConfig.streamThrottleMs);
385140
386116
  const base2 = streamMode === "native" ? createNativeStreamSender({
385141
- client: connection.client,
386117
+ client: connection.actingClient,
385142
386118
  channelId: chatId,
385143
386119
  threadTs,
385144
386120
  throttleMs,
@@ -385148,7 +386124,7 @@ class SlackPlugin extends BaseChannelPlugin {
385148
386124
  formatMode: options?.formatMode ?? "convert",
385149
386125
  logger: this.logger
385150
386126
  }) : createSlackStreamSender({
385151
- client: connection.client,
386127
+ client: connection.actingClient,
385152
386128
  channelId: chatId,
385153
386129
  threadTs,
385154
386130
  streamMode,
@@ -385195,12 +386171,12 @@ class SlackPlugin extends BaseChannelPlugin {
385195
386171
  const status = shouldClear ? "" : options?.status ?? (type === "recording" ? "is recording..." : "is typing...");
385196
386172
  const timerKey = this.presenceStatusTimerKey(instanceId, chatId, threadTs);
385197
386173
  const delivered = status === "" ? await clearTypingStatus({
385198
- client: connection.client,
386174
+ client: connection.actingClient,
385199
386175
  channelId: chatId,
385200
386176
  threadTs,
385201
386177
  logger: this.logger
385202
386178
  }) : await setSlackThreadStatus({
385203
- client: connection.client,
386179
+ client: connection.actingClient,
385204
386180
  channelId: chatId,
385205
386181
  threadTs,
385206
386182
  status,
@@ -385216,7 +386192,7 @@ class SlackPlugin extends BaseChannelPlugin {
385216
386192
  return;
385217
386193
  this.presenceStatusTimers.delete(timerKey);
385218
386194
  clearTypingStatus({
385219
- client: connection.client,
386195
+ client: connection.actingClient,
385220
386196
  channelId: chatId,
385221
386197
  threadTs,
385222
386198
  logger: this.logger
@@ -385253,21 +386229,105 @@ class SlackPlugin extends BaseChannelPlugin {
385253
386229
  }
385254
386230
  async editMessage(instanceId, channelId, messageTs, newText) {
385255
386231
  const connection = this.getConnection(instanceId);
385256
- await editSlackMessage(connection.client, channelId, messageTs, newText, "convert", this.logger);
386232
+ await editSlackMessage(connection.actingClient, channelId, messageTs, newText, "convert", this.logger);
385257
386233
  }
385258
386234
  async deleteMessage(instanceId, channelId, messageTs) {
385259
386235
  const connection = this.getConnection(instanceId);
385260
- 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
+ }
385261
386321
  }
385262
386322
  async addReaction(instanceId, channelId, messageTs, emoji) {
385263
386323
  const connection = this.getConnection(instanceId);
385264
386324
  const { addReaction: addReaction3 } = await Promise.resolve().then(() => (init_tools(), exports_tools));
385265
- await addReaction3(connection.client, channelId, messageTs, emoji, this.logger);
386325
+ await addReaction3(connection.actingClient, channelId, messageTs, emoji, this.logger);
385266
386326
  }
385267
386327
  async removeReaction(instanceId, channelId, messageTs, emoji) {
385268
386328
  const connection = this.getConnection(instanceId);
385269
386329
  const { removeReaction: removeReaction3 } = await Promise.resolve().then(() => (init_tools(), exports_tools));
385270
- await removeReaction3(connection.client, channelId, messageTs, emoji, this.logger);
386330
+ await removeReaction3(connection.actingClient, channelId, messageTs, emoji, this.logger);
385271
386331
  }
385272
386332
  async getProfile(instanceId) {
385273
386333
  const connection = this.getConnection(instanceId);
@@ -385286,7 +386346,7 @@ class SlackPlugin extends BaseChannelPlugin {
385286
386346
  async fetchUserProfile(instanceId, userId) {
385287
386347
  const connection = this.getConnection(instanceId);
385288
386348
  try {
385289
- const result = await connection.client.users.info({ user: userId });
386349
+ const result = await connection.actingClient.users.info({ user: userId });
385290
386350
  const user = result.user;
385291
386351
  if (!user)
385292
386352
  return {};
@@ -385346,11 +386406,7 @@ class SlackPlugin extends BaseChannelPlugin {
385346
386406
  if (!channelId || !threadTs)
385347
386407
  return { totalFetched: 0, messages: [] };
385348
386408
  const botUserId = connection.botUserId;
385349
- const botToken = this.slackConfigs.get(instanceId)?.botToken;
385350
- if (!botToken) {
385351
- this.logger.warn("fetchHistory: no botToken for instance", { instanceId });
385352
- return { totalFetched: 0, messages: [] };
385353
- }
386409
+ const botToken = connection.botToken;
385354
386410
  const limit = options.limit ?? 200;
385355
386411
  const messages = await this.paginateThreadHistory(connection, channelId, threadTs, botUserId, botToken, limit);
385356
386412
  return { totalFetched: messages.length, messages };
@@ -385359,7 +386415,7 @@ class SlackPlugin extends BaseChannelPlugin {
385359
386415
  const messages = [];
385360
386416
  let cursor;
385361
386417
  do {
385362
- const response = await connection.client.conversations.replies({
386418
+ const response = await connection.actingClient.conversations.replies({
385363
386419
  channel: channelId,
385364
386420
  ts: threadTs,
385365
386421
  limit: Math.min(200, maxMessages - messages.length),
@@ -385432,7 +386488,7 @@ class SlackPlugin extends BaseChannelPlugin {
385432
386488
  const connection = this.getConnection(instanceId);
385433
386489
  const slackName = SlackPlugin.EMOJI_TO_SLACK[emoji] ?? emoji.replace(/^:|:$/g, "");
385434
386490
  try {
385435
- await connection.client.reactions.add({ channel: chatId, timestamp: messageId, name: slackName });
386491
+ await connection.actingClient.reactions.add({ channel: chatId, timestamp: messageId, name: slackName });
385436
386492
  } catch (err2) {
385437
386493
  this.logger.warn("react: failed to add reaction", { chatId, messageId, emoji, error: String(err2) });
385438
386494
  }
@@ -385441,7 +386497,7 @@ class SlackPlugin extends BaseChannelPlugin {
385441
386497
  const connection = this.getConnection(instanceId);
385442
386498
  const slackName = SlackPlugin.EMOJI_TO_SLACK[emoji] ?? emoji.replace(/^:|:$/g, "");
385443
386499
  try {
385444
- await connection.client.reactions.remove({ channel: chatId, timestamp: messageId, name: slackName });
386500
+ await connection.actingClient.reactions.remove({ channel: chatId, timestamp: messageId, name: slackName });
385445
386501
  } catch (err2) {
385446
386502
  this.logger.warn("unreact: failed to remove reaction", { chatId, messageId, emoji, error: String(err2) });
385447
386503
  }
@@ -385453,7 +386509,7 @@ class SlackPlugin extends BaseChannelPlugin {
385453
386509
  const emojiName = typeof ackEmoji === "string" ? ackEmoji.replace(/^:|:$/g, "") : null;
385454
386510
  if (!emojiName)
385455
386511
  return;
385456
- 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) => {
385457
386513
  this.logger.warn("ack reaction: failed to add", { channelId, messageTs, emoji: emojiName, error: String(err2) });
385458
386514
  });
385459
386515
  if (config2.removeAckAfterReply !== false) {
@@ -385472,7 +386528,7 @@ class SlackPlugin extends BaseChannelPlugin {
385472
386528
  if (!emojiName)
385473
386529
  continue;
385474
386530
  this.pendingAckReactions.delete(key);
385475
- 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) => {
385476
386532
  this.logger.warn("ack reaction: failed to remove", { channelId, ts, emoji: emojiName, error: String(err2) });
385477
386533
  });
385478
386534
  break;
@@ -385486,7 +386542,7 @@ class SlackPlugin extends BaseChannelPlugin {
385486
386542
  if (!resolvedThread)
385487
386543
  return;
385488
386544
  await clearTypingStatus({
385489
- client: connection.client,
386545
+ client: connection.actingClient,
385490
386546
  channelId,
385491
386547
  threadTs: resolvedThread,
385492
386548
  logger: this.logger
@@ -385532,14 +386588,16 @@ class SlackPlugin extends BaseChannelPlugin {
385532
386588
  async buildEnrichedPayload(instanceId, from, rawPayload) {
385533
386589
  const displayName = await this.resolveUserDisplayName(instanceId, from);
385534
386590
  const isDm = rawPayload.isDm;
385535
- const chatName = isDm ? displayName : undefined;
386591
+ const isMpim = rawPayload.isMpim === true;
386592
+ const isOneToOne = isDm && !isMpim;
386593
+ const chatName = isOneToOne ? displayName : undefined;
385536
386594
  return {
385537
386595
  ...rawPayload,
385538
386596
  displayName,
385539
386597
  senderName: displayName,
385540
386598
  pushName: displayName,
385541
386599
  chatName,
385542
- isGroup: !isDm
386600
+ isGroup: !isOneToOne
385543
386601
  };
385544
386602
  }
385545
386603
  async dispatchMessageFromDebounce(instanceId, args) {
@@ -385570,7 +386628,7 @@ class SlackPlugin extends BaseChannelPlugin {
385570
386628
  },
385571
386629
  onDmRejected: async (_instId, channelId, _userId, message2) => {
385572
386630
  try {
385573
- await sendTextMessage2(connection.client, {
386631
+ await sendTextMessage2(connection.actingClient, {
385574
386632
  channelId,
385575
386633
  text: message2,
385576
386634
  formatMode: "passthrough"
@@ -385587,7 +386645,7 @@ class SlackPlugin extends BaseChannelPlugin {
385587
386645
  channelAllowlist: config2.channelAllowlist,
385588
386646
  channelBlocklist: config2.channelBlocklist,
385589
386647
  channels: config2.channels
385590
- }, reliability);
386648
+ }, reliability, () => connection.actingUserId);
385591
386649
  setupReactionHandlers2(connection.app, instanceId, () => connection.botUserId, {
385592
386650
  onReaction: async (instId, messageId, chatId, userId, emoji, action) => {
385593
386651
  await this.handleReactionReceived(instId, messageId, chatId, userId, emoji, action);
@@ -385627,7 +386685,7 @@ class SlackPlugin extends BaseChannelPlugin {
385627
386685
  const formatMode = message2.metadata?.messageFormatMode ?? "convert";
385628
386686
  const replyToMode = config2.replyToMode ?? "all";
385629
386687
  const threadTs = this.resolveThreadTs(replyToMode, message2.replyTo, message2.threadId);
385630
- return sendTextMessage2(connection.client, {
386688
+ return sendTextMessage2(connection.actingClient, {
385631
386689
  channelId,
385632
386690
  text: message2.content.text ?? "",
385633
386691
  threadTs,
@@ -385645,7 +386703,7 @@ class SlackPlugin extends BaseChannelPlugin {
385645
386703
  if (message2.metadata?.base64) {
385646
386704
  const buffer2 = Buffer.from(message2.metadata.base64, "base64");
385647
386705
  const filename = message2.content.filename || `file-${Date.now()}`;
385648
- return uploadFile(connection.client, {
386706
+ return uploadFile(connection.actingClient, {
385649
386707
  channelId,
385650
386708
  content: buffer2,
385651
386709
  filename,
@@ -385656,7 +386714,7 @@ class SlackPlugin extends BaseChannelPlugin {
385656
386714
  if (!message2.content.mediaUrl) {
385657
386715
  throw new SlackError(SlackErrorCode.SEND_FAILED, "Media URL or base64 required");
385658
386716
  }
385659
- return uploadFileFromUrl(connection.client, {
386717
+ return uploadFileFromUrl(connection.actingClient, {
385660
386718
  channelId,
385661
386719
  url: message2.content.mediaUrl,
385662
386720
  filename: message2.content.filename || `file-${Date.now()}`,
@@ -385671,7 +386729,7 @@ class SlackPlugin extends BaseChannelPlugin {
385671
386729
  throw new SlackError(SlackErrorCode.SEND_FAILED, "Reaction requires emoji and target message");
385672
386730
  }
385673
386731
  const { addReaction: addReaction3 } = await Promise.resolve().then(() => (init_tools(), exports_tools));
385674
- await addReaction3(connection.client, channelId, targetTs, emoji, this.logger);
386732
+ await addReaction3(connection.actingClient, channelId, targetTs, emoji, this.logger);
385675
386733
  return targetTs;
385676
386734
  }
385677
386735
  async handleInboundFiles(instanceId, externalId, chatId, from, content, replyToId, rawPayload, platformTimestamp) {
@@ -385697,6 +386755,7 @@ class SlackPlugin extends BaseChannelPlugin {
385697
386755
  const timings = platformTimestamp ? this.captureInboundTimings(platformTimestamp) : undefined;
385698
386756
  const senderName = typeof rawPayload.senderName === "string" ? rawPayload.senderName : typeof rawPayload.displayName === "string" ? rawPayload.displayName : undefined;
385699
386757
  const chatName = typeof rawPayload.chatName === "string" ? rawPayload.chatName : undefined;
386758
+ const threadTs = typeof rawPayload.threadTs === "string" ? rawPayload.threadTs : undefined;
385700
386759
  const correlationId = await this.emitMessageReceived({
385701
386760
  instanceId,
385702
386761
  externalId,
@@ -385705,7 +386764,7 @@ class SlackPlugin extends BaseChannelPlugin {
385705
386764
  senderName,
385706
386765
  chatName,
385707
386766
  content,
385708
- replyToId,
386767
+ threadId: threadTs,
385709
386768
  rawPayload,
385710
386769
  timings
385711
386770
  });
@@ -387055,14 +388114,14 @@ var protectedSplitters = {
387055
388114
  expandable_blockquote: trySplitBlockquote,
387056
388115
  plain: () => null
387057
388116
  };
387058
- function splitLongProtectedSegment(segment, maxLength) {
387059
- if (segment.value.length <= maxLength)
387060
- return [segment.value];
387061
- const splitter = protectedSplitters[segment.type];
387062
- 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);
387063
388122
  if (result)
387064
388123
  return result;
387065
- return splitPlainByBoundaries(segment.value, maxLength);
388124
+ return splitPlainByBoundaries(segment2.value, maxLength);
387066
388125
  }
387067
388126
  function findNextProtected(html, from) {
387068
388127
  const patterns = [
@@ -387146,12 +388205,12 @@ class ChunkAccumulator {
387146
388205
  return this.chunks.length > 0 ? this.chunks : [""];
387147
388206
  }
387148
388207
  }
387149
- function processPlainSegment(acc, segment, maxLength) {
387150
- const plainParts = splitPlainHtmlPreservingTags(segment.value, maxLength);
388208
+ function processPlainSegment(acc, segment2, maxLength) {
388209
+ const plainParts = splitPlainHtmlPreservingTags(segment2.value, maxLength);
387151
388210
  acc.appendParts(plainParts);
387152
388211
  }
387153
- function processProtectedSegment(acc, segment, maxLength) {
387154
- const protectedParts = splitLongProtectedSegment(segment, maxLength);
388212
+ function processProtectedSegment(acc, segment2, maxLength) {
388213
+ const protectedParts = splitLongProtectedSegment(segment2, maxLength);
387155
388214
  for (const part of protectedParts) {
387156
388215
  if (part.length > maxLength) {
387157
388216
  const hardParts = splitPlainByBoundaries(part, maxLength);
@@ -387166,11 +388225,11 @@ function splitHtmlMessage(html, maxLength = 4096) {
387166
388225
  return [html];
387167
388226
  const segments = segmentHtml(html);
387168
388227
  const acc = new ChunkAccumulator(maxLength);
387169
- for (const segment of segments) {
387170
- if (segment.type === "plain") {
387171
- processPlainSegment(acc, segment, maxLength);
388228
+ for (const segment2 of segments) {
388229
+ if (segment2.type === "plain") {
388230
+ processPlainSegment(acc, segment2, maxLength);
387172
388231
  } else {
387173
- processProtectedSegment(acc, segment, maxLength);
388232
+ processProtectedSegment(acc, segment2, maxLength);
387174
388233
  }
387175
388234
  }
387176
388235
  return acc.result();
@@ -477591,6 +478650,7 @@ for (const [i, DOUBLE_BYTE_TOKEN] of DOUBLE_BYTE_TOKENS.entries()) {
477591
478650
 
477592
478651
  // ../../node_modules/.bun/baileys@vendor+baileys-v7.0.0-rc10.tgz+89588f7e3fe8b2e6/node_modules/baileys/lib/WABinary/jid-utils.js
477593
478652
  var S_WHATSAPP_NET = "@s.whatsapp.net";
478653
+ var SERVER_JID = "server@c.us";
477594
478654
  var PSA_WID = "0@c.us";
477595
478655
  var WAJIDDomains;
477596
478656
  (function(WAJIDDomains2) {
@@ -490823,6 +491883,181 @@ function buildAckStanza(node, errorCode, meId) {
490823
491883
  }
490824
491884
  return stanza;
490825
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
+ };
490826
492061
  // ../../node_modules/.bun/baileys@vendor+baileys-v7.0.0-rc10.tgz+89588f7e3fe8b2e6/node_modules/baileys/lib/Signal/Group/sender-key-name.js
490827
492062
  function isNull(str) {
490828
492063
  return str === null || str === "";
@@ -490874,14 +492109,14 @@ class SenderKeyName2 {
490874
492109
  }
490875
492110
 
490876
492111
  // ../../node_modules/.bun/baileys@vendor+baileys-v7.0.0-rc10.tgz+89588f7e3fe8b2e6/node_modules/baileys/lib/Signal/Group/sender-chain-key.js
490877
- var import_crypto18 = __toESM(require_crypto(), 1);
492112
+ var import_crypto19 = __toESM(require_crypto(), 1);
490878
492113
 
490879
492114
  // ../../node_modules/.bun/baileys@vendor+baileys-v7.0.0-rc10.tgz+89588f7e3fe8b2e6/node_modules/baileys/lib/Signal/Group/sender-message-key.js
490880
- var import_crypto17 = __toESM(require_crypto(), 1);
492115
+ var import_crypto18 = __toESM(require_crypto(), 1);
490881
492116
 
490882
492117
  class SenderMessageKey {
490883
492118
  constructor(iteration, seed) {
490884
- 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"));
490885
492120
  const keys = new Uint8Array(32);
490886
492121
  keys.set(new Uint8Array(derivative[0].slice(16)));
490887
492122
  keys.set(new Uint8Array(derivative[1].slice(0, 16)), 16);
@@ -490925,7 +492160,7 @@ class SenderChainKey {
490925
492160
  return this.chainKey;
490926
492161
  }
490927
492162
  getDerivative(seed, key) {
490928
- return import_crypto18.calculateMAC(key, seed);
492163
+ return import_crypto19.calculateMAC(key, seed);
490929
492164
  }
490930
492165
  }
490931
492166
 
@@ -491162,7 +492397,7 @@ class GroupSessionBuilder2 {
491162
492397
  }
491163
492398
  }
491164
492399
  // ../../node_modules/.bun/baileys@vendor+baileys-v7.0.0-rc10.tgz+89588f7e3fe8b2e6/node_modules/baileys/lib/Signal/Group/group_cipher.js
491165
- var import_crypto19 = __toESM(require_crypto(), 1);
492400
+ var import_crypto20 = __toESM(require_crypto(), 1);
491166
492401
 
491167
492402
  // ../../node_modules/.bun/baileys@vendor+baileys-v7.0.0-rc10.tgz+89588f7e3fe8b2e6/node_modules/baileys/lib/Signal/Group/sender-key-message.js
491168
492403
  var import_curve2 = __toESM(require_curve2(), 1);
@@ -491287,14 +492522,14 @@ class GroupCipher2 {
491287
492522
  }
491288
492523
  async getPlainText(iv, key, ciphertext) {
491289
492524
  try {
491290
- return import_crypto19.decrypt(key, ciphertext, iv);
492525
+ return import_crypto20.decrypt(key, ciphertext, iv);
491291
492526
  } catch (e) {
491292
492527
  throw new Error("InvalidMessageException");
491293
492528
  }
491294
492529
  }
491295
492530
  async getCipherText(iv, key, plaintext) {
491296
492531
  try {
491297
- return import_crypto19.encrypt(key, plaintext, iv);
492532
+ return import_crypto20.encrypt(key, plaintext, iv);
491298
492533
  } catch (e) {
491299
492534
  throw new Error("InvalidMessageException");
491300
492535
  }
@@ -493982,6 +495217,9 @@ var makeSocket = (config2) => {
493982
495217
  let epoch = 1;
493983
495218
  let keepAliveReq;
493984
495219
  let qrTimer;
495220
+ let passkeyHandoff;
495221
+ let passkeyLinkingCache;
495222
+ let passkeySkipHandoffUX = false;
493985
495223
  let closed = false;
493986
495224
  const socketEndHandlers = [];
493987
495225
  const onUnexpectedError = (err2, msg) => {
@@ -494337,6 +495575,156 @@ var makeSocket = (config2) => {
494337
495575
  const ciphered = aesEncryptCTR(authState.creds.pairingEphemeralKeyPair.public, key, randomIv);
494338
495576
  return Buffer.concat([salt, randomIv, ciphered]);
494339
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
+ };
494340
495728
  const sendWAMBuffer = (wamBuffer) => {
494341
495729
  return query({
494342
495730
  tag: "iq",
@@ -494366,6 +495754,12 @@ var makeSocket = (config2) => {
494366
495754
  ws.on("error", mapWebSocketError(end));
494367
495755
  ws.on("close", () => void end(new import_boom14.Boom("Connection Terminated", { statusCode: DisconnectReason.connectionClosed })));
494368
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
+ });
494369
495763
  ws.on("CB:iq,type:set,pair-device", async (stanza) => {
494370
495764
  const iq = {
494371
495765
  tag: "iq",
@@ -494593,6 +495987,8 @@ var makeSocket = (config2) => {
494593
495987
  digestKeyBundle,
494594
495988
  rotateSignedPreKey,
494595
495989
  requestPairingCode,
495990
+ sendPasskeyResponse,
495991
+ sendPasskeyConfirmation,
494596
495992
  updateServerTimeOffset,
494597
495993
  sendUnifiedSession,
494598
495994
  wamBuffer: publicWAMBuffer,
@@ -500123,6 +501519,7 @@ var MAX_QR_ATTEMPTS = 3;
500123
501519
  var qrCycleAttempts = new Map;
500124
501520
  var MAX_QR_CYCLES = 2;
500125
501521
  var activeQrCodes = new Map;
501522
+ var passkeyInstances = new Set;
500126
501523
  var connectionTimeouts = new Map;
500127
501524
  var CONNECTION_TIMEOUT_MS = 45000;
500128
501525
  var authenticatedInstances = new Set;
@@ -500219,6 +501616,7 @@ async function handleConnectionClose(plugin7, instanceId, lastDisconnect, config
500219
501616
  qrCodeAttempts.delete(instanceId);
500220
501617
  qrCycleAttempts.delete(instanceId);
500221
501618
  authenticatedInstances.delete(instanceId);
501619
+ passkeyInstances.delete(instanceId);
500222
501620
  cancelPendingReconnect(instanceId);
500223
501621
  await plugin7.handleDisconnected(instanceId, "Logged out from WhatsApp", false);
500224
501622
  return;
@@ -500266,6 +501664,7 @@ async function handleConnectionOpen(plugin7, instanceId, sock) {
500266
501664
  qrCodeAttempts.delete(instanceId);
500267
501665
  qrCycleAttempts.delete(instanceId);
500268
501666
  activeQrCodes.delete(instanceId);
501667
+ passkeyInstances.delete(instanceId);
500269
501668
  clearConnectionTimeout(instanceId);
500270
501669
  authenticatedInstances.add(instanceId);
500271
501670
  log51.info("Connection opened", { instanceId });
@@ -500273,8 +501672,16 @@ async function handleConnectionOpen(plugin7, instanceId, sock) {
500273
501672
  }
500274
501673
  function setupConnectionHandlers2(sock, plugin7, instanceId, onReconnect, clearAuthAndReconnect, config2 = DEFAULT_RECONNECT_CONFIG2) {
500275
501674
  sock.ev.on("connection.update", async (update) => {
500276
- const { connection, lastDisconnect, qr } = update;
500277
- 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)) {
500278
501685
  const shouldContinue = await handleQrCode(plugin7, instanceId, qr, clearAuthAndReconnect);
500279
501686
  if (!shouldContinue) {
500280
501687
  return;
@@ -500301,6 +501708,7 @@ function resetConnectionState2(instanceId) {
500301
501708
  qrCodeAttempts.delete(instanceId);
500302
501709
  qrCycleAttempts.delete(instanceId);
500303
501710
  activeQrCodes.delete(instanceId);
501711
+ passkeyInstances.delete(instanceId);
500304
501712
  authenticatedInstances.delete(instanceId);
500305
501713
  clearConnectionTimeout(instanceId);
500306
501714
  cancelPendingReconnect(instanceId);
@@ -500492,6 +501900,77 @@ function getMediaSize(msg) {
500492
501900
  var log52 = createLogger("whatsapp:messages");
500493
501901
  var fallbackDedupeCache3 = createInboundDedupeCache();
500494
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
+ }
500495
501974
  var contentExtractors = [
500496
501975
  {
500497
501976
  check: (m2) => !!m2.conversation,
@@ -500695,6 +502174,25 @@ var contentExtractors = [
500695
502174
  text: m2.buttonsResponseMessage?.selectedDisplayText ?? m2.buttonsResponseMessage?.selectedButtonId ?? undefined
500696
502175
  })
500697
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
+ },
500698
502196
  {
500699
502197
  check: (m2) => !!m2.senderKeyDistributionMessage,
500700
502198
  extract: () => null
@@ -501499,9 +502997,9 @@ function splitWhatsAppMessage(text, maxLength = DEFAULT_MAX_LENGTH2) {
501499
502997
  flushCurrent();
501500
502998
  current = piece;
501501
502999
  };
501502
- for (const segment of segments) {
501503
- if (segment.type === "text") {
501504
- const paragraphUnits = splitByDelimiter(segment.value, `
503000
+ for (const segment2 of segments) {
503001
+ if (segment2.type === "text") {
503002
+ const paragraphUnits = splitByDelimiter(segment2.value, `
501505
503003
 
501506
503004
  `);
501507
503005
  const textPieces = packUnits(paragraphUnits, maxLength);
@@ -501510,12 +503008,12 @@ function splitWhatsAppMessage(text, maxLength = DEFAULT_MAX_LENGTH2) {
501510
503008
  }
501511
503009
  continue;
501512
503010
  }
501513
- if (segment.value.length <= maxLength) {
501514
- appendPiece(segment.value);
503011
+ if (segment2.value.length <= maxLength) {
503012
+ appendPiece(segment2.value);
501515
503013
  continue;
501516
503014
  }
501517
503015
  flushCurrent();
501518
- const splitCode = splitHugeCodeBlock(segment.value, maxLength);
503016
+ const splitCode = splitHugeCodeBlock(segment2.value, maxLength);
501519
503017
  for (const piece of splitCode) {
501520
503018
  appendPiece(piece);
501521
503019
  }
@@ -502131,6 +503629,7 @@ class WhatsAppPlugin extends BaseChannelPlugin {
502131
503629
  version = "1.0.0";
502132
503630
  capabilities = WHATSAPP_CAPABILITIES;
502133
503631
  sockets = new Map;
503632
+ passkeyStates = new Map;
502134
503633
  pluginConfig = {};
502135
503634
  historySyncCallbacks = new Map;
502136
503635
  historyPushFetchCount = new Map;
@@ -502496,6 +503995,7 @@ class WhatsAppPlugin extends BaseChannelPlugin {
502496
503995
  async onInitialize(_context) {}
502497
503996
  async connect(instanceId, config2) {
502498
503997
  if (config2.options?.forceNewQr === true) {
503998
+ this.passkeyStates.delete(instanceId);
502499
503999
  const existingSocket = this.sockets.get(instanceId);
502500
504000
  if (existingSocket) {
502501
504001
  existingSocket.ev.removeAllListeners("connection.update");
@@ -502589,6 +504089,7 @@ class WhatsAppPlugin extends BaseChannelPlugin {
502589
504089
  await this.emitInstanceDisconnected(instanceId, "User requested disconnect");
502590
504090
  }
502591
504091
  clearInstanceCaches(instanceId) {
504092
+ this.passkeyStates.delete(instanceId);
502592
504093
  this.groupMetadataCache.delete(instanceId);
502593
504094
  this.groupsCache.delete(instanceId);
502594
504095
  this.contactsCache.delete(instanceId);
@@ -502632,6 +504133,91 @@ class WhatsAppPlugin extends BaseChannelPlugin {
502632
504133
  throw new WhatsAppError(ErrorCode2.PAIRING_FAILED, `Failed to request pairing code: ${message2}`);
502633
504134
  }
502634
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
+ }
502635
504221
  buildQuotedOptions(message2, jid) {
502636
504222
  if (!message2.replyTo)
502637
504223
  return;
@@ -503556,6 +505142,7 @@ class WhatsAppPlugin extends BaseChannelPlugin {
503556
505142
  }
503557
505143
  }
503558
505144
  async handleConnected(instanceId, sock) {
505145
+ this.passkeyStates.delete(instanceId);
503559
505146
  let profileName;
503560
505147
  let profilePicUrl;
503561
505148
  let ownerIdentifier;