@automagik/omni 2.260624.2 → 2.260624.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -62387,7 +62387,8 @@ class AgnoAgentProvider {
62387
62387
  });
62388
62388
  const response = await this.client.run(request);
62389
62389
  const customerContent = toSafeCustomerFallback(response.content);
62390
- if (customerContent !== response.content) {
62390
+ const customerErrorBlocked = customerContent !== response.content;
62391
+ if (customerErrorBlocked) {
62391
62392
  log8.error("Blocked provider error from customer-facing Agno response", {
62392
62393
  agentId: this.config.agentId,
62393
62394
  runId: response.runId,
@@ -62414,7 +62415,8 @@ class AgnoAgentProvider {
62414
62415
  cost: response.metrics ? {
62415
62416
  inputTokens: response.metrics.inputTokens,
62416
62417
  outputTokens: response.metrics.outputTokens
62417
- } : undefined
62418
+ } : undefined,
62419
+ customerErrorBlocked
62418
62420
  }
62419
62421
  };
62420
62422
  }
@@ -125002,7 +125004,7 @@ import { fileURLToPath } from "url";
125002
125004
  // package.json
125003
125005
  var package_default = {
125004
125006
  name: "@automagik/omni",
125005
- version: "2.260624.2",
125007
+ version: "2.260624.4",
125006
125008
  description: "LLM-optimized CLI for Omni",
125007
125009
  type: "module",
125008
125010
  bin: {
@@ -30720,7 +30720,8 @@ class AgnoAgentProvider {
30720
30720
  });
30721
30721
  const response = await this.client.run(request);
30722
30722
  const customerContent = toSafeCustomerFallback(response.content);
30723
- if (customerContent !== response.content) {
30723
+ const customerErrorBlocked = customerContent !== response.content;
30724
+ if (customerErrorBlocked) {
30724
30725
  log8.error("Blocked provider error from customer-facing Agno response", {
30725
30726
  agentId: this.config.agentId,
30726
30727
  runId: response.runId,
@@ -30747,7 +30748,8 @@ class AgnoAgentProvider {
30747
30748
  cost: response.metrics ? {
30748
30749
  inputTokens: response.metrics.inputTokens,
30749
30750
  outputTokens: response.metrics.outputTokens
30750
- } : undefined
30751
+ } : undefined,
30752
+ customerErrorBlocked
30751
30753
  }
30752
30754
  };
30753
30755
  }
@@ -225289,7 +225291,7 @@ var init_sentry_scrub = __esm(() => {
225289
225291
  var require_package7 = __commonJS((exports, module) => {
225290
225292
  module.exports = {
225291
225293
  name: "@omni/api",
225292
- version: "2.260624.2",
225294
+ version: "2.260624.4",
225293
225295
  type: "module",
225294
225296
  exports: {
225295
225297
  ".": {
@@ -332955,6 +332957,62 @@ var init_batch_pricing = __esm(() => {
332955
332957
  // ../api/src/services/media-storage.ts
332956
332958
  import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync6, statSync as statSync3, writeFileSync as writeFileSync2 } from "fs";
332957
332959
  import { dirname as dirname6, extname as extname2, join as join21, relative as relative2 } from "path";
332960
+ function normalizeMimeType2(value) {
332961
+ return value?.split(";")[0]?.trim().toLowerCase() || undefined;
332962
+ }
332963
+ function isHtmlMime2(value) {
332964
+ const mimeType = normalizeMimeType2(value);
332965
+ return mimeType === "text/html" || mimeType === "application/xhtml+xml";
332966
+ }
332967
+ function looksLikeHtml2(buffer3) {
332968
+ const preview = buffer3.subarray(0, 512).toString("utf8").trimStart().toLowerCase();
332969
+ return preview.startsWith("<!doctype html") || preview.startsWith("<html>") || preview.startsWith("<html ");
332970
+ }
332971
+ function shouldRejectHtmlMedia(expectedMimeType, responseMimeType, buffer3) {
332972
+ const expected = normalizeMimeType2(expectedMimeType);
332973
+ if (!expected || isHtmlMime2(expected))
332974
+ return false;
332975
+ return isHtmlMime2(responseMimeType) || looksLikeHtml2(buffer3);
332976
+ }
332977
+ function hostMatchesSuffix2(hostname3, suffix) {
332978
+ const normalizedHost = hostname3.toLowerCase();
332979
+ const normalizedSuffix = suffix.toLowerCase();
332980
+ return normalizedHost === normalizedSuffix || normalizedHost.endsWith(`.${normalizedSuffix}`);
332981
+ }
332982
+ function shouldPreserveAuthForRedirect(url, suffixes) {
332983
+ return Boolean(suffixes?.some((suffix) => hostMatchesSuffix2(url.hostname, suffix)));
332984
+ }
332985
+ function headersWithOptionalAuthorization2(headers, preserveAuthorization) {
332986
+ const nextHeaders = new Headers(headers);
332987
+ if (!preserveAuthorization)
332988
+ nextHeaders.delete("authorization");
332989
+ return nextHeaders;
332990
+ }
332991
+ async function fetchWithOptionalAuthenticatedRedirects(url, fetchOptions) {
332992
+ const { preserveAuthRedirectHostSuffixes, ...init5 } = fetchOptions ?? {};
332993
+ if (!preserveAuthRedirectHostSuffixes?.length) {
332994
+ return fetch(url, init5);
332995
+ }
332996
+ let currentUrl = new URL(url);
332997
+ let currentHeaders = new Headers(init5.headers);
332998
+ for (let redirects = 0;redirects <= 5; redirects++) {
332999
+ const response = await fetch(currentUrl.toString(), {
333000
+ ...init5,
333001
+ headers: currentHeaders,
333002
+ redirect: "manual"
333003
+ });
333004
+ if (response.status < 300 || response.status >= 400)
333005
+ return response;
333006
+ const location = response.headers.get("location");
333007
+ if (!location)
333008
+ return response;
333009
+ const nextUrl = new URL(location, currentUrl);
333010
+ const preserveAuthorization = shouldPreserveAuthForRedirect(currentUrl, preserveAuthRedirectHostSuffixes) && shouldPreserveAuthForRedirect(nextUrl, preserveAuthRedirectHostSuffixes);
333011
+ currentHeaders = headersWithOptionalAuthorization2(init5.headers, preserveAuthorization);
333012
+ currentUrl = nextUrl;
333013
+ }
333014
+ throw new Error("Failed to download media: too many redirects");
333015
+ }
332958
333016
  function getExtensionFromMime2(mimeType) {
332959
333017
  const mimeToExt = {
332960
333018
  "image/jpeg": ".jpg",
@@ -333043,12 +333101,16 @@ class MediaStorageService {
333043
333101
  };
333044
333102
  }
333045
333103
  async storeFromUrl(instanceId, messageId, url, mimeType, timestamp3, fetchOptions) {
333046
- const response = await fetch(url, fetchOptions);
333104
+ const response = await fetchWithOptionalAuthenticatedRedirects(url, fetchOptions);
333047
333105
  if (!response.ok) {
333048
333106
  throw new Error(`Failed to download media: ${response.status}`);
333049
333107
  }
333108
+ const responseContentType = response.headers.get("content-type") ?? undefined;
333050
333109
  const buffer3 = Buffer.from(await response.arrayBuffer());
333051
- const contentType = mimeType ?? response.headers.get("content-type") ?? undefined;
333110
+ if (shouldRejectHtmlMedia(mimeType, responseContentType, buffer3)) {
333111
+ throw new Error(`Downloaded media content mismatch: expected ${mimeType}, received ${responseContentType ?? "unknown content type"}`);
333112
+ }
333113
+ const contentType = mimeType ?? responseContentType;
333052
333114
  return this.storeFromBuffer(instanceId, messageId, buffer3, contentType, timestamp3);
333053
333115
  }
333054
333116
  async updateMessageLocalPath(messageId, localPath) {
@@ -338466,6 +338528,68 @@ async function sendErrorFeedback(channel5, instanceId, chatId, error3, message2)
338466
338528
  log99.error("agent_dispatch_error", { channel: channel5, instanceId, chatId, error: errorMsg });
338467
338529
  await sendTextMessage5(channel5, instanceId, chatId, message2);
338468
338530
  }
338531
+ function resolveErrorHandoffMessage(env2 = process.env) {
338532
+ return env2.OMNI_AGENT_ERROR_HANDOFF_MESSAGE?.trim() || DEFAULT_ERROR_HANDOFF_MESSAGE;
338533
+ }
338534
+ async function triggerErrorHandoff(services, db2, channel5, instance4, chatId, message2) {
338535
+ const plugin7 = await getPlugin(channel5);
338536
+ if (plugin7?.capabilities?.canHandoff !== true)
338537
+ return false;
338538
+ let sendResult;
338539
+ try {
338540
+ sendResult = await plugin7.sendMessage(instance4.id, {
338541
+ to: chatId,
338542
+ content: { type: "text", text: message2 },
338543
+ metadata: { isHandoff: true, motivoHandoff: "agent_dispatch_error" }
338544
+ });
338545
+ } catch (err) {
338546
+ log99.error("agent_dispatch_error_handoff_failed", { instanceId: instance4.id, chatId, error: String(err) });
338547
+ return false;
338548
+ }
338549
+ if (!sendResult?.success) {
338550
+ log99.error("agent_dispatch_error_handoff_delivery_failed", {
338551
+ instanceId: instance4.id,
338552
+ chatId,
338553
+ error: sendResult?.error
338554
+ });
338555
+ return false;
338556
+ }
338557
+ try {
338558
+ const chat2 = await services.chats.findByExternalIdSmart(instance4.id, chatId);
338559
+ if (chat2) {
338560
+ const settings = chat2.settings ?? {};
338561
+ await services.chats.update(chat2.id, { settings: { ...settings, agentPaused: true } });
338562
+ await services.followUpLifecycle.disarm({ chatId: chat2.id, instanceId: instance4.id, reason: "handoff" });
338563
+ await db2.insert(handoffLogs).values({
338564
+ instanceId: instance4.id,
338565
+ chatUuid: chat2.id,
338566
+ chatId,
338567
+ toPhone: chatId,
338568
+ text: message2,
338569
+ extraInfo: null,
338570
+ agentId: instance4.agentId ?? null,
338571
+ externalMessageId: sendResult?.messageId ?? null,
338572
+ handoffFields: null,
338573
+ sentAt: new Date,
338574
+ metadata: { instanceChannel: channel5, channelHandoffSupported: true, motivoHandoff: "agent_dispatch_error" }
338575
+ });
338576
+ }
338577
+ } catch (err) {
338578
+ log99.warn("agent_dispatch_error_handoff_side_effects_failed", {
338579
+ instanceId: instance4.id,
338580
+ chatId,
338581
+ error: String(err)
338582
+ });
338583
+ }
338584
+ log99.info("agent_dispatch_error_handoff", { instanceId: instance4.id, chatId, channel: channel5 });
338585
+ return true;
338586
+ }
338587
+ async function handleDispatchFailure(services, db2, channel5, instance4, chatId, error3) {
338588
+ const handedOff = await triggerErrorHandoff(services, db2, channel5, instance4, chatId, resolveErrorHandoffMessage());
338589
+ if (handedOff)
338590
+ return;
338591
+ await sendErrorFeedback(channel5, instance4.id, chatId, error3, resolveDispatchErrorMessage(null)).catch(() => {});
338592
+ }
338469
338593
  function sleep5(ms) {
338470
338594
  return new Promise((resolve3) => setTimeout(resolve3, ms));
338471
338595
  }
@@ -339567,7 +339691,11 @@ async function dispatchViaProvider(services, instance4, messages4, triggerType,
339567
339691
  }
339568
339692
  const chatAfterRun = await services.chats.findByExternalIdSmart(instance4.id, chatId);
339569
339693
  const handoffTriggered = chatAfterRun?.settings?.agentPaused === true;
339570
- if (result && result.parts.length > 0 && !handoffTriggered) {
339694
+ let errorHandoffDone = false;
339695
+ if (result?.metadata.customerErrorBlocked === true && !handoffTriggered) {
339696
+ errorHandoffDone = await triggerErrorHandoff(services, db2, channel5, instance4, chatId, resolveErrorHandoffMessage());
339697
+ }
339698
+ if (result && result.parts.length > 0 && !handoffTriggered && !errorHandoffDone) {
339571
339699
  const selfChat = isSelfChat(chatId, instance4.ownerIdentifier);
339572
339700
  const rawParts = selfChat ? result.parts.map((p2) => `${BOT_PREFIX}${p2}`) : result.parts;
339573
339701
  const parts = await Promise.all(rawParts.map((part) => executeBeforeMessageWriteHooks(instance4.id, chatId, part)));
@@ -340071,7 +340199,7 @@ async function processAgentResponse(services, instance4, messages4, triggerType,
340071
340199
  error: String(error3),
340072
340200
  traceId
340073
340201
  });
340074
- sendErrorFeedback(channel5, instance4.id, chatId, error3, resolveDispatchErrorMessage(null)).catch(() => {});
340202
+ await handleDispatchFailure(services, db2, channel5, instance4, chatId, error3);
340075
340203
  } finally {
340076
340204
  ackHandle.remove();
340077
340205
  }
@@ -341198,7 +341326,7 @@ async function setupAgentDispatcher(eventBus, services, db2) {
341198
341326
  log99.info("Agent dispatcher shutdown complete");
341199
341327
  };
341200
341328
  }
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;
341329
+ 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.", 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, 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;
341202
341330
  var init_agent_dispatcher = __esm(() => {
341203
341331
  init_src2();
341204
341332
  init_src();
@@ -356723,6 +356851,27 @@ async function resolveMessageFromRef(services, ref) {
356723
356851
  if ("messageId" in ref) {
356724
356852
  return services.messages.getById(ref.messageId);
356725
356853
  }
356854
+ if ("chatExternalId" in ref) {
356855
+ const chat2 = await services.chats.findByExternalIdSmart(ref.instanceId, ref.chatExternalId);
356856
+ if (!chat2) {
356857
+ throw new OmniError({
356858
+ code: ERROR_CODES.NOT_FOUND,
356859
+ message: `Chat not found for instanceId=${ref.instanceId}, chatExternalId=${ref.chatExternalId}`,
356860
+ context: { instanceId: ref.instanceId, chatExternalId: ref.chatExternalId },
356861
+ recoverable: false
356862
+ });
356863
+ }
356864
+ const found2 = await services.messages.getByExternalId(chat2.id, ref.externalId);
356865
+ if (!found2) {
356866
+ throw new OmniError({
356867
+ code: ERROR_CODES.NOT_FOUND,
356868
+ message: `Message not found for chatExternalId=${ref.chatExternalId}, externalId=${ref.externalId}`,
356869
+ context: { instanceId: ref.instanceId, chatExternalId: ref.chatExternalId, externalId: ref.externalId },
356870
+ recoverable: false
356871
+ });
356872
+ }
356873
+ return found2;
356874
+ }
356726
356875
  const found = await services.messages.getByExternalId(ref.chatId, ref.externalId);
356727
356876
  if (!found) {
356728
356877
  throw new OmniError({
@@ -356734,6 +356883,17 @@ async function resolveMessageFromRef(services, ref) {
356734
356883
  }
356735
356884
  return found;
356736
356885
  }
356886
+ function buildMediaDownloadFetchOptions(instance4) {
356887
+ if (instance4.channel !== "slack")
356888
+ return;
356889
+ const slackBotToken = typeof instance4.slackBotToken === "string" ? instance4.slackBotToken : undefined;
356890
+ if (!slackBotToken)
356891
+ return;
356892
+ return {
356893
+ headers: { Authorization: `Bearer ${slackBotToken}` },
356894
+ preserveAuthRedirectHostSuffixes: ["slack.com"]
356895
+ };
356896
+ }
356737
356897
  async function computeCloseContactTerminalState(db2, chatUuid, outcome, auditRowId) {
356738
356898
  const cfg = resolveCloseContactConfig(outcome, null);
356739
356899
  if (isHardTerminalOutcome(outcome)) {
@@ -357021,7 +357181,8 @@ var init_messages5 = __esm(() => {
357021
357181
  });
357022
357182
  messageRefSchema = exports_external.union([
357023
357183
  exports_external.object({ messageId: exports_external.string().uuid() }),
357024
- exports_external.object({ chatId: exports_external.string().uuid(), externalId: exports_external.string().min(1) })
357184
+ exports_external.object({ chatId: exports_external.string().uuid(), externalId: exports_external.string().min(1) }),
357185
+ exports_external.object({ instanceId: exports_external.string().uuid(), chatExternalId: exports_external.string().min(1), externalId: exports_external.string().min(1) })
357025
357186
  ]);
357026
357187
  messagesRoutes.post("/media/download", zValidator("json", messageRefSchema), async (c) => {
357027
357188
  const body = c.req.valid("json");
@@ -357039,6 +357200,7 @@ var init_messages5 = __esm(() => {
357039
357200
  recoverable: false
357040
357201
  });
357041
357202
  }
357203
+ const instance4 = await services.instances.getById(instanceId);
357042
357204
  checkInstanceAccess2(apiKey, instanceId);
357043
357205
  if (!message2.hasMedia || !message2.mediaUrl) {
357044
357206
  return c.json({
@@ -357066,7 +357228,7 @@ var init_messages5 = __esm(() => {
357066
357228
  }
357067
357229
  if (!cached) {
357068
357230
  try {
357069
- const result = await mediaStorage2.storeFromUrl(instanceId, message2.id, mediaUrl, message2.mediaMimeType ?? undefined, message2.platformTimestamp ?? undefined);
357231
+ const result = await mediaStorage2.storeFromUrl(instanceId, message2.id, mediaUrl, message2.mediaMimeType ?? undefined, message2.platformTimestamp ?? undefined, buildMediaDownloadFetchOptions(instance4));
357070
357232
  mediaLocalPath = result.localPath;
357071
357233
  await mediaStorage2.updateMessageLocalPath(message2.id, result.localPath);
357072
357234
  mediaDownloadLog.info("Downloaded and cached media", {
@@ -357087,7 +357249,7 @@ var init_messages5 = __esm(() => {
357087
357249
  });
357088
357250
  }
357089
357251
  }
357090
- const downloadUrl = `/api/v2/media/${instanceId}/${mediaLocalPath}`;
357252
+ const downloadUrl = `/api/v2/media/${mediaLocalPath}`;
357091
357253
  return c.json({
357092
357254
  data: {
357093
357255
  messageId: message2.id,
@@ -367738,6 +367900,51 @@ function setupCommandHandlers(app, instanceId, commandNames, callbacks, logger5)
367738
367900
  init_src2();
367739
367901
  init_types10();
367740
367902
  var downloadGuard2 = createDownloadGuard();
367903
+ function normalizeMimeType(value) {
367904
+ return value?.split(";")[0]?.trim().toLowerCase() || undefined;
367905
+ }
367906
+ function isHtmlMime(value) {
367907
+ const mimeType = normalizeMimeType(value);
367908
+ return mimeType === "text/html" || mimeType === "application/xhtml+xml";
367909
+ }
367910
+ function looksLikeHtml(buffer2) {
367911
+ const preview = buffer2.subarray(0, 512).toString("utf8").trimStart().toLowerCase();
367912
+ return preview.startsWith("<!doctype html") || preview.startsWith("<html>") || preview.startsWith("<html ");
367913
+ }
367914
+ function hostMatchesSuffix(hostname, suffix) {
367915
+ const normalizedHost = hostname.toLowerCase();
367916
+ const normalizedSuffix = suffix.toLowerCase();
367917
+ return normalizedHost === normalizedSuffix || normalizedHost.endsWith(`.${normalizedSuffix}`);
367918
+ }
367919
+ function isSlackHost(url) {
367920
+ return hostMatchesSuffix(url.hostname, "slack.com");
367921
+ }
367922
+ function headersWithOptionalAuthorization(botToken, preserveAuthorization) {
367923
+ const headers = new Headers;
367924
+ if (preserveAuthorization)
367925
+ headers.set("Authorization", `Bearer ${botToken}`);
367926
+ headers.set("Accept", "application/octet-stream");
367927
+ return headers;
367928
+ }
367929
+ async function fetchSlackPrivateUrl(url, botToken) {
367930
+ let currentUrl = new URL(url);
367931
+ let preserveAuthorization = true;
367932
+ for (let redirects = 0;redirects <= 5; redirects++) {
367933
+ const response = await fetch(currentUrl.toString(), {
367934
+ headers: headersWithOptionalAuthorization(botToken, preserveAuthorization),
367935
+ redirect: "manual"
367936
+ });
367937
+ if (response.status < 300 || response.status >= 400)
367938
+ return response;
367939
+ const location = response.headers.get("location");
367940
+ if (!location)
367941
+ return response;
367942
+ const nextUrl = new URL(location, currentUrl);
367943
+ preserveAuthorization = isSlackHost(currentUrl) && isSlackHost(nextUrl);
367944
+ currentUrl = nextUrl;
367945
+ }
367946
+ throw new SlackError(SlackErrorCode.FILE_DOWNLOAD_FAILED, "Failed to download file: too many redirects");
367947
+ }
367741
367948
  function extractFileInfo(files) {
367742
367949
  if (!files || !Array.isArray(files))
367743
367950
  return [];
@@ -367754,20 +367961,19 @@ function extractFileInfo(files) {
367754
367961
  };
367755
367962
  });
367756
367963
  }
367757
- async function downloadSlackFile(url, botToken, logger5) {
367964
+ async function downloadSlackFile(url, botToken, logger5, expectedMimeType) {
367758
367965
  logger5.debug("Downloading file from Slack CDN", { url: url.substring(0, 50) });
367759
367966
  try {
367760
- const response = await fetch(url, {
367761
- headers: {
367762
- Authorization: `Bearer ${botToken}`
367763
- }
367764
- });
367967
+ const response = await fetchSlackPrivateUrl(url, botToken);
367765
367968
  if (!response.ok) {
367766
367969
  throw new SlackError(SlackErrorCode.FILE_DOWNLOAD_FAILED, `Failed to download file: HTTP ${response.status} ${response.statusText}`);
367767
367970
  }
367768
367971
  downloadGuard2.checkResponse(response, logger5, { channel: "slack", url: url.substring(0, 50) });
367769
367972
  const buffer2 = Buffer.from(await response.arrayBuffer());
367770
367973
  const mimeType = response.headers.get("content-type") ?? "application/octet-stream";
367974
+ if (!isHtmlMime(expectedMimeType) && (isHtmlMime(mimeType) || looksLikeHtml(buffer2))) {
367975
+ throw new SlackError(SlackErrorCode.FILE_DOWNLOAD_FAILED, `Failed to download file bytes: Slack returned HTML instead of ${expectedMimeType ?? "media"}`);
367976
+ }
367771
367977
  logger5.debug("File downloaded successfully", { size: buffer2.length, mimeType });
367772
367978
  return { buffer: buffer2, mimeType };
367773
367979
  } catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automagik/omni",
3
- "version": "2.260624.2",
3
+ "version": "2.260624.4",
4
4
  "description": "LLM-optimized CLI for Omni",
5
5
  "type": "module",
6
6
  "bin": {