@automagik/omni 2.260622.1 → 2.260624.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.
@@ -29993,7 +29993,7 @@ class AgnoClient {
29993
29993
  function createAgnoClient(config2) {
29994
29994
  return new AgnoClient(config2);
29995
29995
  }
29996
- var DEFAULT_TIMEOUT_MS = 60000;
29996
+ var DEFAULT_TIMEOUT_MS = 600000;
29997
29997
  var init_agno_client = __esm(() => {
29998
29998
  init_trace_context();
29999
29999
  init_types7();
@@ -30661,6 +30661,25 @@ var init_claude_code_client = __esm(() => {
30661
30661
  THINKING_TAG_RE = /<thinking>[\s\S]*?<\/thinking>\s*/g;
30662
30662
  });
30663
30663
 
30664
+ // ../core/src/providers/customer-safe-errors.ts
30665
+ function isUnsafeCustomerFacingProviderText(text) {
30666
+ return Boolean(text && PROVIDER_ERROR_PATTERN.test(text));
30667
+ }
30668
+ function resolveSafeProviderErrorMessage(env = process.env) {
30669
+ const override = env.OMNI_SAFE_PROVIDER_ERROR_MESSAGE?.trim();
30670
+ return override || SAFE_PROVIDER_ERROR_MESSAGE;
30671
+ }
30672
+ function toSafeCustomerFallback(text, env = process.env) {
30673
+ if (isUnsafeCustomerFacingProviderText(text)) {
30674
+ return resolveSafeProviderErrorMessage(env);
30675
+ }
30676
+ return text ?? "";
30677
+ }
30678
+ var SAFE_PROVIDER_ERROR_MESSAGE = "T\xF4 com um probleminha t\xE9cnico aqui agora. J\xE1 estou avisando o time. Pode tentar de novo em alguns minutos? \uD83D\uDE4F", PROVIDER_ERROR_PATTERN;
30679
+ var init_customer_safe_errors = __esm(() => {
30680
+ PROVIDER_ERROR_PATTERN = /(litellm\.(BadRequestError|AuthenticationError|RateLimitError|APIError)|ModelProviderError|AnthropicException|Authentication Error|Invalid proxy server token|Received API Key|Available Model Group Fallbacks|credit balance is too low|Plans\s*&\s*Billing|\bsk-[A-Za-z0-9_-]{8,}|Bearer\s+[A-Za-z0-9._-]{20,}|api[_ -]?key\s*[=:])/i;
30681
+ });
30682
+
30664
30683
  // ../core/src/providers/agno-provider.ts
30665
30684
  class AgnoAgentProvider {
30666
30685
  id;
@@ -30700,9 +30719,17 @@ class AgnoAgentProvider {
30700
30719
  traceId: context2.traceId
30701
30720
  });
30702
30721
  const response = await this.client.run(request);
30703
- const parts = this.config.enableAutoSplit !== false ? response.content.split(`
30722
+ const customerContent = toSafeCustomerFallback(response.content);
30723
+ if (customerContent !== response.content) {
30724
+ log8.error("Blocked provider error from customer-facing Agno response", {
30725
+ agentId: this.config.agentId,
30726
+ runId: response.runId,
30727
+ traceId: context2.traceId
30728
+ });
30729
+ }
30730
+ const parts = this.config.enableAutoSplit !== false ? customerContent.split(`
30704
30731
 
30705
- `).map((p2) => p2.trim()).filter(Boolean) : [response.content.trim()].filter(Boolean);
30732
+ `).map((p2) => p2.trim()).filter(Boolean) : [customerContent.trim()].filter(Boolean);
30706
30733
  const durationMs = Date.now() - startTime;
30707
30734
  log8.info("Agno agent responded", {
30708
30735
  agentId: this.config.agentId,
@@ -30740,10 +30767,20 @@ class AgnoAgentProvider {
30740
30767
  try {
30741
30768
  for await (const chunk of this.client.stream(request)) {
30742
30769
  if (chunk.content) {
30743
- yield { phase: "content", content: chunk.content };
30770
+ const safeContent = toSafeCustomerFallback(chunk.content);
30771
+ if (safeContent !== chunk.content) {
30772
+ log8.error("Blocked provider error from customer-facing Agno stream chunk", {
30773
+ agentId: this.config.agentId,
30774
+ traceId: context2.traceId
30775
+ });
30776
+ yield { phase: "content", content: safeContent };
30777
+ continue;
30778
+ }
30779
+ yield { phase: "content", content: safeContent };
30744
30780
  }
30745
30781
  if (chunk.isComplete) {
30746
- yield { phase: "final", content: chunk.fullContent ?? chunk.content ?? "" };
30782
+ const finalContent = chunk.fullContent ?? chunk.content ?? "";
30783
+ yield { phase: "final", content: toSafeCustomerFallback(finalContent) };
30747
30784
  }
30748
30785
  }
30749
30786
  } catch (error) {
@@ -30753,7 +30790,7 @@ class AgnoAgentProvider {
30753
30790
  traceId: context2.traceId,
30754
30791
  error: message3
30755
30792
  });
30756
- yield { phase: "error", error: message3 };
30793
+ yield { phase: "error", error: resolveSafeProviderErrorMessage() };
30757
30794
  }
30758
30795
  }
30759
30796
  async checkHealth() {
@@ -30796,6 +30833,7 @@ class AgnoAgentProvider {
30796
30833
  var log8;
30797
30834
  var init_agno_provider = __esm(() => {
30798
30835
  init_logger();
30836
+ init_customer_safe_errors();
30799
30837
  init_trace_context();
30800
30838
  log8 = createLogger("provider:agno");
30801
30839
  });
@@ -148890,6 +148928,9 @@ import { spawn } from "child_process";
148890
148928
  import { promises as fs } from "fs";
148891
148929
  import { tmpdir as tmpdir2 } from "os";
148892
148930
  import { join as join5 } from "path";
148931
+ function spawnWithEvents(command, args) {
148932
+ return spawn(command, args);
148933
+ }
148893
148934
  async function writeToTempFile(data) {
148894
148935
  const path = join5(tmpdir2(), `audio-decode-${Date.now()}-${Math.random().toString(36).slice(2)}.tmp`);
148895
148936
  await fs.writeFile(path, data);
@@ -148948,7 +148989,7 @@ async function decode2(input) {
148948
148989
  function ffmpegDecode(inputPath, sampleRate) {
148949
148990
  return new Promise((resolve, reject) => {
148950
148991
  const chunks = [];
148951
- const ffmpeg = spawn("ffmpeg", [
148992
+ const ffmpeg = spawnWithEvents("ffmpeg", [
148952
148993
  "-i",
148953
148994
  inputPath,
148954
148995
  "-f",
@@ -155249,9 +155290,12 @@ import { tmpdir as tmpdir5 } from "os";
155249
155290
  import { join as join10 } from "path";
155250
155291
  import { Readable as Readable2 } from "stream";
155251
155292
  import { pipeline as pipeline3 } from "stream/promises";
155293
+ function spawnWithEvents2(command, args) {
155294
+ return spawn2(command, args);
155295
+ }
155252
155296
  async function isFFmpegAvailable() {
155253
155297
  return new Promise((resolve) => {
155254
- const proc = spawn2("ffmpeg", ["-version"]);
155298
+ const proc = spawnWithEvents2("ffmpeg", ["-version"]);
155255
155299
  proc.on("error", () => resolve(false));
155256
155300
  proc.on("close", (code) => resolve(code === 0));
155257
155301
  });
@@ -155306,7 +155350,7 @@ async function downloadToTemp(url) {
155306
155350
  async function convertToOggOpus(inputPath) {
155307
155351
  const outputPath = join10(tmpdir5(), `omni-audio-${Date.now()}-output.ogg`);
155308
155352
  return new Promise((resolve, reject) => {
155309
- const ffmpeg = spawn2("ffmpeg", [
155353
+ const ffmpeg = spawnWithEvents2("ffmpeg", [
155310
155354
  "-i",
155311
155355
  inputPath,
155312
155356
  "-c:a",
@@ -225245,7 +225289,7 @@ var init_sentry_scrub = __esm(() => {
225245
225289
  var require_package7 = __commonJS((exports, module) => {
225246
225290
  module.exports = {
225247
225291
  name: "@omni/api",
225248
- version: "2.260622.1",
225292
+ version: "2.260624.2",
225249
225293
  type: "module",
225250
225294
  exports: {
225251
225295
  ".": {
@@ -311084,6 +311128,9 @@ import { spawn as spawn3 } from "child_process";
311084
311128
  import { promises as fs10 } from "fs";
311085
311129
  import { tmpdir as tmpdir6 } from "os";
311086
311130
  import { join as join16 } from "path";
311131
+ function spawnWithEvents3(command, args) {
311132
+ return spawn3(command, args);
311133
+ }
311087
311134
 
311088
311135
  class GeminiTtsProvider {
311089
311136
  settings;
@@ -311254,7 +311301,7 @@ async function convertWavToOggOpus(wavBuffer) {
311254
311301
  try {
311255
311302
  await fs10.writeFile(inputPath, wavBuffer);
311256
311303
  await new Promise((resolve2, reject) => {
311257
- const ffmpeg = spawn3("ffmpeg", [
311304
+ const ffmpeg = spawnWithEvents3("ffmpeg", [
311258
311305
  "-i",
311259
311306
  inputPath,
311260
311307
  "-c:a",
@@ -311300,7 +311347,7 @@ async function getAudioDurationMs(audioBuffer) {
311300
311347
  try {
311301
311348
  await fs10.writeFile(tempPath, audioBuffer);
311302
311349
  return await new Promise((resolve2) => {
311303
- const ffprobe = spawn3("ffprobe", [
311350
+ const ffprobe = spawnWithEvents3("ffprobe", [
311304
311351
  "-i",
311305
311352
  tempPath,
311306
311353
  "-show_entries",
@@ -312008,6 +312055,9 @@ import { spawn as spawn4 } from "child_process";
312008
312055
  import { promises as fs12 } from "fs";
312009
312056
  import { tmpdir as tmpdir8 } from "os";
312010
312057
  import { join as join18 } from "path";
312058
+ function spawnWithEvents4(command, args) {
312059
+ return spawn4(command, args);
312060
+ }
312011
312061
 
312012
312062
  class OpenAiTtsProvider {
312013
312063
  settings;
@@ -312126,7 +312176,7 @@ async function convertAudio(input, from, to) {
312126
312176
  try {
312127
312177
  await fs12.writeFile(inputPath, input);
312128
312178
  await new Promise((resolve2, reject) => {
312129
- const ffmpeg = spawn4("ffmpeg", [
312179
+ const ffmpeg = spawnWithEvents4("ffmpeg", [
312130
312180
  "-hide_banner",
312131
312181
  "-loglevel",
312132
312182
  "error",
@@ -337005,7 +337055,7 @@ class AgentDispatchLimiter {
337005
337055
  return `${context20.instanceId}:${context20.chatId}`;
337006
337056
  }
337007
337057
  }
337008
- var DEFAULT_AGENT_DISPATCH_CONCURRENCY = 8, DEFAULT_AGENT_DISPATCH_QUEUE_MAX_DEPTH = 100, DEFAULT_AGENT_DISPATCH_QUEUE_MAX_WAIT_MS = 60000, DEFAULT_AGENT_DISPATCH_PER_CHAT_CONCURRENCY = 1, AgentDispatchQueueFullError, AgentDispatchQueueTimeoutError;
337058
+ var DEFAULT_AGENT_DISPATCH_CONCURRENCY = 8, DEFAULT_AGENT_DISPATCH_QUEUE_MAX_DEPTH = 100, DEFAULT_AGENT_DISPATCH_QUEUE_MAX_WAIT_MS = 600000, DEFAULT_AGENT_DISPATCH_PER_CHAT_CONCURRENCY = 1, AgentDispatchQueueFullError, AgentDispatchQueueTimeoutError;
337009
337059
  var init_agent_dispatch_limiter = __esm(() => {
337010
337060
  AgentDispatchQueueFullError = class AgentDispatchQueueFullError extends Error {
337011
337061
  snapshot;
@@ -337482,9 +337532,9 @@ function findLongStrings(obj, prefix = "", minLength = 200) {
337482
337532
  }
337483
337533
  return result;
337484
337534
  }
337485
- function extractPlatformTimestamp(rawPayload, fallback) {
337535
+ function extractPlatformTimestamp(rawPayload, _fallback) {
337486
337536
  if (!rawPayload?.messageTimestamp)
337487
- return new Date(fallback);
337537
+ return null;
337488
337538
  const ts = rawPayload.messageTimestamp;
337489
337539
  let tsNum = null;
337490
337540
  if (typeof ts === "number") {
@@ -337492,10 +337542,12 @@ function extractPlatformTimestamp(rawPayload, fallback) {
337492
337542
  } else if (typeof ts === "string") {
337493
337543
  tsNum = Number(ts);
337494
337544
  } else if (typeof ts === "object" && ts !== null && "low" in ts) {
337495
- tsNum = ts.low;
337545
+ const lo = ts.low >>> 0;
337546
+ const hi = (ts.high ?? 0) >>> 0;
337547
+ tsNum = hi * 4294967296 + lo;
337496
337548
  }
337497
337549
  if (!tsNum || Number.isNaN(tsNum))
337498
- return new Date(fallback);
337550
+ return null;
337499
337551
  return new Date(tsNum < 1000000000000 ? tsNum * 1000 : tsNum);
337500
337552
  }
337501
337553
  function buildChatPreview(payload, rawPayload) {
@@ -337728,6 +337780,7 @@ async function resolveOrCreateChat(services, instanceId, chatExternalId, chatTyp
337728
337780
  }
337729
337781
  async function handleMessageReceived(services, payload, metadata, eventTimestamp) {
337730
337782
  const channel5 = metadata.channelType ?? "whatsapp";
337783
+ const isHistorySync = metadata.ingestMode === "history-sync";
337731
337784
  const chatExternalId = truncate3(payload.chatId, 255) ?? payload.chatId;
337732
337785
  const messageExternalId = truncate3(payload.externalId, 255) ?? payload.externalId;
337733
337786
  const rawPayload = payload.rawPayload ? deepSanitize(payload.rawPayload) : undefined;
@@ -337744,11 +337797,18 @@ async function handleMessageReceived(services, payload, metadata, eventTimestamp
337744
337797
  const senderDisplayName = resolveSenderDisplayName(payload.senderName, rawPayload, participantResult);
337745
337798
  const quotedMessage = rawPayload?.quotedMessage;
337746
337799
  const platformTimestamp = extractPlatformTimestamp(rawPayload, eventTimestamp);
337800
+ if (isHistorySync && platformTimestamp === null) {
337801
+ log98.debug("Skipping history-sync message without messageTimestamp", {
337802
+ externalId: payload.externalId,
337803
+ chatId: payload.chatId
337804
+ });
337805
+ return;
337806
+ }
337747
337807
  const { message: message2, created } = await services.messages.findOrCreate(chat2.id, messageExternalId, {
337748
- source: "realtime",
337808
+ source: isHistorySync ? "sync" : "realtime",
337749
337809
  messageType: mapContentType(payload.content.type),
337750
337810
  textContent: sanitizeText(payload.content.text),
337751
- platformTimestamp,
337811
+ platformTimestamp: platformTimestamp ?? new Date(eventTimestamp),
337752
337812
  senderPlatformUserId: truncate3(payload.from, 255),
337753
337813
  senderDisplayName,
337754
337814
  senderPersonId: personId,
@@ -337764,12 +337824,12 @@ async function handleMessageReceived(services, payload, metadata, eventTimestamp
337764
337824
  isForwarded: !!(rawPayload?.isForwarded || rawPayload?.forwardingScore),
337765
337825
  rawPayload
337766
337826
  });
337767
- await maybeRecordMessageEdit(services, created, rawPayload, message2.id, sanitizeText(payload.content.text) ?? undefined, platformTimestamp, payload.from);
337827
+ await maybeRecordMessageEdit(services, created, rawPayload, message2.id, sanitizeText(payload.content.text) ?? undefined, platformTimestamp ?? new Date(eventTimestamp), payload.from);
337768
337828
  if (created) {
337769
337829
  log98.debug("Created message", { externalId: payload.externalId, chatId: chat2.id });
337770
337830
  }
337771
337831
  await maybeRecordParticipantActivity(services, chat2.id, payload.from);
337772
- maybeUpdateRecency(services, metadata.instanceId, chat2.id, rawPayload, payload, platformTimestamp);
337832
+ maybeUpdateRecency(services, metadata.instanceId, chat2.id, rawPayload, payload, platformTimestamp ?? new Date(eventTimestamp));
337773
337833
  }
337774
337834
  function logMessageReceivedError(payload, error3) {
337775
337835
  const rawPayload = payload.rawPayload;
@@ -338392,11 +338452,19 @@ async function sendTextMessage5(channel5, instanceId, chatId, text3, messageForm
338392
338452
  metadata: { messageFormatMode, correlationId, senderAgentId }
338393
338453
  });
338394
338454
  }
338395
- async function sendErrorFeedback(channel5, instanceId, chatId, error3) {
338455
+ function resolveDispatchErrorMessage(instanceErrorMessage, env2 = process.env) {
338456
+ const instanceMsg = instanceErrorMessage?.trim();
338457
+ if (instanceMsg)
338458
+ return instanceMsg;
338459
+ const envMsg = env2.OMNI_AGENT_DISPATCH_ERROR_MESSAGE?.trim();
338460
+ if (envMsg)
338461
+ return envMsg;
338462
+ return DEFAULT_DISPATCH_ERROR_MESSAGE;
338463
+ }
338464
+ async function sendErrorFeedback(channel5, instanceId, chatId, error3, message2) {
338396
338465
  const errorMsg = error3 instanceof Error ? error3.message : String(error3);
338397
338466
  log99.error("agent_dispatch_error", { channel: channel5, instanceId, chatId, error: errorMsg });
338398
- const text3 = "\u26A0\uFE0F Sorry, I ran into an issue processing your message. Please try again.";
338399
- await sendTextMessage5(channel5, instanceId, chatId, text3);
338467
+ await sendTextMessage5(channel5, instanceId, chatId, message2);
338400
338468
  }
338401
338469
  function sleep5(ms) {
338402
338470
  return new Promise((resolve3) => setTimeout(resolve3, ms));
@@ -339953,7 +340021,7 @@ async function processAgentResponse(services, instance4, messages4, triggerType,
339953
340021
  }
339954
340022
  if (chatSettings?.agentResumedAt) {
339955
340023
  const resumedAt = new Date(chatSettings.agentResumedAt).getTime();
339956
- const msgTimestamp = extractPlatformTimestamp(firstMessage.payload.rawPayload, Date.now()).getTime();
340024
+ const msgTimestamp = (extractPlatformTimestamp(firstMessage.payload.rawPayload, Date.now()) ?? new Date).getTime();
339957
340025
  if (msgTimestamp < resumedAt) {
339958
340026
  log99.debug("Dropping pre-resume message (arrived during handoff window)", {
339959
340027
  instanceId: instance4.id,
@@ -340003,7 +340071,7 @@ async function processAgentResponse(services, instance4, messages4, triggerType,
340003
340071
  error: String(error3),
340004
340072
  traceId
340005
340073
  });
340006
- sendErrorFeedback(channel5, instance4.id, chatId, error3).catch(() => {});
340074
+ sendErrorFeedback(channel5, instance4.id, chatId, error3, resolveDispatchErrorMessage(null)).catch(() => {});
340007
340075
  } finally {
340008
340076
  ackHandle.remove();
340009
340077
  }
@@ -340041,7 +340109,7 @@ function createOpenClawProviderInstance(provider, instance4) {
340041
340109
  const schemaConfig = provider.schemaConfig ?? {};
340042
340110
  const providerConfig = {
340043
340111
  defaultAgentId: resolveRequiredAgentId(instance4, schemaConfig, provider.id, "defaultAgentId"),
340044
- agentTimeoutMs: (instance4.agentTimeout ?? provider.defaultTimeout ?? 120) * 1000,
340112
+ agentTimeoutMs: (instance4.agentTimeout ?? provider.defaultTimeout ?? 600) * 1000,
340045
340113
  sendAckTimeoutMs: 1e4,
340046
340114
  prefixSenderName: instance4.agentPrefixSenderName ?? true
340047
340115
  };
@@ -340085,7 +340153,7 @@ function createClaudeCodeProviderInstance(provider, instance4, db2) {
340085
340153
  maxTurns: schemaConfig.maxTurns,
340086
340154
  pathToClaudeCodeExecutable: schemaConfig.pathToClaudeCodeExecutable
340087
340155
  }, createSessionStorage(db2, provider.id), {
340088
- timeoutMs: (instance4.agentTimeout ?? provider.defaultTimeout ?? 120) * 1000,
340156
+ timeoutMs: (instance4.agentTimeout ?? provider.defaultTimeout ?? 600) * 1000,
340089
340157
  enableAutoSplit: instance4.enableAutoSplit ?? true,
340090
340158
  prefixSenderName: instance4.agentPrefixSenderName ?? true,
340091
340159
  streamConfig: schemaConfig.streamConfig
@@ -340097,7 +340165,7 @@ function createWebhookProvider(provider) {
340097
340165
  webhookUrl: provider.baseUrl,
340098
340166
  apiKey: provider.apiKey ?? undefined,
340099
340167
  mode: schemaConfig.mode ?? "round-trip",
340100
- timeoutMs: (provider.defaultTimeout ?? 30) * 1000,
340168
+ timeoutMs: (provider.defaultTimeout ?? 600) * 1000,
340101
340169
  retries: schemaConfig.retries ?? 1
340102
340170
  });
340103
340171
  }
@@ -340570,7 +340638,7 @@ function isInboundTooStale(rawPayload, maxAgeMinutes, now = Date.now()) {
340570
340638
  if (maxAgeMinutes <= 0) {
340571
340639
  return { stale: false, ageMs: 0, maxAgeMs };
340572
340640
  }
340573
- const platformTs = extractPlatformTimestamp(rawPayload, now);
340641
+ const platformTs = extractPlatformTimestamp(rawPayload, now) ?? new Date(now);
340574
340642
  const ageMs = now - platformTs.getTime();
340575
340643
  return { stale: ageMs > maxAgeMs, ageMs, maxAgeMs };
340576
340644
  }
@@ -341130,7 +341198,7 @@ async function setupAgentDispatcher(eventBus, services, db2) {
341130
341198
  log99.info("Agent dispatcher shutdown complete");
341131
341199
  };
341132
341200
  }
341133
- var import_api32, log99, agentDispatchLimiter, _natsGenieProviderCtor, QUOTED_MESSAGE_MAX_CHARS = 4000, DM_HISTORY_LIMIT = 20, LIFECYCLE_PREVIEW_MAX_CHARS = 160, LIFECYCLE_SENSITIVE_KEY_PARTS, 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, sessionActivityStore, PROC_REACT_START, PROC_REACT_DONE = "\u2705", providerCache, openclawClientPool, nullFilterWarnedInstances, ACTIVE_OWNER_IDENTIFIER_CACHE_TTL_MS = 1e4, cachedActiveOwnerIdentifiers = null, cachedActiveOwnerIdentifiersAt = 0, DEFAULT_GATE_MODEL = "gemini-3-flash-preview", GATE_TIMEOUT_MS = 3000, setupAgentResponder;
341201
+ var import_api32, log99, agentDispatchLimiter, _natsGenieProviderCtor, QUOTED_MESSAGE_MAX_CHARS = 4000, DM_HISTORY_LIMIT = 20, LIFECYCLE_PREVIEW_MAX_CHARS = 160, LIFECYCLE_SENSITIVE_KEY_PARTS, DEFAULT_DISPATCH_ERROR_MESSAGE = "\u26A0\uFE0F Sorry, I ran into an issue processing your message. Please try again.", 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, sessionActivityStore, PROC_REACT_START, PROC_REACT_DONE = "\u2705", providerCache, openclawClientPool, nullFilterWarnedInstances, ACTIVE_OWNER_IDENTIFIER_CACHE_TTL_MS = 1e4, cachedActiveOwnerIdentifiers = null, cachedActiveOwnerIdentifiersAt = 0, DEFAULT_GATE_MODEL = "gemini-3-flash-preview", GATE_TIMEOUT_MS = 3000, setupAgentResponder;
341134
341202
  var init_agent_dispatcher = __esm(() => {
341135
341203
  init_src2();
341136
341204
  init_src();
@@ -342156,6 +342224,9 @@ import { spawn as spawn5 } from "child_process";
342156
342224
  import { promises as fs15 } from "fs";
342157
342225
  import { tmpdir as tmpdir12 } from "os";
342158
342226
  import { join as join25 } from "path";
342227
+ function spawnWithEvents5(command, args) {
342228
+ return spawn5(command, args);
342229
+ }
342159
342230
  var ELEVENLABS_API_URL = "https://api.elevenlabs.io/v1/text-to-speech", ELEVENLABS_VOICES_URL = "https://api.elevenlabs.io/v1/voices", TTSService;
342160
342231
  var init_tts3 = __esm(() => {
342161
342232
  init_src();
@@ -342265,7 +342336,7 @@ var init_tts3 = __esm(() => {
342265
342336
  try {
342266
342337
  await fs15.writeFile(inputPath, mp3Buffer);
342267
342338
  await new Promise((resolve3, reject) => {
342268
- const ffmpeg = spawn5("ffmpeg", [
342339
+ const ffmpeg = spawnWithEvents5("ffmpeg", [
342269
342340
  "-i",
342270
342341
  inputPath,
342271
342342
  "-c:a",
@@ -342311,7 +342382,7 @@ var init_tts3 = __esm(() => {
342311
342382
  try {
342312
342383
  await fs15.writeFile(tempPath, audioBuffer);
342313
342384
  return await new Promise((resolve3) => {
342314
- const ffprobe = spawn5("ffprobe", [
342385
+ const ffprobe = spawnWithEvents5("ffprobe", [
342315
342386
  "-i",
342316
342387
  tempPath,
342317
342388
  "-show_entries",
@@ -347458,7 +347529,7 @@ function registerMessageSchemas(registry4) {
347458
347529
  operationId: "sendPresence",
347459
347530
  tags: ["Messages", "Presence"],
347460
347531
  summary: "Send presence indicator",
347461
- description: "Send typing/recording indicator in a chat. Auto-pauses after duration. WhatsApp supports typing, recording, paused. Discord only supports typing.",
347532
+ description: "Send typing/recording indicator in a chat. WhatsApp supports typing, recording, paused. Discord supports typing. Slack supports AI Assistant thread status via threadId or the active thread cache; omit duration to keep Slack status until reply cleanup or Slack timeout.",
347462
347533
  request: { body: { content: { "application/json": { schema: SendPresenceSchema } } } },
347463
347534
  responses: {
347464
347535
  200: {
@@ -347644,13 +347715,22 @@ var init_messages4 = __esm(() => {
347644
347715
  instanceId: exports_external.string().uuid().openapi({ description: "Instance ID to send from" }),
347645
347716
  to: exports_external.string().min(1).openapi({ description: "Chat ID to show presence in" }),
347646
347717
  type: exports_external.enum(["typing", "recording", "paused"]).openapi({ description: "Presence type" }),
347647
- duration: exports_external.number().int().min(0).max(30000).optional().default(5000).openapi({ description: "Duration in ms before auto-pause (default 5000, 0 = until paused)" })
347718
+ threadId: exports_external.string().optional().openapi({ description: "Thread timestamp/id for thread-scoped presence surfaces such as Slack AI Assistant" }),
347719
+ status: exports_external.string().min(1).max(100).optional().openapi({ description: "Custom status text for channels that support text status, such as Slack AI Assistant" }),
347720
+ loadingMessages: exports_external.array(exports_external.string().min(1).max(100)).max(10).optional().openapi({ description: "Rotating loading messages for channels that support them, such as Slack AI Assistant" }),
347721
+ duration: exports_external.number().int().min(0).max(30000).optional().openapi({ description: "Duration in ms before auto-pause; omit for channel default, 0 = until paused" })
347648
347722
  });
347649
347723
  PresenceResponseSchema = exports_external.object({
347650
347724
  instanceId: exports_external.string().uuid().openapi({ description: "Instance ID" }),
347651
347725
  chatId: exports_external.string().openapi({ description: "Chat ID where presence was sent" }),
347652
347726
  type: exports_external.string().openapi({ description: "Presence type sent" }),
347653
- duration: exports_external.number().openapi({ description: "Duration in ms" })
347727
+ duration: exports_external.number().openapi({ description: "Duration in ms" }),
347728
+ threadId: exports_external.string().optional().openapi({ description: "Thread timestamp/id used by the presence renderer" }),
347729
+ delivered: exports_external.boolean().optional().openapi({ description: "Whether the channel reported the status as delivered" }),
347730
+ method: exports_external.string().optional().openapi({ description: "Channel-specific presence method used" }),
347731
+ reason: exports_external.string().optional().openapi({ description: "Reason when the channel no-opped or failed best-effort" }),
347732
+ status: exports_external.string().optional().openapi({ description: "Channel-specific status text sent" }),
347733
+ loadingMessages: exports_external.array(exports_external.string()).optional().openapi({ description: "Channel-specific rotating loading messages sent" })
347654
347734
  });
347655
347735
  MarkMessageReadSchema = exports_external.object({
347656
347736
  instanceId: exports_external.string().uuid().openapi({ description: "Instance ID" })
@@ -356675,6 +356755,9 @@ async function computeCloseContactTerminalState(db2, chatUuid, outcome, auditRow
356675
356755
  const closeUntil = cfg.cooldownMs !== null ? new Date(Date.now() + cfg.cooldownMs) : null;
356676
356756
  return { terminal: false, escalated: false, closeUntil };
356677
356757
  }
356758
+ function hasPresenceStatusSender(plugin7) {
356759
+ return typeof plugin7 === "object" && plugin7 !== null && "sendPresenceStatus" in plugin7 && typeof plugin7.sendPresenceStatus === "function";
356760
+ }
356678
356761
  async function verifyMessageInstanceOwnership(services, message2, instanceId) {
356679
356762
  if (!message2.chatId)
356680
356763
  return;
@@ -357881,31 +357964,57 @@ var init_messages5 = __esm(() => {
357881
357964
  instanceId: exports_external.string().uuid().describe("Instance ID to send from"),
357882
357965
  to: exports_external.string().min(1).describe("Chat ID to show presence in"),
357883
357966
  type: exports_external.enum(["typing", "recording", "paused"]).describe("Presence type"),
357884
- duration: exports_external.number().int().min(0).max(30000).optional().default(5000).describe("Duration in ms before auto-pause (default 5000, 0 = until paused)")
357967
+ threadId: exports_external.string().min(1).optional().describe("Thread timestamp/id for thread-scoped presence surfaces"),
357968
+ status: exports_external.string().min(1).max(100).optional().describe("Custom status text for channels that support it"),
357969
+ loadingMessages: exports_external.array(exports_external.string().min(1).max(100)).max(10).optional().describe("Rotating loading messages for channels that support them"),
357970
+ duration: exports_external.number().int().min(0).max(30000).optional().describe("Duration in ms before auto-pause; omit for channel default, 0 = until paused")
357885
357971
  });
357886
357972
  messagesRoutes.post("/send/presence", zValidator("json", sendPresenceSchema), async (c) => {
357887
- const { instanceId, to, type, duration } = c.req.valid("json");
357973
+ const { instanceId, to, type, duration, threadId: threadId2, status, loadingMessages } = c.req.valid("json");
357888
357974
  const services = c.get("services");
357889
357975
  checkInstanceAccess2(c.get("apiKey"), instanceId);
357890
- const { instance: instance4, plugin: plugin7 } = await getPluginForInstance2(services, c.get("channelRegistry"), instanceId, "canSendTyping");
357976
+ const { instance: instance4, plugin: plugin7 } = await getPluginForInstance2(services, c.get("channelRegistry"), instanceId);
357891
357977
  const resolvedTo = await resolveRecipient(to, instance4.channel, services);
357892
- if (!("sendTyping" in plugin7) || typeof plugin7.sendTyping !== "function") {
357978
+ const canSendNativeTyping = plugin7.capabilities.canSendTyping === true;
357979
+ const canSendThreadStatus = hasPresenceStatusSender(plugin7);
357980
+ if (!canSendNativeTyping && !canSendThreadStatus) {
357893
357981
  throw new OmniError({
357894
357982
  code: ERROR_CODES.CAPABILITY_NOT_SUPPORTED,
357895
- message: `Channel ${instance4.channel} plugin does not implement sendTyping`,
357983
+ message: `Channel ${instance4.channel} does not support typing indicators or thread status`,
357896
357984
  context: { channelType: instance4.channel },
357897
357985
  recoverable: false
357898
357986
  });
357899
357987
  }
357900
- const effectiveDuration = type === "paused" ? 0 : duration;
357901
- await plugin7.sendTyping(instanceId, resolvedTo, effectiveDuration);
357988
+ const effectiveDuration = type === "paused" ? 0 : duration ?? (canSendThreadStatus ? 0 : 5000);
357989
+ const presenceResult = canSendThreadStatus ? await plugin7.sendPresenceStatus(instanceId, resolvedTo, type, effectiveDuration, {
357990
+ threadId: threadId2,
357991
+ status,
357992
+ loadingMessages
357993
+ }) : await (async () => {
357994
+ if (!("sendTyping" in plugin7) || typeof plugin7.sendTyping !== "function") {
357995
+ throw new OmniError({
357996
+ code: ERROR_CODES.CAPABILITY_NOT_SUPPORTED,
357997
+ message: `Channel ${instance4.channel} plugin does not implement sendTyping`,
357998
+ context: { channelType: instance4.channel },
357999
+ recoverable: false
358000
+ });
358001
+ }
358002
+ await plugin7.sendTyping(instanceId, resolvedTo, effectiveDuration);
358003
+ return { delivered: true, method: "typing_indicator" };
358004
+ })();
357902
358005
  return c.json({
357903
358006
  success: true,
357904
358007
  data: {
357905
358008
  instanceId,
357906
358009
  chatId: resolvedTo,
357907
358010
  type,
357908
- duration: effectiveDuration
358011
+ duration: effectiveDuration,
358012
+ threadId: presenceResult?.threadId ?? threadId2,
358013
+ delivered: presenceResult?.delivered ?? true,
358014
+ method: presenceResult?.method ?? "typing_indicator",
358015
+ reason: presenceResult?.reason,
358016
+ status: presenceResult?.status,
358017
+ loadingMessages: presenceResult?.loadingMessages
357909
358018
  }
357910
358019
  });
357911
358020
  });
@@ -368022,38 +368131,39 @@ function setupReactionHandlers2(app, instanceId, botUserId, callbacks, logger5)
368022
368131
  }
368023
368132
 
368024
368133
  // ../channel-slack/src/handlers/typing.ts
368025
- var TYPING_STATUS = "is typing...";
368026
368134
  var CLEAR_STATUS = "";
368027
368135
  async function setSlackThreadStatus(params) {
368028
- const { client, channelId, threadTs, status, logger: logger5, instanceId } = params;
368136
+ const { client, channelId, threadTs, status, loadingMessages, logger: logger5, instanceId } = params;
368029
368137
  if (!threadTs)
368030
- return;
368138
+ return false;
368031
368139
  const payload = {
368032
368140
  channel_id: channelId,
368033
368141
  thread_ts: threadTs,
368034
- status
368142
+ status,
368143
+ ...loadingMessages?.length ? { loading_messages: loadingMessages } : {}
368035
368144
  };
368036
368145
  try {
368037
368146
  const clientAny = client;
368038
368147
  if (typeof clientAny.assistant?.threads?.setStatus === "function") {
368039
368148
  await clientAny.assistant.threads.setStatus(payload);
368040
- return;
368149
+ return true;
368041
368150
  }
368042
368151
  if (typeof clientAny.apiCall === "function") {
368043
368152
  await clientAny.apiCall("assistant.threads.setStatus", payload);
368153
+ return true;
368044
368154
  }
368045
368155
  } catch (err) {
368046
368156
  logger5.warn("setSlackThreadStatus: failed", {
368047
368157
  instanceId,
368048
368158
  channelId,
368049
368159
  threadTs,
368050
- status,
368160
+ clearing: status.length === 0,
368161
+ statusLength: status.length,
368162
+ loadingMessageCount: loadingMessages?.length ?? 0,
368051
368163
  error: String(err)
368052
368164
  });
368053
368165
  }
368054
- }
368055
- async function setTypingStatus(params) {
368056
- return setSlackThreadStatus({ ...params, status: TYPING_STATUS });
368166
+ return false;
368057
368167
  }
368058
368168
  async function clearTypingStatus(params) {
368059
368169
  return setSlackThreadStatus({ ...params, status: CLEAR_STATUS });
@@ -368553,6 +368663,7 @@ class SlackPlugin extends BaseChannelPlugin {
368553
368663
  debouncers = new Map;
368554
368664
  threadCaches = new Map;
368555
368665
  activeThreads = new Map;
368666
+ presenceStatusTimers = new Map;
368556
368667
  pendingAckReactions = new Map;
368557
368668
  async onInitialize(_context) {}
368558
368669
  async onDestroy() {
@@ -368575,6 +368686,10 @@ class SlackPlugin extends BaseChannelPlugin {
368575
368686
  cache2.dispose();
368576
368687
  }
368577
368688
  this.threadCaches.clear();
368689
+ for (const timer of this.presenceStatusTimers.values()) {
368690
+ clearTimeout(timer);
368691
+ }
368692
+ this.presenceStatusTimers.clear();
368578
368693
  this.activeThreads.clear();
368579
368694
  this.pendingAckReactions.clear();
368580
368695
  }
@@ -368673,6 +368788,12 @@ class SlackPlugin extends BaseChannelPlugin {
368673
368788
  if (key.startsWith(`${instanceId}:`))
368674
368789
  this.activeThreads.delete(key);
368675
368790
  }
368791
+ for (const [key, timer] of this.presenceStatusTimers) {
368792
+ if (!key.startsWith(`${instanceId}:`))
368793
+ continue;
368794
+ clearTimeout(timer);
368795
+ this.presenceStatusTimers.delete(key);
368796
+ }
368676
368797
  for (const key of this.pendingAckReactions.keys()) {
368677
368798
  if (key.startsWith(`${instanceId}:`))
368678
368799
  this.pendingAckReactions.delete(key);
@@ -368777,27 +368898,75 @@ class SlackPlugin extends BaseChannelPlugin {
368777
368898
  };
368778
368899
  }
368779
368900
  async sendTyping(instanceId, chatId, duration) {
368901
+ await this.sendPresenceStatus(instanceId, chatId, duration === 0 ? "paused" : "typing", duration);
368902
+ }
368903
+ async sendPresenceStatus(instanceId, chatId, type, duration, options) {
368904
+ const method = "assistant.threads.setStatus";
368780
368905
  const connection = this.connections.get(instanceId);
368781
368906
  if (!connection)
368782
- return;
368783
- const threadTs = this.activeThreads.get(`${instanceId}:${chatId}`);
368907
+ return { delivered: false, method, reason: "not_connected" };
368908
+ const threadTs = options?.threadId ?? this.activeThreads.get(`${instanceId}:${chatId}`);
368784
368909
  if (!threadTs)
368785
- return;
368786
- if (duration === 0) {
368787
- await clearTypingStatus({
368788
- client: connection.client,
368789
- channelId: chatId,
368790
- threadTs,
368791
- logger: this.logger
368792
- });
368793
- } else {
368794
- await setTypingStatus({
368795
- client: connection.client,
368796
- channelId: chatId,
368797
- threadTs,
368798
- logger: this.logger
368799
- });
368910
+ return { delivered: false, method, reason: "no_active_thread" };
368911
+ const shouldClear = type === "paused";
368912
+ const status = shouldClear ? "" : options?.status ?? (type === "recording" ? "is recording..." : "is typing...");
368913
+ const timerKey = this.presenceStatusTimerKey(instanceId, chatId, threadTs);
368914
+ const delivered = status === "" ? await clearTypingStatus({
368915
+ client: connection.client,
368916
+ channelId: chatId,
368917
+ threadTs,
368918
+ logger: this.logger
368919
+ }) : await setSlackThreadStatus({
368920
+ client: connection.client,
368921
+ channelId: chatId,
368922
+ threadTs,
368923
+ status,
368924
+ loadingMessages: options?.loadingMessages,
368925
+ logger: this.logger
368926
+ });
368927
+ if (delivered) {
368928
+ this.clearPresenceStatusTimer(timerKey);
368800
368929
  }
368930
+ if (delivered && status !== "" && duration && duration > 0) {
368931
+ const timer = setTimeout(() => {
368932
+ if (this.presenceStatusTimers.get(timerKey) !== timer)
368933
+ return;
368934
+ this.presenceStatusTimers.delete(timerKey);
368935
+ clearTypingStatus({
368936
+ client: connection.client,
368937
+ channelId: chatId,
368938
+ threadTs,
368939
+ logger: this.logger
368940
+ }).catch((err) => {
368941
+ this.logger.warn("Slack presence status auto-clear failed", {
368942
+ instanceId,
368943
+ channelId: chatId,
368944
+ threadTs,
368945
+ error: String(err)
368946
+ });
368947
+ });
368948
+ }, duration);
368949
+ this.presenceStatusTimers.set(timerKey, timer);
368950
+ timer.unref?.();
368951
+ }
368952
+ return delivered ? { delivered: true, method, threadId: threadTs, status, loadingMessages: options?.loadingMessages } : {
368953
+ delivered: false,
368954
+ method,
368955
+ threadId: threadTs,
368956
+ status,
368957
+ loadingMessages: options?.loadingMessages,
368958
+ reason: "slack_status_failed"
368959
+ };
368960
+ }
368961
+ presenceStatusTimerKey(instanceId, channelId, threadTs) {
368962
+ return `${instanceId}:${channelId}:${threadTs}`;
368963
+ }
368964
+ clearPresenceStatusTimer(timerKey) {
368965
+ const timer = this.presenceStatusTimers.get(timerKey);
368966
+ if (!timer)
368967
+ return;
368968
+ clearTimeout(timer);
368969
+ this.presenceStatusTimers.delete(timerKey);
368801
368970
  }
368802
368971
  async editMessage(instanceId, channelId, messageTs, newText) {
368803
368972
  const connection = this.getConnection(instanceId);
@@ -369039,6 +369208,7 @@ class SlackPlugin extends BaseChannelPlugin {
369039
369208
  threadTs: resolvedThread,
369040
369209
  logger: this.logger
369041
369210
  });
369211
+ this.clearPresenceStatusTimer(this.presenceStatusTimerKey(instanceId, channelId, resolvedThread));
369042
369212
  }
369043
369213
  getConnection(instanceId) {
369044
369214
  const connection = this.connections.get(instanceId);
@@ -487766,30 +487936,44 @@ class WhatsAppPlugin extends BaseChannelPlugin {
487766
487936
  }
487767
487937
  this.reportHistorySyncProgress(instanceId, syncState, progress, isLatest, messages2.length);
487768
487938
  }
487769
- async processHistoryMessage(instanceId, msg, syncState) {
487770
- if (!msg.key?.id || !msg.key?.remoteJid) {
487771
- this.logger.debug("Skipping history message without key", { instanceId, hasKey: !!msg.key });
487772
- return;
487773
- }
487774
- const timestamp = this.getMessageTimestamp(msg);
487939
+ isHistoryMessageWithinSyncRange(msg, timestamp, syncState, instanceId) {
487775
487940
  if (syncState?.since && timestamp < syncState.since) {
487776
487941
  this.logger.debug("Skipping history message - before since", {
487777
487942
  instanceId,
487778
- messageId: msg.key.id,
487779
- chatId: msg.key.remoteJid,
487780
- timestamp: new Date(timestamp).toISOString(),
487943
+ messageId: msg.key?.id,
487944
+ chatId: msg.key?.remoteJid,
487945
+ timestamp: timestamp.toISOString(),
487781
487946
  since: new Date(syncState.since).toISOString()
487782
487947
  });
487783
- return;
487948
+ return false;
487784
487949
  }
487785
487950
  if (syncState?.until && timestamp > syncState.until) {
487786
487951
  this.logger.debug("Skipping history message - after until", {
487787
487952
  instanceId,
487788
- messageId: msg.key.id,
487789
- chatId: msg.key.remoteJid,
487790
- timestamp: new Date(timestamp).toISOString(),
487953
+ messageId: msg.key?.id,
487954
+ chatId: msg.key?.remoteJid,
487955
+ timestamp: timestamp.toISOString(),
487791
487956
  until: new Date(syncState.until).toISOString()
487792
487957
  });
487958
+ return false;
487959
+ }
487960
+ return true;
487961
+ }
487962
+ async processHistoryMessage(instanceId, msg, syncState) {
487963
+ if (!msg.key?.id || !msg.key?.remoteJid) {
487964
+ this.logger.debug("Skipping history message without key", { instanceId, hasKey: !!msg.key });
487965
+ return;
487966
+ }
487967
+ const timestamp = this.getMessageTimestamp(msg);
487968
+ if (!timestamp) {
487969
+ this.logger.debug("Skipping history message without timestamp", {
487970
+ instanceId,
487971
+ messageId: msg.key.id,
487972
+ chatId: msg.key.remoteJid
487973
+ });
487974
+ return;
487975
+ }
487976
+ if (!this.isHistoryMessageWithinSyncRange(msg, timestamp, syncState, instanceId)) {
487793
487977
  return;
487794
487978
  }
487795
487979
  const content = this.extractHistoryMessageContent(msg);
@@ -487878,8 +488062,10 @@ class WhatsAppPlugin extends BaseChannelPlugin {
487878
488062
  }
487879
488063
  getMessageTimestamp(msg) {
487880
488064
  if (!msg.messageTimestamp)
487881
- return new Date;
488065
+ return null;
487882
488066
  const ts = typeof msg.messageTimestamp === "number" ? msg.messageTimestamp : Number(msg.messageTimestamp);
488067
+ if (!ts || Number.isNaN(ts))
488068
+ return null;
487883
488069
  return new Date(ts * 1000);
487884
488070
  }
487885
488071
  reportHistorySyncProgress(instanceId, syncState, progress, isLatest, messageCount) {