@automagik/omni 2.260901.2 → 2.260902.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -242901,7 +242901,7 @@ var init_sentry_scrub = __esm(() => {
242901
242901
  var require_package7 = __commonJS((exports, module) => {
242902
242902
  module.exports = {
242903
242903
  name: "@omni/api",
242904
- version: "2.260901.2",
242904
+ version: "2.260902.1",
242905
242905
  type: "module",
242906
242906
  exports: {
242907
242907
  ".": {
@@ -345619,6 +345619,10 @@ async function consumeStream(generator, sender, instanceId, chatId, traceId) {
345619
345619
  function extractThreadId(messages4) {
345620
345620
  return (messages4[0]?.payload.rawPayload ?? {}).threadId;
345621
345621
  }
345622
+ function extractCancelThreadId(messages4) {
345623
+ const parsed = CancelThreadPayloadSchema.safeParse(messages4[0]?.payload.rawPayload ?? {});
345624
+ return (parsed.success ? parsed.data.cancelThreadId : undefined) ?? extractThreadId(messages4);
345625
+ }
345622
345626
  function extractKhalSessionId(messages4) {
345623
345627
  for (const message2 of messages4) {
345624
345628
  const rawPayload = message2.payload.rawPayload ?? {};
@@ -345759,8 +345763,9 @@ async function dispatchViaStreamingProvider(services, instance4, messages4, trig
345759
345763
  const streamKey = `${instance4.id}:${chatId}`;
345760
345764
  activeStreams.set(streamKey, sender);
345761
345765
  const cancelController = new AbortController;
345762
- const runKey = runAbortKey(instance4.id, chatId, rawThreadId);
345763
- activeRunAborts.set(runKey, { controller: cancelController, startedAt: Date.now(), sender });
345766
+ const runKey = runAbortKey(instance4.id, chatId, extractCancelThreadId(messages4));
345767
+ const runEntry = { controller: cancelController, startedAt: Date.now(), sender };
345768
+ activeRunAborts.set(runKey, runEntry);
345764
345769
  trigger.abortSignal = cancelController.signal;
345765
345770
  const streamDispatchStart = Date.now();
345766
345771
  try {
@@ -345794,7 +345799,8 @@ async function dispatchViaStreamingProvider(services, instance4, messages4, trig
345794
345799
  return false;
345795
345800
  } finally {
345796
345801
  activeStreams.delete(streamKey);
345797
- activeRunAborts.delete(runKey);
345802
+ if (activeRunAborts.get(runKey) === runEntry)
345803
+ activeRunAborts.delete(runKey);
345798
345804
  }
345799
345805
  }
345800
345806
  function formatMediaContent(msg) {
@@ -346004,10 +346010,11 @@ function resolveReplySuppression(flags) {
346004
346010
  return "run_cancelled_by_user";
346005
346011
  return null;
346006
346012
  }
346007
- async function triggerProviderWithCancellation(provider, trigger, spanAttributes, instanceId, chatId, traceId) {
346013
+ async function triggerProviderWithCancellation(provider, trigger, spanAttributes, instanceId, chatId, traceId, cancelThreadId = trigger.source.threadId) {
346008
346014
  const cancelController = new AbortController;
346009
- const runKey = runAbortKey(instanceId, chatId, trigger.source.threadId);
346010
- activeRunAborts.set(runKey, { controller: cancelController, startedAt: Date.now() });
346015
+ const runKey = runAbortKey(instanceId, chatId, cancelThreadId);
346016
+ const runEntry = { controller: cancelController, startedAt: Date.now() };
346017
+ activeRunAborts.set(runKey, runEntry);
346011
346018
  try {
346012
346019
  const result = await withLifecycleSpan("omni.dispatch_to_agno", spanAttributes, () => provider.trigger({
346013
346020
  ...trigger,
@@ -346022,7 +346029,8 @@ async function triggerProviderWithCancellation(provider, trigger, spanAttributes
346022
346029
  }
346023
346030
  throw err2;
346024
346031
  } finally {
346025
- activeRunAborts.delete(runKey);
346032
+ if (activeRunAborts.get(runKey) === runEntry)
346033
+ activeRunAborts.delete(runKey);
346026
346034
  }
346027
346035
  }
346028
346036
  async function dispatchViaProvider(services, instance4, messages4, triggerType, channel5, chatId, senderId, personId, senderName, traceId, rawEvent, db2, extraContextMessages, senderAgentId) {
@@ -346098,7 +346106,7 @@ async function dispatchViaProvider(services, instance4, messages4, triggerType,
346098
346106
  ...lifecycleBase,
346099
346107
  stage: "dispatch_to_agno",
346100
346108
  extra: { trigger_type: triggerType, provider_id: provider.id, provider_schema: provider.schema }
346101
- }), instance4.id, chatId, traceId);
346109
+ }), instance4.id, chatId, traceId, extractCancelThreadId(messages4));
346102
346110
  if (!triggerOutcome)
346103
346111
  return true;
346104
346112
  const { result, cancelled: runCancelled } = triggerOutcome;
@@ -346488,6 +346496,7 @@ function takeQueuedAck(chatKey) {
346488
346496
  }
346489
346497
  async function ackQueuedMessage(info2) {
346490
346498
  const chatKey = `${info2.instanceId}:${info2.chatId}`;
346499
+ let entry;
346491
346500
  try {
346492
346501
  const existing = queuedMessageAcks.get(chatKey);
346493
346502
  if (existing && Date.now() - existing.at < QUEUED_ACK_TIMEOUT_MS)
@@ -346496,16 +346505,22 @@ async function ackQueuedMessage(info2) {
346496
346505
  const messageId = info2.message.payload.externalId;
346497
346506
  if (!instance4 || !messageId)
346498
346507
  return;
346499
- const entry = { at: Date.now() };
346508
+ entry = { at: Date.now() };
346500
346509
  queuedMessageAcks.set(chatKey, entry);
346501
346510
  const channel5 = info2.message.metadata.channelType ?? instance4.channel;
346502
346511
  const plugin10 = await getPlugin(channel5) ?? null;
346503
- entry.handle = startAck(plugin10, createPluginAckProvider(plugin10), instance4.id, info2.chatId, messageId, channel5, {
346512
+ const handle = startAck(plugin10, createPluginAckProvider(plugin10), instance4.id, info2.chatId, messageId, channel5, {
346504
346513
  ...buildAckConfig(instance4),
346505
346514
  ackTimeoutMs: QUEUED_ACK_TIMEOUT_MS
346506
346515
  });
346516
+ if (queuedMessageAcks.get(chatKey) !== entry) {
346517
+ handle.release();
346518
+ return;
346519
+ }
346520
+ entry.handle = handle;
346507
346521
  } catch (error3) {
346508
- queuedMessageAcks.delete(chatKey);
346522
+ if (entry && queuedMessageAcks.get(chatKey) === entry)
346523
+ queuedMessageAcks.delete(chatKey);
346509
346524
  log91.debug("Failed to ack queued message", { chatId: info2.chatId, error: String(error3) });
346510
346525
  }
346511
346526
  }
@@ -347876,7 +347891,7 @@ async function setupAgentDispatcher(eventBus, services, db2) {
347876
347891
  log91.info("Agent dispatcher shutdown complete");
347877
347892
  };
347878
347893
  }
347879
- var import_api32, log91, agentDispatchLimiter, _natsGenieProviderCtor, QUOTED_MESSAGE_MAX_CHARS = 4000, DM_HISTORY_LIMIT = 20, LIFECYCLE_PREVIEW_MAX_CHARS = 160, LIFECYCLE_SENSITIVE_KEY_PARTS, PRESENCE_DEFAULT_MIN_MS = 5000, PRESENCE_DEFAULT_MAX_WAIT_MS = 30000, activeMessageDebouncer = null, DEFAULT_DISPATCH_ERROR_MESSAGE = "Opa, tive um probleminha aqui \uD83D\uDE05 Pode mandar de novo?", DEFAULT_ERROR_HANDOFF_MESSAGE = "T\xF4 com um probleminha t\xE9cnico aqui agora. Logo algu\xE9m do time vai entrar em contato com voc\xEA.", TRANSIENT_DISPATCH_ERROR_PATTERNS, TRANSIENT_DISPATCH_RETRY_DELAYS_MS, CHANNEL_MESSAGE_LIMITS, DEFAULT_MESSAGE_LIMIT = 4000, MEDIA_BASE_PATH3, MEDIA_ICONS, MEDIA_WAIT_NULL, mediaCompletions, mediaResultCache, MEDIA_WAIT_TIMEOUT_MS = 30000, DEFAULT_SEND_MEDIA_PATH_TYPES, BOT_PREFIX = "\uD83E\uDD16 ", activeStreams, activeRunAborts, sessionActivityStore, PROC_REACT_START, PROC_REACT_DONE = "\u2705", queuedMessageAcks, QUEUED_ACK_TIMEOUT_MS = 120000, providerCache, openclawClientPool, _openClawClientCtor, nullFilterWarnedInstances, ACTIVE_OWNER_IDENTIFIER_CACHE_TTL_MS = 1e4, cachedActiveOwnerIdentifiers = null, cachedActiveOwnerIdentifiersAt = 0, tenantCachedActiveOwnerIdentifiers, DEFAULT_GATE_MODEL = "gemini-3-flash-preview", GATE_TIMEOUT_MS = 3000, setupAgentResponder;
347894
+ var import_api32, log91, agentDispatchLimiter, _natsGenieProviderCtor, QUOTED_MESSAGE_MAX_CHARS = 4000, DM_HISTORY_LIMIT = 20, LIFECYCLE_PREVIEW_MAX_CHARS = 160, LIFECYCLE_SENSITIVE_KEY_PARTS, PRESENCE_DEFAULT_MIN_MS = 5000, PRESENCE_DEFAULT_MAX_WAIT_MS = 30000, activeMessageDebouncer = null, DEFAULT_DISPATCH_ERROR_MESSAGE = "Opa, tive um probleminha aqui \uD83D\uDE05 Pode mandar de novo?", DEFAULT_ERROR_HANDOFF_MESSAGE = "T\xF4 com um probleminha t\xE9cnico aqui agora. Logo algu\xE9m do time vai entrar em contato com voc\xEA.", TRANSIENT_DISPATCH_ERROR_PATTERNS, TRANSIENT_DISPATCH_RETRY_DELAYS_MS, CHANNEL_MESSAGE_LIMITS, DEFAULT_MESSAGE_LIMIT = 4000, MEDIA_BASE_PATH3, MEDIA_ICONS, MEDIA_WAIT_NULL, mediaCompletions, mediaResultCache, MEDIA_WAIT_TIMEOUT_MS = 30000, DEFAULT_SEND_MEDIA_PATH_TYPES, BOT_PREFIX = "\uD83E\uDD16 ", activeStreams, activeRunAborts, CancelThreadPayloadSchema, sessionActivityStore, PROC_REACT_START, PROC_REACT_DONE = "\u2705", queuedMessageAcks, QUEUED_ACK_TIMEOUT_MS = 120000, providerCache, openclawClientPool, _openClawClientCtor, nullFilterWarnedInstances, ACTIVE_OWNER_IDENTIFIER_CACHE_TTL_MS = 1e4, cachedActiveOwnerIdentifiers = null, cachedActiveOwnerIdentifiersAt = 0, tenantCachedActiveOwnerIdentifiers, DEFAULT_GATE_MODEL = "gemini-3-flash-preview", GATE_TIMEOUT_MS = 3000, setupAgentResponder;
347880
347895
  var init_agent_dispatcher = __esm(() => {
347881
347896
  init_src2();
347882
347897
  init_src();
@@ -347884,6 +347899,7 @@ var init_agent_dispatcher = __esm(() => {
347884
347899
  init_src8();
347885
347900
  init_esm5();
347886
347901
  init_drizzle_orm();
347902
+ init_zod();
347887
347903
  init_idempotency();
347888
347904
  init_sentry_scrub();
347889
347905
  init_agent_runner();
@@ -347934,6 +347950,7 @@ var init_agent_dispatcher = __esm(() => {
347934
347950
  DEFAULT_SEND_MEDIA_PATH_TYPES = ["image", "video", "document"];
347935
347951
  activeStreams = new Map;
347936
347952
  activeRunAborts = new Map;
347953
+ CancelThreadPayloadSchema = exports_external.object({ cancelThreadId: exports_external.string().min(1).optional() });
347937
347954
  sessionActivityStore = new InMemorySessionActivityStore;
347938
347955
  PROC_REACT_START = { audio: "\uD83C\uDFA7", image: "\uD83D\uDC40", video: "\uD83D\uDC40", document: "\uD83D\uDC40" };
347939
347956
  queuedMessageAcks = new Map;
@@ -352912,9 +352929,23 @@ function openProviderSecrets(tenantId, row) {
352912
352929
 
352913
352930
  class ProviderService {
352914
352931
  db;
352932
+ clientCacheInvalidators = [];
352915
352933
  constructor(db2) {
352916
352934
  this.db = db2;
352917
352935
  }
352936
+ onProviderChanged(invalidate) {
352937
+ this.clientCacheInvalidators.push(invalidate);
352938
+ }
352939
+ invalidateCaches(providerId) {
352940
+ runAfterTenantCommit(() => {
352941
+ invalidateProviderCache(providerId);
352942
+ for (const invalidate of this.clientCacheInvalidators) {
352943
+ try {
352944
+ invalidate(providerId);
352945
+ } catch {}
352946
+ }
352947
+ });
352948
+ }
352918
352949
  get tenantId() {
352919
352950
  return currentTenantScope()?.tenantId ?? null;
352920
352951
  }
@@ -352955,7 +352986,7 @@ class ProviderService {
352955
352986
  if (!updated) {
352956
352987
  throw new NotFoundError("AgentProvider", id);
352957
352988
  }
352958
- invalidateProviderCache(id);
352989
+ this.invalidateCaches(id);
352959
352990
  return openProviderSecrets(tenantId, updated);
352960
352991
  }
352961
352992
  async delete(id) {
@@ -352963,7 +352994,7 @@ class ProviderService {
352963
352994
  if (!result.length) {
352964
352995
  throw new NotFoundError("AgentProvider", id);
352965
352996
  }
352966
- invalidateProviderCache(id);
352997
+ this.invalidateCaches(id);
352967
352998
  }
352968
352999
  async checkHealth(id) {
352969
353000
  const provider = await this.getById(id);
@@ -355406,6 +355437,9 @@ function createServices(db2, eventBus) {
355406
355437
  batchJobs2.setAuthPlane(authPlane.db);
355407
355438
  const mediaStorage = new MediaStorageService(db2);
355408
355439
  mediaStorage.setTenantAdmissibilityCheck((tenantId) => isTenantWorkAdmissible(authPlane.db, tenantId));
355440
+ const providers2 = new ProviderService(db2);
355441
+ const agentRunner = new AgentRunnerService(db2);
355442
+ providers2.onProviderChanged((providerId) => agentRunner.clearCache(providerId));
355409
355443
  return {
355410
355444
  agents: new AgentService(db2, eventBus),
355411
355445
  agentState: new AgentStateService(eventBus),
@@ -355418,7 +355452,7 @@ function createServices(db2, eventBus) {
355418
355452
  events: new EventService(db2),
355419
355453
  settings,
355420
355454
  access: new AccessService(db2, eventBus, accessCache),
355421
- providers: new ProviderService(db2),
355455
+ providers: providers2,
355422
355456
  routes: new RouteService(db2, routeResolver),
355423
355457
  routeResolver,
355424
355458
  deadLetters,
@@ -355430,7 +355464,7 @@ function createServices(db2, eventBus) {
355430
355464
  messages: new MessageService(db2, eventBus),
355431
355465
  syncJobs: new SyncJobService(db2, eventBus),
355432
355466
  batchJobs: batchJobs2,
355433
- agentRunner: new AgentRunnerService(db2),
355467
+ agentRunner,
355434
355468
  tts,
355435
355469
  turns: new TurnService(db2),
355436
355470
  consumerOffsets: new ConsumerOffsetService(db2),
@@ -355833,6 +355867,13 @@ var init_error3 = __esm(() => {
355833
355867
  });
355834
355868
 
355835
355869
  // ../api/src/middleware/rate-limit.ts
355870
+ function resolveClientIp(trustedProxyHeaderValue, remoteAddr) {
355871
+ const raw2 = trustedProxyHeaderValue ?? remoteAddr;
355872
+ if (!raw2)
355873
+ return;
355874
+ const hops = raw2.split(",").map((hop) => hop.trim()).filter(Boolean);
355875
+ return hops.at(-1);
355876
+ }
355836
355877
  function createRateLimiter2(config4 = RATE_LIMITS.general, keyPrefix = "ratelimit") {
355837
355878
  return createMiddleware(async (c, next) => {
355838
355879
  const apiKey = c.get("apiKey");
@@ -355840,8 +355881,7 @@ function createRateLimiter2(config4 = RATE_LIMITS.general, keyPrefix = "ratelimi
355840
355881
  const trustedProxyIp = trustedProxyHeader ? c.req.header(trustedProxyHeader) : undefined;
355841
355882
  const server = c.env;
355842
355883
  const remoteAddr = server?.remoteAddr ?? server?.requestIP?.(c.req.raw)?.address;
355843
- const rawIp = trustedProxyIp ?? remoteAddr;
355844
- const normalizedIp = rawIp?.split(",")[0]?.trim() || undefined;
355884
+ const normalizedIp = resolveClientIp(trustedProxyIp, remoteAddr);
355845
355885
  const identifier = apiKey?.id ? `api:${apiKey.id}` : normalizedIp ? `ip:${normalizedIp}` : "anon";
355846
355886
  const key = `${keyPrefix}:${identifier}`;
355847
355887
  const now = Date.now();
@@ -360507,7 +360547,10 @@ var init_messages4 = __esm(() => {
360507
360547
  instanceId: exports_external.string().uuid().openapi({ description: "Instance UUID" }),
360508
360548
  to: exports_external.string().openapi({ description: "Recipient" }),
360509
360549
  audioSizeKb: exports_external.number().openapi({ description: "Audio size in KB" }),
360510
- durationMs: exports_external.number().openapi({ description: "Audio duration in ms" })
360550
+ durationMs: exports_external.number().openapi({ description: "Audio duration in ms" }),
360551
+ senderAgentId: exports_external.string().uuid().nullable().optional().openapi({
360552
+ description: "Present when the request set sentBy: 'agent' \u2014 the agent the send was attributed to, or null when the instance has no configured agent (attribution did not happen)"
360553
+ })
360511
360554
  });
360512
360555
  });
360513
360556
 
@@ -361627,6 +361670,7 @@ var init_voice = __esm(() => {
361627
361670
 
361628
361671
  // ../api/src/schemas/openapi/webhooks.ts
361629
361672
  function registerWebhookSchemas(registry4) {
361673
+ registry4.register("WebhookSignatureConfig", WebhookSignatureConfigSchema);
361630
361674
  registry4.register("WebhookSource", WebhookSourceSchema);
361631
361675
  registry4.register("CreateWebhookSourceRequest", CreateWebhookSourceSchema);
361632
361676
  registry4.register("TriggerEventRequest", TriggerEventSchema);
@@ -361767,16 +361811,22 @@ function registerWebhookSchemas(registry4) {
361767
361811
  }
361768
361812
  var WebhookSignatureConfigSchema, WebhookSourceSchema, CreateWebhookSourceSchema, TriggerEventSchema, WebhookReceiveResponseSchema;
361769
361813
  var init_webhooks2 = __esm(() => {
361814
+ init_src5();
361770
361815
  init_zod_openapi();
361771
361816
  init_common4();
361772
361817
  WebhookSignatureConfigSchema = exports_external.object({
361773
- algorithm: exports_external.enum(["hmac-sha256", "hmac-sha1", "token-match"]).openapi({
361818
+ algorithm: exports_external.enum(webhookSignatureAlgorithms).openapi({
361774
361819
  description: "How to verify: HMAC over the raw request body, or direct token match"
361775
361820
  }),
361776
- header: exports_external.string().openapi({ description: "Header carrying the signature/token (e.g. X-Hub-Signature-256)" }),
361777
- prefix: exports_external.string().optional().openapi({
361778
- description: 'Prefix before the hex digest (e.g. "sha256="). HMAC algorithms only \u2014 rejected with token-match'
361821
+ header: exports_external.string().min(1).max(200).openapi({
361822
+ description: "Header carrying the signature/token (e.g. X-Hub-Signature-256). 1-200 characters"
361823
+ }),
361824
+ prefix: exports_external.string().max(50).optional().openapi({
361825
+ description: 'Prefix before the hex digest (e.g. "sha256="), at most 50 characters. HMAC algorithms only \u2014 rejected with token-match'
361779
361826
  })
361827
+ }).refine((config4) => config4.algorithm !== "token-match" || config4.prefix === undefined, {
361828
+ message: "prefix is not applicable to algorithm 'token-match'",
361829
+ path: ["prefix"]
361780
361830
  });
361781
361831
  WebhookSourceSchema = exports_external.object({
361782
361832
  id: exports_external.string().uuid().openapi({ description: "Source UUID" }),
@@ -361790,14 +361840,14 @@ var init_webhooks2 = __esm(() => {
361790
361840
  updatedAt: exports_external.string().datetime().openapi({ description: "Last update timestamp" })
361791
361841
  });
361792
361842
  CreateWebhookSourceSchema = exports_external.object({
361793
- name: exports_external.string().min(1).max(100).openapi({ description: "Unique source name" }),
361843
+ name: exports_external.string().min(1).max(100).openapi({ description: "Unique source name (e.g., github, stripe, agno)" }),
361794
361844
  description: exports_external.string().optional().openapi({ description: "Description" }),
361795
361845
  expectedHeaders: exports_external.record(exports_external.string(), exports_external.boolean()).optional().openapi({ description: "Headers to validate" }),
361796
361846
  signatureConfig: WebhookSignatureConfigSchema.nullable().optional().openapi({
361797
- description: "Signature verification config; required for the source to be reachable on the public ingress"
361847
+ description: "Signature verification config; required for the source to be reachable on the public ingress. " + "Always paired with signatureSecret: a config without a stored secret is rejected (400), and clearing " + "the config (null) also clears the stored secret."
361798
361848
  }),
361799
361849
  signatureSecret: exports_external.string().min(8).max(512).nullable().optional().openapi({
361800
- description: "Shared secret used by signatureConfig (write-only, never returned)"
361850
+ description: "Shared secret used by signatureConfig (write-only, never returned; 8-512 characters). Cannot be set " + "without a signatureConfig (given in the same request, or already stored on update); null clears it."
361801
361851
  }),
361802
361852
  enabled: exports_external.boolean().default(true).openapi({ description: "Whether enabled" })
361803
361853
  });
@@ -365273,6 +365323,27 @@ var init_handoffs = __esm(() => {
365273
365323
  });
365274
365324
  });
365275
365325
 
365326
+ // ../api/src/lib/whatsapp-business-connection.ts
365327
+ function applyWhatsAppBusinessConnectionOptions(options, input) {
365328
+ if (input.metaAccessToken)
365329
+ options.metaAccessToken = input.metaAccessToken;
365330
+ if (input.metaPhoneNumberId)
365331
+ options.metaPhoneNumberId = input.metaPhoneNumberId;
365332
+ if (input.metaWabaId)
365333
+ options.metaWabaId = input.metaWabaId;
365334
+ if (input.metaAppId)
365335
+ options.metaAppId = input.metaAppId;
365336
+ if (input.metaBusinessId)
365337
+ options.metaBusinessId = input.metaBusinessId;
365338
+ const apiVersion = process.env.META_GRAPH_API_VERSION ?? input.metaApiVersion;
365339
+ if (apiVersion)
365340
+ options.metaApiVersion = apiVersion;
365341
+ if (input.metaDisplayPhoneNumber)
365342
+ options.metaDisplayPhoneNumber = input.metaDisplayPhoneNumber;
365343
+ if (input.metaConnectionMethod)
365344
+ options.metaConnectionMethod = input.metaConnectionMethod;
365345
+ }
365346
+
365276
365347
  // ../../node_modules/.bun/qrcode-terminal@0.12.0/node_modules/qrcode-terminal/vendor/QRCode/QRMode.js
365277
365348
  var require_QRMode = __commonJS((exports, module) => {
365278
365349
  module.exports = {
@@ -366430,7 +366501,7 @@ class AgentReplayService {
366430
366501
  SELECT 1 FROM ${messages2} AS reply
366431
366502
  WHERE reply.chat_id = ${messages2.chatId}
366432
366503
  AND reply.is_from_me = true
366433
- AND COALESCE((reply.raw_payload ->> 'omniSystemNotice')::boolean, false) = false
366504
+ AND (reply.raw_payload -> 'omniSystemNotice') IS DISTINCT FROM 'true'::jsonb
366434
366505
  AND reply.platform_timestamp > ${messages2.platformTimestamp}
366435
366506
  AND reply.deleted_at IS NULL
366436
366507
  )`, cursorId ? or2(gt(messages2.platformTimestamp, cursorTimestamp), and2(eq(messages2.platformTimestamp, cursorTimestamp), gt(messages2.id, cursorId))) : gt(messages2.platformTimestamp, cursorTimestamp), lte(messages2.platformTimestamp, now))).orderBy(asc(messages2.platformTimestamp), asc(messages2.id)).limit(PAGE_SIZE));
@@ -366698,6 +366769,9 @@ function applyChannelSpecificConnectionOptions(options, input) {
366698
366769
  case "hermes":
366699
366770
  applyHermesConnectionOptions(options, input);
366700
366771
  return;
366772
+ case "whatsapp-business":
366773
+ applyWhatsAppBusinessConnectionOptions(options, input);
366774
+ return;
366701
366775
  }
366702
366776
  }
366703
366777
  function buildInstanceConnectionOptions(input) {
@@ -366841,7 +366915,15 @@ function buildConnectConnectionOptions(instance4, body, forceNewQr) {
366841
366915
  hermesUsername: body.hermesUsername ?? instance4.hermesUsername,
366842
366916
  hermesPassword: body.hermesPassword ?? instance4.hermesPassword,
366843
366917
  hermesMediaId: body.hermesMediaId ?? instance4.hermesMediaId,
366844
- hermesTemplateNamespace: body.hermesTemplateNamespace ?? instance4.hermesTemplateNamespace
366918
+ hermesTemplateNamespace: body.hermesTemplateNamespace ?? instance4.hermesTemplateNamespace,
366919
+ metaAccessToken: instance4.metaAccessToken,
366920
+ metaPhoneNumberId: instance4.metaPhoneNumberId,
366921
+ metaWabaId: instance4.metaWabaId,
366922
+ metaAppId: instance4.metaAppId,
366923
+ metaBusinessId: instance4.metaBusinessId,
366924
+ metaApiVersion: instance4.metaApiVersion,
366925
+ metaDisplayPhoneNumber: instance4.metaDisplayPhoneNumber,
366926
+ metaConnectionMethod: instance4.metaConnectionMethod
366845
366927
  });
366846
366928
  }
366847
366929
  function hydrateConnectionOptionsForInstance(plugin10, instance4, options) {
@@ -367579,6 +367661,9 @@ var init_instances3 = __esm(() => {
367579
367661
  if (instance4.channel === "hermes") {
367580
367662
  applyHermesConnectionOptions(restartOptions, instance4);
367581
367663
  }
367664
+ if (instance4.channel === "whatsapp-business") {
367665
+ applyWhatsAppBusinessConnectionOptions(restartOptions, instance4);
367666
+ }
367582
367667
  if (instance4.channel === "whatsapp-baileys" && instance4.markOnlineOnConnect != null) {
367583
367668
  restartOptions.whatsapp = {
367584
367669
  ...restartOptions.whatsapp,
@@ -371179,7 +371264,12 @@ var init_messages5 = __esm(() => {
371179
371264
  ...list ? { list } : {}
371180
371265
  },
371181
371266
  replyTo,
371182
- metadata: { ...mentions ? { mentions } : {}, ...replyContext, ...senderAgentId ? { senderAgentId } : {} }
371267
+ metadata: {
371268
+ ...mentions ? { mentions } : {},
371269
+ ...replyContext,
371270
+ ...senderAgentId ? { senderAgentId } : {},
371271
+ ...instance4.messageFormatMode ? { messageFormatMode: instance4.messageFormatMode } : {}
371272
+ }
371183
371273
  };
371184
371274
  if (correlationId && tracker.isTracking(correlationId)) {
371185
371275
  tracker.recordCheckpoint(correlationId, "T8", JOURNEY_STAGES.T8);
@@ -374111,35 +374201,41 @@ var init_voice2 = __esm(() => {
374111
374201
  });
374112
374202
  });
374113
374203
 
374204
+ // ../api/src/utils/json-body.ts
374205
+ function parseJsonObjectBody(rawBody) {
374206
+ if (!rawBody)
374207
+ return {};
374208
+ let parsed;
374209
+ try {
374210
+ parsed = JSON.parse(rawBody);
374211
+ } catch {
374212
+ throw new ValidationError2(NOT_AN_OBJECT);
374213
+ }
374214
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
374215
+ throw new ValidationError2(NOT_AN_OBJECT);
374216
+ }
374217
+ return parsed;
374218
+ }
374219
+ var NOT_AN_OBJECT = "Request body must be a JSON object";
374220
+ var init_json_body = __esm(() => {
374221
+ init_src();
374222
+ });
374223
+
374114
374224
  // ../api/src/routes/v2/webhooks.ts
374115
374225
  function sanitizeSource(source) {
374116
374226
  const { signatureSecret, ...rest } = source;
374117
374227
  return { ...rest, hasSignatureSecret: Boolean(signatureSecret) };
374118
374228
  }
374119
- var webhooksRoutes, signatureConfigSchema, createWebhookSourceSchema, updateWebhookSourceSchema, listQuerySchema21, triggerEventSchema;
374229
+ var webhooksRoutes, createWebhookSourceSchema, updateWebhookSourceSchema, listQuerySchema21, triggerEventSchema;
374120
374230
  var init_webhooks3 = __esm(() => {
374121
374231
  init_dist6();
374122
- init_src5();
374123
374232
  init_dist2();
374124
374233
  init_zod();
374234
+ init_webhooks2();
374125
374235
  init_api_keys();
374236
+ init_json_body();
374126
374237
  webhooksRoutes = new Hono2;
374127
- signatureConfigSchema = exports_external.object({
374128
- algorithm: exports_external.enum(webhookSignatureAlgorithms).describe("How to verify: HMAC over the raw body, or token match"),
374129
- header: exports_external.string().min(1).max(200).describe("Header carrying the signature/token (e.g. X-Hub-Signature-256)"),
374130
- prefix: exports_external.string().max(50).optional().describe("Prefix before the hex digest (HMAC algorithms only)")
374131
- }).refine((config4) => config4.algorithm !== "token-match" || config4.prefix === undefined, {
374132
- message: "prefix is not applicable to algorithm 'token-match'",
374133
- path: ["prefix"]
374134
- });
374135
- createWebhookSourceSchema = exports_external.object({
374136
- name: exports_external.string().min(1).max(100).describe("Unique source name (e.g., github, stripe, agno)"),
374137
- description: exports_external.string().optional().describe("Description of the webhook source"),
374138
- expectedHeaders: exports_external.record(exports_external.string(), exports_external.boolean()).optional().describe("Headers to validate"),
374139
- signatureConfig: signatureConfigSchema.nullable().optional().describe("Signature verification config"),
374140
- signatureSecret: exports_external.string().min(8).max(512).nullable().optional().describe("Shared secret (write-only)"),
374141
- enabled: exports_external.boolean().default(true).describe("Whether source is enabled")
374142
- });
374238
+ createWebhookSourceSchema = CreateWebhookSourceSchema;
374143
374239
  updateWebhookSourceSchema = createWebhookSourceSchema.partial();
374144
374240
  listQuerySchema21 = exports_external.object({
374145
374241
  enabled: exports_external.coerce.boolean().optional()
@@ -374179,12 +374275,7 @@ var init_webhooks3 = __esm(() => {
374179
374275
  const sourceName = c.req.param("source");
374180
374276
  const services = c.get("services");
374181
374277
  const rawBody = await c.req.text();
374182
- let payload;
374183
- try {
374184
- payload = rawBody ? JSON.parse(rawBody) : {};
374185
- } catch {
374186
- payload = {};
374187
- }
374278
+ const payload = parseJsonObjectBody(rawBody);
374188
374279
  const headers = {};
374189
374280
  for (const [key, value] of Object.entries(c.req.header())) {
374190
374281
  headers[key.toLowerCase()] = value ?? "";
@@ -375484,18 +375575,14 @@ function createApp(db2, eventBus = null, channelRegistry2 = null) {
375484
375575
  const sourceName = c.req.param("source");
375485
375576
  const services2 = c.get("services");
375486
375577
  const rawBody = await c.req.text();
375487
- let payload = {};
375488
- if (rawBody) {
375489
- let parsed;
375490
- try {
375491
- parsed = JSON.parse(rawBody);
375492
- } catch {
375493
- parsed = undefined;
375494
- }
375495
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
375496
- return c.json({ error: { code: "VALIDATION", message: "Request body must be a JSON object" } }, 400);
375578
+ let payload;
375579
+ try {
375580
+ payload = parseJsonObjectBody(rawBody);
375581
+ } catch (error3) {
375582
+ if (error3 instanceof ValidationError2) {
375583
+ return c.json({ error: { code: "VALIDATION", message: error3.message } }, 400);
375497
375584
  }
375498
- payload = parsed;
375585
+ throw error3;
375499
375586
  }
375500
375587
  const headers = {};
375501
375588
  for (const [key, value] of Object.entries(c.req.header())) {
@@ -375598,6 +375685,7 @@ var init_app = __esm(() => {
375598
375685
  init_v2();
375599
375686
  init_platform_tenants2();
375600
375687
  init_a2a_discovery();
375688
+ init_json_body();
375601
375689
  httpLog = createLogger("http");
375602
375690
  });
375603
375691
 
@@ -377572,7 +377660,7 @@ function buildInstanceConnectOptions(instance4) {
377572
377660
  applyTwilioWhatsAppOptions(options, instance4);
377573
377661
  }
377574
377662
  if (instance4.channel === "whatsapp-business") {
377575
- applyWhatsAppBusinessOptions(options, instance4);
377663
+ applyWhatsAppBusinessConnectionOptions(options, instance4);
377576
377664
  }
377577
377665
  if (instance4.channel === "hermes") {
377578
377666
  applyHermesOptions(options, instance4);
@@ -377591,24 +377679,6 @@ function applyHermesOptions(options, instance4) {
377591
377679
  if (instance4.hermesTemplateNamespace)
377592
377680
  options.hermesTemplateNamespace = instance4.hermesTemplateNamespace;
377593
377681
  }
377594
- function applyWhatsAppBusinessOptions(options, instance4) {
377595
- if (instance4.metaAccessToken)
377596
- options.metaAccessToken = instance4.metaAccessToken;
377597
- if (instance4.metaPhoneNumberId)
377598
- options.metaPhoneNumberId = instance4.metaPhoneNumberId;
377599
- if (instance4.metaWabaId)
377600
- options.metaWabaId = instance4.metaWabaId;
377601
- if (instance4.metaAppId)
377602
- options.metaAppId = instance4.metaAppId;
377603
- if (instance4.metaBusinessId)
377604
- options.metaBusinessId = instance4.metaBusinessId;
377605
- if (instance4.metaApiVersion)
377606
- options.metaApiVersion = instance4.metaApiVersion;
377607
- if (instance4.metaDisplayPhoneNumber)
377608
- options.metaDisplayPhoneNumber = instance4.metaDisplayPhoneNumber;
377609
- if (instance4.metaConnectionMethod)
377610
- options.metaConnectionMethod = instance4.metaConnectionMethod;
377611
- }
377612
377682
  function applyGupshupOptions(options, instance4) {
377613
377683
  if (instance4.gupshupCallbackUrl)
377614
377684
  options.gupshupCallbackUrl = instance4.gupshupCallbackUrl;
@@ -385377,16 +385447,33 @@ async function checkBoltHealth(connection) {
385377
385447
  }
385378
385448
 
385379
385449
  // ../channel-slack/src/handlers/agent-sessions.ts
385450
+ init_zod();
385451
+ var AgentSessionStoppedEventSchema = exports_external.object({
385452
+ channel: exports_external.string().min(1),
385453
+ thread_ts: exports_external.string().optional(),
385454
+ user: exports_external.string().optional(),
385455
+ event_ts: exports_external.string().optional(),
385456
+ streaming_message_ts: exports_external.array(exports_external.string()).optional()
385457
+ }).passthrough();
385380
385458
  function setupAgentSessionHandlers(app, instanceId, callbacks, logger5) {
385381
385459
  app.event("agent_session_stopped", async ({ event }) => {
385382
- const evt = event;
385383
- const channelId = evt.channel;
385384
- if (!channelId)
385460
+ const channelHint = event?.channel;
385461
+ if (typeof channelHint !== "string" || channelHint.length === 0)
385385
385462
  return;
385386
- const threadTs = evt.thread_ts;
385387
- const userId = evt.user;
385388
- const eventTs = evt.event_ts;
385389
- const streamingMessageTs = Array.isArray(evt.streaming_message_ts) ? evt.streaming_message_ts : [];
385463
+ const parsed = AgentSessionStoppedEventSchema.safeParse(event);
385464
+ if (!parsed.success) {
385465
+ logger5.warn("Ignoring malformed agent_session_stopped event", {
385466
+ instanceId,
385467
+ channelId: channelHint,
385468
+ issues: parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
385469
+ });
385470
+ return;
385471
+ }
385472
+ const channelId = parsed.data.channel;
385473
+ const threadTs = parsed.data.thread_ts;
385474
+ const userId = parsed.data.user;
385475
+ const eventTs = parsed.data.event_ts;
385476
+ const streamingMessageTs = parsed.data.streaming_message_ts ?? [];
385390
385477
  logger5.info("Agent session stopped by user", { instanceId, channelId, threadTs, userId, streamingMessageTs });
385391
385478
  await callbacks.onSessionStopped(instanceId, { channelId, threadTs, userId, streamingMessageTs, eventTs });
385392
385479
  });
@@ -385717,6 +385804,7 @@ function buildRawPayload(meta, msg, extra) {
385717
385804
  ts: meta.ts,
385718
385805
  threadTs: meta.threadTs,
385719
385806
  threadId: !meta.isDm && meta.threadTs ? meta.threadTs : undefined,
385807
+ cancelThreadId: meta.threadTs,
385720
385808
  channelType: meta.channelType,
385721
385809
  teamId: meta.teamId,
385722
385810
  isDm: meta.isDm,
@@ -386332,6 +386420,7 @@ function createNativeStreamSender(options) {
386332
386420
  } = options;
386333
386421
  let streamer;
386334
386422
  let lastSentContent = "";
386423
+ let streamTs;
386335
386424
  let stopped = false;
386336
386425
  let fallback;
386337
386426
  function buildFallback() {
@@ -386362,7 +386451,7 @@ function createNativeStreamSender(options) {
386362
386451
  channel: channelId,
386363
386452
  ...threadTs ? { thread_ts: threadTs } : {}
386364
386453
  });
386365
- await streamer.append({ markdown_text: firstChunk });
386454
+ noteStreamTs(await streamer.append({ markdown_text: firstChunk }));
386366
386455
  lastSentContent = firstChunk;
386367
386456
  } catch (err2) {
386368
386457
  logger5.warn("native-stream: chat.startStream failed, falling back to replace mode", {
@@ -386372,12 +386461,27 @@ function createNativeStreamSender(options) {
386372
386461
  fallback = buildFallback();
386373
386462
  }
386374
386463
  }
386464
+ function noteStreamTs(response) {
386465
+ if (streamTs || typeof response !== "object" || response === null)
386466
+ return;
386467
+ const ts = response.ts;
386468
+ if (typeof ts === "string" && ts.length > 0)
386469
+ streamTs = ts;
386470
+ }
386471
+ function resolveStreamTs() {
386472
+ if (streamTs)
386473
+ return streamTs;
386474
+ const fromStreamer = streamer?.streamTs;
386475
+ if (typeof fromStreamer === "string" && fromStreamer.length > 0)
386476
+ streamTs = fromStreamer;
386477
+ return streamTs;
386478
+ }
386375
386479
  async function appendDelta(newContent) {
386376
386480
  const delta = newContent.slice(lastSentContent.length);
386377
386481
  if (!delta)
386378
386482
  return;
386379
386483
  try {
386380
- await streamer?.append({ markdown_text: delta });
386484
+ noteStreamTs(await streamer?.append({ markdown_text: delta }));
386381
386485
  lastSentContent = newContent;
386382
386486
  } catch (err2) {
386383
386487
  logger5.warn("native-stream: append failed", { error: String(err2) });
@@ -386437,6 +386541,16 @@ function createNativeStreamSender(options) {
386437
386541
  if (fallback)
386438
386542
  return fallback.cancel ? fallback.cancel() : fallback.abort();
386439
386543
  await stopStream();
386544
+ },
386545
+ markStoppedByPlatform(stoppedTs) {
386546
+ if (fallback || stopped || !streamer)
386547
+ return false;
386548
+ const ts = resolveStreamTs();
386549
+ if (!ts || !stoppedTs.includes(ts))
386550
+ return false;
386551
+ stopped = true;
386552
+ logger5.debug("native-stream: stream already halted by Slack, skipping chat.stopStream", { channelId, ts });
386553
+ return true;
386440
386554
  }
386441
386555
  };
386442
386556
  }
@@ -386598,6 +386712,7 @@ class SlackPlugin extends BaseChannelPlugin {
386598
386712
  activeThreads = new Map;
386599
386713
  presenceStatusTimers = new Map;
386600
386714
  pendingAckReactions = new Map;
386715
+ activeNativeStreams = new Map;
386601
386716
  async onInitialize(_context) {}
386602
386717
  async onDestroy() {
386603
386718
  for (const [instanceId, connection] of this.connections) {
@@ -386625,6 +386740,7 @@ class SlackPlugin extends BaseChannelPlugin {
386625
386740
  this.presenceStatusTimers.clear();
386626
386741
  this.activeThreads.clear();
386627
386742
  this.pendingAckReactions.clear();
386743
+ this.activeNativeStreams.clear();
386628
386744
  }
386629
386745
  async connect(instanceId, config2) {
386630
386746
  const existing = this.connections.get(instanceId);
@@ -386729,6 +386845,10 @@ class SlackPlugin extends BaseChannelPlugin {
386729
386845
  clearTimeout(timer);
386730
386846
  this.presenceStatusTimers.delete(key);
386731
386847
  }
386848
+ for (const key of this.activeNativeStreams.keys()) {
386849
+ if (key.startsWith(`${instanceId}:`))
386850
+ this.activeNativeStreams.delete(key);
386851
+ }
386732
386852
  for (const key of this.pendingAckReactions.keys()) {
386733
386853
  if (key.startsWith(`${instanceId}:`))
386734
386854
  this.pendingAckReactions.delete(key);
@@ -386790,29 +386910,40 @@ class SlackPlugin extends BaseChannelPlugin {
386790
386910
  const threadTs = this.resolveThreadTs(replyToMode, replyToMessageId, undefined);
386791
386911
  const streamMode = resolveStreamMode(slackConfig.streamMode);
386792
386912
  const throttleMs = resolveStreamThrottle(slackConfig.streamThrottleMs);
386793
- const base2 = streamMode === "native" ? createNativeStreamSender({
386794
- client: connection.actingClient,
386795
- channelId: chatId,
386796
- threadTs,
386797
- throttleMs,
386798
- username: slackConfig.defaultUsername,
386799
- iconUrl: slackConfig.defaultIconUrl,
386800
- iconEmoji: slackConfig.defaultIconEmoji,
386801
- formatMode: options?.formatMode ?? "convert",
386802
- logger: this.logger
386803
- }) : createSlackStreamSender({
386804
- client: connection.actingClient,
386805
- channelId: chatId,
386806
- threadTs,
386807
- streamMode,
386808
- throttleMs,
386809
- username: slackConfig.defaultUsername,
386810
- iconUrl: slackConfig.defaultIconUrl,
386811
- iconEmoji: slackConfig.defaultIconEmoji,
386812
- formatMode: options?.formatMode ?? "convert",
386813
- logger: this.logger
386814
- });
386913
+ const streamKey = `${instanceId}:${chatId}`;
386914
+ let native;
386915
+ let base2;
386916
+ if (streamMode === "native") {
386917
+ native = createNativeStreamSender({
386918
+ client: connection.actingClient,
386919
+ channelId: chatId,
386920
+ threadTs,
386921
+ throttleMs,
386922
+ username: slackConfig.defaultUsername,
386923
+ iconUrl: slackConfig.defaultIconUrl,
386924
+ iconEmoji: slackConfig.defaultIconEmoji,
386925
+ formatMode: options?.formatMode ?? "convert",
386926
+ logger: this.logger
386927
+ });
386928
+ base2 = native;
386929
+ this.trackNativeStream(streamKey, native);
386930
+ } else {
386931
+ base2 = createSlackStreamSender({
386932
+ client: connection.actingClient,
386933
+ channelId: chatId,
386934
+ threadTs,
386935
+ streamMode,
386936
+ throttleMs,
386937
+ username: slackConfig.defaultUsername,
386938
+ iconUrl: slackConfig.defaultIconUrl,
386939
+ iconEmoji: slackConfig.defaultIconEmoji,
386940
+ formatMode: options?.formatMode ?? "convert",
386941
+ logger: this.logger
386942
+ });
386943
+ }
386815
386944
  const cleanup = () => {
386945
+ if (native)
386946
+ this.untrackNativeStream(streamKey, native);
386816
386947
  this.removeAckReaction(instanceId, chatId, connection, replyToMessageId, threadTs);
386817
386948
  this.clearActiveTyping(instanceId, chatId, connection, threadTs).catch(() => {});
386818
386949
  };
@@ -386837,6 +386968,32 @@ class SlackPlugin extends BaseChannelPlugin {
386837
386968
  }
386838
386969
  };
386839
386970
  }
386971
+ trackNativeStream(streamKey, sender) {
386972
+ let set = this.activeNativeStreams.get(streamKey);
386973
+ if (!set) {
386974
+ set = new Set;
386975
+ this.activeNativeStreams.set(streamKey, set);
386976
+ }
386977
+ set.add(sender);
386978
+ }
386979
+ untrackNativeStream(streamKey, sender) {
386980
+ const set = this.activeNativeStreams.get(streamKey);
386981
+ if (!set)
386982
+ return;
386983
+ set.delete(sender);
386984
+ if (set.size === 0)
386985
+ this.activeNativeStreams.delete(streamKey);
386986
+ }
386987
+ markStreamsStoppedByPlatform(instanceId, channelId, stoppedTs) {
386988
+ if (stoppedTs.length === 0)
386989
+ return;
386990
+ const senders2 = this.activeNativeStreams.get(`${instanceId}:${channelId}`);
386991
+ if (!senders2)
386992
+ return;
386993
+ for (const sender of senders2) {
386994
+ sender.markStoppedByPlatform(stoppedTs);
386995
+ }
386996
+ }
386840
386997
  async sendTyping(instanceId, chatId, duration) {
386841
386998
  await this.sendPresenceStatus(instanceId, chatId, duration === 0 ? "paused" : "typing", duration);
386842
386999
  }
@@ -387238,6 +387395,7 @@ class SlackPlugin extends BaseChannelPlugin {
387238
387395
  async handleAgentSessionStopped(instanceId, connection, args) {
387239
387396
  const parsedEventTs = args.eventTs ? Number.parseFloat(args.eventTs) : Number.NaN;
387240
387397
  const requestedAt = Number.isFinite(parsedEventTs) ? Math.round(parsedEventTs * 1000) : Date.now();
387398
+ this.markStreamsStoppedByPlatform(instanceId, args.channelId, args.streamingMessageTs ?? []);
387241
387399
  try {
387242
387400
  await this.eventBus.publish("agent.run.cancel_requested", {
387243
387401
  instanceId,