mcp-scraper 0.40.3 → 0.41.0

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.
@@ -67,7 +67,7 @@ import {
67
67
  renewConnectedDataArtifactDownload,
68
68
  renewDirectoryArtifactDownload,
69
69
  transcribeMediaUrl
70
- } from "./chunk-6SZ52BQ5.js";
70
+ } from "./chunk-SIE5LZ2V.js";
71
71
  import {
72
72
  auditImageUrls,
73
73
  auditImages,
@@ -119,7 +119,7 @@ import {
119
119
  RawMapsOverviewSchema,
120
120
  RawMapsReviewStatsSchema
121
121
  } from "./chunk-CB5C3BPB.js";
122
- import "./chunk-XU4ZLIB2.js";
122
+ import "./chunk-OOB35KFT.js";
123
123
  import {
124
124
  abandonExtractSettlement,
125
125
  countSuccessfulPages,
@@ -29824,6 +29824,7 @@ var CONNECTED_DATA_DATASETS = [
29824
29824
  "calendar_events",
29825
29825
  "zoom_recordings",
29826
29826
  "zoom_transcripts",
29827
+ "slack_channel_messages",
29827
29828
  "meta_ads_insights",
29828
29829
  "search_console_performance",
29829
29830
  "resend_data",
@@ -29850,8 +29851,9 @@ function resolveConnectedDataRange(args) {
29850
29851
  const from = args.from ? new Date(args.from) : new Date(to.getTime() - (args.lastDays ?? 7) * 864e5);
29851
29852
  if (!Number.isFinite(from.getTime())) throw new ConnectedDataExportValidationError("from must be an RFC3339 timestamp.");
29852
29853
  if (from.getTime() >= to.getTime()) throw new ConnectedDataExportValidationError("from must be earlier than to.");
29853
- if (to.getTime() - from.getTime() > 90 * 864e5) {
29854
- throw new ConnectedDataExportValidationError("A connected-data export can cover at most 90 days per request.");
29854
+ const maxRangeDays = args.maxRangeDays === void 0 ? 90 : args.maxRangeDays;
29855
+ if (maxRangeDays !== null && to.getTime() - from.getTime() > maxRangeDays * 864e5) {
29856
+ throw new ConnectedDataExportValidationError(`A connected-data export can cover at most ${maxRangeDays} days per request.`);
29855
29857
  }
29856
29858
  if (to.getTime() > now.getTime() + 5 * 6e4) {
29857
29859
  throw new ConnectedDataExportValidationError("to cannot be in the future.");
@@ -29861,10 +29863,18 @@ function resolveConnectedDataRange(args) {
29861
29863
  function resolveConnectedDataExportRequest(args) {
29862
29864
  const continuation = args.continuation;
29863
29865
  if (!continuation) {
29866
+ const scope2 = resolveConnectedDataExportScope(args.requestedDataset, args.scope);
29864
29867
  return {
29865
29868
  dataset: args.requestedDataset,
29866
- range: resolveConnectedDataRange({ from: args.from, to: args.to, lastDays: args.lastDays, now: args.now }),
29867
- ...args.cursor ? { cursor: args.cursor } : {}
29869
+ range: resolveConnectedDataRange({
29870
+ from: args.from,
29871
+ to: args.to,
29872
+ lastDays: args.lastDays,
29873
+ now: args.now,
29874
+ maxRangeDays: args.requestedDataset === "slack_channel_messages" ? null : 90
29875
+ }),
29876
+ ...args.cursor ? { cursor: args.cursor } : {},
29877
+ ...scope2 ? { scope: scope2 } : {}
29868
29878
  };
29869
29879
  }
29870
29880
  const supported = CONNECTED_DATA_DATASETS.filter((dataset2) => dataset2 !== "auto");
@@ -29887,10 +29897,38 @@ function resolveConnectedDataExportRequest(args) {
29887
29897
  throw new ConnectedDataExportValidationError("dataset conflicts with continuation.");
29888
29898
  }
29889
29899
  const dataset = continuation.dataset;
29900
+ const scope = resolveConnectedDataExportScope(dataset, continuation.scope);
29901
+ if (args.scope && JSON.stringify(resolveConnectedDataExportScope(dataset, args.scope)) !== JSON.stringify(scope)) {
29902
+ throw new ConnectedDataExportValidationError("scope conflicts with continuation.");
29903
+ }
29890
29904
  return {
29891
29905
  dataset,
29892
- range: resolveConnectedDataRange({ from: continuation.from, to: continuation.to, now: args.now }),
29893
- cursor: continuation.cursor
29906
+ range: resolveConnectedDataRange({
29907
+ from: continuation.from,
29908
+ to: continuation.to,
29909
+ now: args.now,
29910
+ maxRangeDays: dataset === "slack_channel_messages" ? null : 90
29911
+ }),
29912
+ cursor: continuation.cursor,
29913
+ ...scope ? { scope } : {}
29914
+ };
29915
+ }
29916
+ function resolveConnectedDataExportScope(dataset, scope) {
29917
+ if (dataset !== "slack_channel_messages") {
29918
+ if (scope?.slack) {
29919
+ throw new ConnectedDataExportValidationError("Slack export scope requires dataset slack_channel_messages.");
29920
+ }
29921
+ return void 0;
29922
+ }
29923
+ const channelId = scope?.slack?.channelId?.trim();
29924
+ if (!channelId || !/^[A-Za-z0-9_-]{2,100}$/.test(channelId)) {
29925
+ throw new ConnectedDataExportValidationError("Slack channel export requires a valid channelId.");
29926
+ }
29927
+ return {
29928
+ slack: {
29929
+ channelId,
29930
+ includeThreads: scope?.slack?.includeThreads !== false
29931
+ }
29894
29932
  };
29895
29933
  }
29896
29934
  function previewRecord(value) {
@@ -29919,7 +29957,14 @@ function previewRecord(value) {
29919
29957
  "createdAt",
29920
29958
  "updatedAt",
29921
29959
  "hasTranscript",
29922
- "contentTruncated"
29960
+ "contentTruncated",
29961
+ "channelId",
29962
+ "threadTs",
29963
+ "parentMessageTs",
29964
+ "occurredAt",
29965
+ "userId",
29966
+ "text",
29967
+ "replyCount"
29923
29968
  ]) {
29924
29969
  if (record[key] === void 0) continue;
29925
29970
  const item = record[key];
@@ -29962,7 +30007,8 @@ async function collectConnectedDataExport(args) {
29962
30007
  dataset: args.dataset,
29963
30008
  from: args.range.from,
29964
30009
  to: args.range.to,
29965
- pageSize: Math.min(25, maxItems - records.length),
30010
+ pageSize: Math.min(args.dataset === "slack_channel_messages" ? 200 : 25, maxItems - records.length),
30011
+ ...args.scope ? { scope: args.scope } : {},
29966
30012
  ...cursor !== void 0 && cursor !== null ? { cursor } : {}
29967
30013
  });
29968
30014
  pages++;
@@ -30007,6 +30053,7 @@ async function collectConnectedDataExport(args) {
30007
30053
  providerConfigKey,
30008
30054
  dataset,
30009
30055
  range: args.range,
30056
+ ...args.scope ? { scope: args.scope } : {},
30010
30057
  complete,
30011
30058
  counts: { pages, listed, exported: records.length, failed },
30012
30059
  untrustedContent: true,
@@ -30026,6 +30073,7 @@ ${lines.length ? `${lines.join("\n")}
30026
30073
  providerConfigKey,
30027
30074
  dataset,
30028
30075
  range: args.range,
30076
+ ...args.scope ? { scope: args.scope } : {},
30029
30077
  counts: { pages, listed, exported: records.length, failed, bytes },
30030
30078
  complete,
30031
30079
  ...!artifact ? { records } : {},
@@ -30035,7 +30083,8 @@ ${lines.length ? `${lines.join("\n")}
30035
30083
  cursor: continuationCursor,
30036
30084
  from: args.range.from,
30037
30085
  to: args.range.to,
30038
- dataset
30086
+ dataset,
30087
+ ...args.scope ? { scope: args.scope } : {}
30039
30088
  } : null,
30040
30089
  warnings: [...warnings],
30041
30090
  untrustedContent: true
@@ -30205,6 +30254,97 @@ async function getFreeCreditBreakdown(userId) {
30205
30254
 
30206
30255
  // src/api/memory-db.ts
30207
30256
  import { neon } from "@neondatabase/serverless";
30257
+
30258
+ // src/api/memory-universe.ts
30259
+ var SEP = "\n";
30260
+ var WIKILINK = /\[\[([^\][]+)\]\]/g;
30261
+ var HASH = /#[^]*$/;
30262
+ var CONNECTION_RE = /^Connection - (.+) - ([A-Za-z0-9_-]+)$/;
30263
+ function stripExt(value) {
30264
+ return value.replace(/\.(md|markdown|txt)$/i, "");
30265
+ }
30266
+ function vaultLabel(vault) {
30267
+ const provider = vault.match(CONNECTION_RE)?.[1];
30268
+ if (!provider) return vault;
30269
+ return provider.split(/[\s_-]+/).filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
30270
+ }
30271
+ function noteTags(props) {
30272
+ const tags = props.tags;
30273
+ return Array.isArray(tags) ? tags.filter((tag) => typeof tag === "string") : [];
30274
+ }
30275
+ function wikiTargets(content) {
30276
+ const targets = [];
30277
+ for (const match of content.matchAll(WIKILINK)) {
30278
+ const target = match[1].split("|", 1)[0].replace(HASH, "").trim();
30279
+ if (target) targets.push(target);
30280
+ }
30281
+ return targets;
30282
+ }
30283
+ function buildResolver(notes) {
30284
+ const index = /* @__PURE__ */ new Map();
30285
+ const add = (key, note) => {
30286
+ const normalized = key.trim().toLowerCase();
30287
+ if (normalized && !index.has(normalized)) index.set(normalized, note);
30288
+ };
30289
+ for (const note of notes) add(`${note.vault}/${note.path}`, note);
30290
+ for (const note of notes) add(note.path, note);
30291
+ for (const note of notes) add(stripExt(note.path), note);
30292
+ for (const note of notes) if (note.title) add(note.title, note);
30293
+ for (const note of notes) {
30294
+ const base = note.path.split("/").pop() ?? note.path;
30295
+ add(base, note);
30296
+ add(stripExt(base), note);
30297
+ }
30298
+ return (target) => {
30299
+ const normalized = target.trim().toLowerCase();
30300
+ return index.get(normalized) ?? index.get(stripExt(normalized)) ?? null;
30301
+ };
30302
+ }
30303
+ function buildUniverseData(notes) {
30304
+ const resolver = buildResolver(notes);
30305
+ const nodes = [];
30306
+ const indexById = /* @__PURE__ */ new Map();
30307
+ const nodeFor = (id, label, group, ghost, extra) => {
30308
+ const existing = indexById.get(id);
30309
+ if (existing !== void 0) return existing;
30310
+ const index = nodes.length;
30311
+ indexById.set(id, index);
30312
+ nodes.push({ id, label, group, ghost, ...extra });
30313
+ return index;
30314
+ };
30315
+ for (const note of notes) {
30316
+ nodeFor(
30317
+ `${note.vault}${SEP}${note.path}`,
30318
+ note.title.trim() || note.path.split("/").pop() || note.path,
30319
+ vaultLabel(note.vault),
30320
+ false,
30321
+ { sub: note.path, tags: noteTags(note.props), updatedAt: note.updatedAt }
30322
+ );
30323
+ }
30324
+ const links = [];
30325
+ const seenEdges = /* @__PURE__ */ new Set();
30326
+ for (const note of notes) {
30327
+ const from = indexById.get(`${note.vault}${SEP}${note.path}`);
30328
+ if (from === void 0) continue;
30329
+ for (const target of wikiTargets(note.content)) {
30330
+ const resolved = resolver(target);
30331
+ const to = resolved ? nodeFor(
30332
+ `${resolved.vault}${SEP}${resolved.path}`,
30333
+ resolved.title.trim() || resolved.path.split("/").pop() || resolved.path,
30334
+ vaultLabel(resolved.vault),
30335
+ false
30336
+ ) : nodeFor(`ghost:${target.toLowerCase()}`, target, "", true);
30337
+ if (to === from) continue;
30338
+ const edgeKey = from < to ? `${from}:${to}` : `${to}:${from}`;
30339
+ if (seenEdges.has(edgeKey)) continue;
30340
+ seenEdges.add(edgeKey);
30341
+ links.push({ s: from, t: to });
30342
+ }
30343
+ }
30344
+ return { nodes, links, total: notes.length, built: notes.length, failed: 0 };
30345
+ }
30346
+
30347
+ // src/api/memory-db.ts
30208
30348
  var _sql = null;
30209
30349
  function sql() {
30210
30350
  if (_sql) return _sql;
@@ -30349,6 +30489,35 @@ async function dbNote(identity, vault, path5) {
30349
30489
  capturedAt: r.captured_at
30350
30490
  };
30351
30491
  }
30492
+ async function dbUniverse(identity, includeConnections = false) {
30493
+ const entitled = await entitledVaultRows(identity);
30494
+ const selected = entitled.filter(
30495
+ (entry) => includeConnections || !/^Connection - (.+) - ([A-Za-z0-9_-]+)$/.test(entry.vault)
30496
+ );
30497
+ if (selected.length === 0) return buildUniverseData([]);
30498
+ const logicalByPhysical = new Map(selected.map((entry) => [entry.physical, entry.vault]));
30499
+ const physicals = [...logicalByPhysical.keys()];
30500
+ const rows = await sql().query(
30501
+ `SELECT vault, path, title, content, updated_at, props
30502
+ FROM mem_notes
30503
+ WHERE vault = ANY($1)
30504
+ ORDER BY updated_at DESC`,
30505
+ [physicals]
30506
+ );
30507
+ const notes = rows.flatMap((row) => {
30508
+ const vault = logicalByPhysical.get(row.vault);
30509
+ if (!vault) return [];
30510
+ return [{
30511
+ vault,
30512
+ path: row.path,
30513
+ title: row.title,
30514
+ content: row.content,
30515
+ updatedAt: row.updated_at,
30516
+ props: row.props && typeof row.props === "object" ? row.props : {}
30517
+ }];
30518
+ });
30519
+ return buildUniverseData(notes);
30520
+ }
30352
30521
  var FREE_COST_CAP_USD = 1;
30353
30522
  async function dbUsage(identity, plan) {
30354
30523
  const now = /* @__PURE__ */ new Date();
@@ -30380,6 +30549,375 @@ async function dbUsage(identity, plan) {
30380
30549
  // src/api/nango-control.ts
30381
30550
  import { createHash as createHash14, randomUUID as randomUUID17 } from "crypto";
30382
30551
 
30552
+ // src/api/slack-archive-analysis.ts
30553
+ function isRecord2(value) {
30554
+ return !!value && typeof value === "object" && !Array.isArray(value);
30555
+ }
30556
+ function cleanString(value) {
30557
+ return typeof value === "string" && value.trim() ? value.trim() : null;
30558
+ }
30559
+ function slackWeekStart(value) {
30560
+ if (!value) return null;
30561
+ const date = new Date(value);
30562
+ if (!Number.isFinite(date.getTime())) return null;
30563
+ const daysSinceMonday = (date.getUTCDay() + 6) % 7;
30564
+ date.setUTCDate(date.getUTCDate() - daysSinceMonday);
30565
+ return date.toISOString().slice(0, 10);
30566
+ }
30567
+ function normalizedUrl(value) {
30568
+ const candidate = cleanString(value)?.split("|", 1)[0]?.replace(/[),.;!?]+$/, "");
30569
+ if (!candidate || !/^https?:\/\//i.test(candidate)) return null;
30570
+ try {
30571
+ return new URL(candidate).toString();
30572
+ } catch {
30573
+ return null;
30574
+ }
30575
+ }
30576
+ function deriveSlackMessageIndexFields(args) {
30577
+ const fileNames = args.files.flatMap((file) => {
30578
+ if (!isRecord2(file)) return [];
30579
+ const name = cleanString(file.name) ?? cleanString(file.title) ?? cleanString(file.id);
30580
+ return name ? [name] : [];
30581
+ });
30582
+ const urls = /* @__PURE__ */ new Set();
30583
+ const text2 = args.text ?? "";
30584
+ for (const match of text2.matchAll(/<(https?:\/\/[^>|]+)(?:\|[^>]*)?>/gi)) {
30585
+ const url = normalizedUrl(match[1]);
30586
+ if (url) urls.add(url);
30587
+ }
30588
+ for (const match of text2.matchAll(/https?:\/\/[^\s<>()]+/gi)) {
30589
+ const url = normalizedUrl(match[0]);
30590
+ if (url) urls.add(url);
30591
+ }
30592
+ const attachments = Array.isArray(args.providerData.attachments) ? args.providerData.attachments : [];
30593
+ for (const attachment of attachments) {
30594
+ if (!isRecord2(attachment)) continue;
30595
+ for (const key of ["url", "title_link", "from_url", "service_url"]) {
30596
+ const url = normalizedUrl(attachment[key]);
30597
+ if (url) urls.add(url);
30598
+ }
30599
+ }
30600
+ const profile = isRecord2(args.providerData.user_profile) ? args.providerData.user_profile : isRecord2(args.providerData.profile) ? args.providerData.profile : null;
30601
+ const authorName = cleanString(profile?.display_name) ?? cleanString(profile?.real_name) ?? cleanString(args.providerData.username) ?? cleanString(args.providerData.user_name);
30602
+ return {
30603
+ weekStart: slackWeekStart(args.occurredAt),
30604
+ authorName,
30605
+ fileCount: args.files.length,
30606
+ hasFiles: args.files.length > 0,
30607
+ fileNames,
30608
+ linkCount: urls.size,
30609
+ links: [...urls]
30610
+ };
30611
+ }
30612
+
30613
+ // src/api/slack-connected-data-export.ts
30614
+ var CURSOR_PREFIX = "slack1.";
30615
+ var MAX_CURSOR_BYTES = 64 * 1024;
30616
+ var MAX_PENDING_THREADS = 200;
30617
+ var THREAD_CONCURRENCY = 3;
30618
+ var MAX_SLACK_RETRIES = 3;
30619
+ function isRecord3(value) {
30620
+ return !!value && typeof value === "object" && !Array.isArray(value);
30621
+ }
30622
+ function cleanString2(value, maxLength = 1e4) {
30623
+ return typeof value === "string" && value.trim() ? value.trim().slice(0, maxLength) : null;
30624
+ }
30625
+ function unwrapToolResult(value) {
30626
+ if (!isRecord3(value)) return value;
30627
+ if (isRecord3(value.structuredContent)) return value.structuredContent;
30628
+ if (Array.isArray(value.content)) {
30629
+ const textPart = value.content.find((part) => isRecord3(part) && part.type === "text" && typeof part.text === "string");
30630
+ if (isRecord3(textPart) && typeof textPart.text === "string") {
30631
+ try {
30632
+ return unwrapToolResult(JSON.parse(textPart.text));
30633
+ } catch {
30634
+ return textPart.text;
30635
+ }
30636
+ }
30637
+ }
30638
+ if (isRecord3(value.result)) return unwrapToolResult(value.result);
30639
+ if (isRecord3(value.data)) return unwrapToolResult(value.data);
30640
+ return value;
30641
+ }
30642
+ function messagesFromToolResult(value) {
30643
+ const root = unwrapToolResult(value);
30644
+ if (Array.isArray(root)) return root.filter(isRecord3);
30645
+ if (!isRecord3(root)) return [];
30646
+ for (const key of ["messages", "replies", "items", "data"]) {
30647
+ if (Array.isArray(root[key])) return root[key].filter(isRecord3);
30648
+ }
30649
+ return [];
30650
+ }
30651
+ function nextCursorFromToolResult(value) {
30652
+ const root = unwrapToolResult(value);
30653
+ if (!isRecord3(root)) return null;
30654
+ const metadata = isRecord3(root.response_metadata) ? root.response_metadata : isRecord3(root.responseMetadata) ? root.responseMetadata : isRecord3(root.metadata) ? root.metadata : null;
30655
+ return cleanString2(
30656
+ root.next_cursor ?? root.nextCursor ?? metadata?.next_cursor ?? metadata?.nextCursor,
30657
+ 1e4
30658
+ );
30659
+ }
30660
+ function slackTimestampToIso(value) {
30661
+ const timestamp = cleanString2(value, 64);
30662
+ if (!timestamp || !/^\d{1,20}(?:\.\d{1,9})?$/.test(timestamp)) return null;
30663
+ const milliseconds = Number(timestamp) * 1e3;
30664
+ if (!Number.isFinite(milliseconds)) return null;
30665
+ try {
30666
+ return new Date(milliseconds).toISOString();
30667
+ } catch {
30668
+ return null;
30669
+ }
30670
+ }
30671
+ function normalizeSlackMessage(row, channelId, parentMessageTs) {
30672
+ const timestamp = cleanString2(row.ts, 64);
30673
+ const replyCount = typeof row.reply_count === "number" && Number.isFinite(row.reply_count) ? Math.max(0, Math.floor(row.reply_count)) : 0;
30674
+ const rowThreadTs = cleanString2(row.thread_ts, 64);
30675
+ const inferredParentMessageTs = parentMessageTs ?? (rowThreadTs && rowThreadTs !== timestamp ? rowThreadTs : null);
30676
+ const threadTs = inferredParentMessageTs ?? rowThreadTs ?? (replyCount > 0 ? timestamp : null);
30677
+ const occurredAt = slackTimestampToIso(timestamp);
30678
+ const files = Array.isArray(row.files) ? row.files : [];
30679
+ const text2 = typeof row.text === "string" ? row.text : null;
30680
+ return {
30681
+ recordType: inferredParentMessageTs ? "slack_thread_reply" : "slack_message",
30682
+ dataset: "slack_channel_messages",
30683
+ id: timestamp ? `${channelId}:${timestamp}` : `${channelId}:unknown`,
30684
+ providerRecordId: timestamp,
30685
+ channelId,
30686
+ threadTs,
30687
+ parentMessageTs: inferredParentMessageTs,
30688
+ isThreadReply: inferredParentMessageTs !== null,
30689
+ occurredAt,
30690
+ userId: cleanString2(row.user, 100) ?? cleanString2(row.bot_id, 100),
30691
+ text: text2,
30692
+ subtype: cleanString2(row.subtype, 100),
30693
+ replyCount,
30694
+ files,
30695
+ reactions: Array.isArray(row.reactions) ? row.reactions : [],
30696
+ providerData: row,
30697
+ ...deriveSlackMessageIndexFields({
30698
+ occurredAt,
30699
+ text: text2,
30700
+ files,
30701
+ providerData: row
30702
+ })
30703
+ };
30704
+ }
30705
+ function encodeCursor(state) {
30706
+ const encoded = `${CURSOR_PREFIX}${Buffer.from(JSON.stringify(state)).toString("base64url")}`;
30707
+ if (Buffer.byteLength(encoded, "utf8") > MAX_CURSOR_BYTES) {
30708
+ throw new Error("slack_export_cursor_too_large");
30709
+ }
30710
+ return encoded;
30711
+ }
30712
+ function validProviderCursor(value) {
30713
+ return value === null || typeof value === "string" && value.length > 0 && value.length <= 1e4;
30714
+ }
30715
+ function decodeCursor(cursor, channelId, includeThreads) {
30716
+ if (!cursor) {
30717
+ return { version: 1, phase: "history", channelId, includeThreads, historyCursor: null };
30718
+ }
30719
+ if (!cursor.startsWith(CURSOR_PREFIX) || Buffer.byteLength(cursor, "utf8") > MAX_CURSOR_BYTES) {
30720
+ throw new Error("slack_export_cursor_invalid");
30721
+ }
30722
+ let parsed;
30723
+ try {
30724
+ parsed = JSON.parse(Buffer.from(cursor.slice(CURSOR_PREFIX.length), "base64url").toString("utf8"));
30725
+ } catch {
30726
+ throw new Error("slack_export_cursor_invalid");
30727
+ }
30728
+ if (!isRecord3(parsed) || parsed.version !== 1 || parsed.channelId !== channelId || parsed.includeThreads !== includeThreads || !validProviderCursor(parsed.historyCursor)) {
30729
+ throw new Error("slack_export_cursor_invalid");
30730
+ }
30731
+ if (parsed.phase === "history") {
30732
+ return {
30733
+ version: 1,
30734
+ phase: "history",
30735
+ channelId,
30736
+ includeThreads,
30737
+ historyCursor: parsed.historyCursor
30738
+ };
30739
+ }
30740
+ if (parsed.phase !== "threads" || includeThreads !== true || !Array.isArray(parsed.historyMessageTs) || !Array.isArray(parsed.threads) || parsed.threads.length < 1 || parsed.threads.length > MAX_PENDING_THREADS) {
30741
+ throw new Error("slack_export_cursor_invalid");
30742
+ }
30743
+ const historyMessageTs = parsed.historyMessageTs.flatMap((value) => {
30744
+ const timestamp = cleanString2(value, 64);
30745
+ return timestamp && /^\d{1,20}(?:\.\d{1,9})?$/.test(timestamp) ? [timestamp] : [];
30746
+ });
30747
+ if (historyMessageTs.length !== parsed.historyMessageTs.length || historyMessageTs.length > MAX_PENDING_THREADS) {
30748
+ throw new Error("slack_export_cursor_invalid");
30749
+ }
30750
+ const threads = parsed.threads.flatMap((value) => {
30751
+ if (!isRecord3(value)) return [];
30752
+ const threadTs = cleanString2(value.threadTs, 64);
30753
+ if (!threadTs || !/^\d{1,20}(?:\.\d{1,9})?$/.test(threadTs) || !validProviderCursor(value.cursor)) return [];
30754
+ return [{ threadTs, cursor: value.cursor }];
30755
+ });
30756
+ if (threads.length !== parsed.threads.length) throw new Error("slack_export_cursor_invalid");
30757
+ return {
30758
+ version: 1,
30759
+ phase: "threads",
30760
+ channelId,
30761
+ includeThreads: true,
30762
+ historyCursor: parsed.historyCursor,
30763
+ historyMessageTs,
30764
+ threads
30765
+ };
30766
+ }
30767
+ function slackBoundaryTimestamp(iso, offsetMicroseconds) {
30768
+ const milliseconds = new Date(iso).getTime();
30769
+ if (!Number.isFinite(milliseconds)) throw new Error("slack_export_range_invalid");
30770
+ return Math.max(0, milliseconds / 1e3 + offsetMicroseconds / 1e6).toFixed(6);
30771
+ }
30772
+ function errorText(error) {
30773
+ if (error instanceof Error) return `${error.name} ${error.message}`;
30774
+ if (typeof error === "string") return error;
30775
+ try {
30776
+ return JSON.stringify(error);
30777
+ } catch {
30778
+ return "";
30779
+ }
30780
+ }
30781
+ function retryAfterMilliseconds(error, attempt) {
30782
+ const text2 = errorText(error);
30783
+ if (!/429|rate.?limit|resource_exhausted/i.test(text2)) return null;
30784
+ if (isRecord3(error) && typeof error.retryAfterMs === "number" && Number.isFinite(error.retryAfterMs)) {
30785
+ return Math.min(Math.max(error.retryAfterMs, 1e3), 6e4);
30786
+ }
30787
+ const explicit = text2.match(/retry[_ -]?after[^0-9]{0,20}(\d{1,4})/i);
30788
+ const seconds = explicit ? Number(explicit[1]) : attempt;
30789
+ return Math.min(Math.max(seconds, 1) * 1e3, 6e4);
30790
+ }
30791
+ async function callSlackToolWithRetry(dependencies, args) {
30792
+ for (let attempt = 1; attempt <= MAX_SLACK_RETRIES; attempt += 1) {
30793
+ try {
30794
+ return await dependencies.callTool(args);
30795
+ } catch (error) {
30796
+ const delay = retryAfterMilliseconds(error, attempt);
30797
+ if (delay === null || attempt === MAX_SLACK_RETRIES) throw error;
30798
+ await (dependencies.sleep ?? ((milliseconds) => new Promise((resolve2) => setTimeout(resolve2, milliseconds))))(delay);
30799
+ }
30800
+ }
30801
+ throw new Error("slack_export_retry_exhausted");
30802
+ }
30803
+ function nextAfterThreads(state) {
30804
+ if (state.threads.length > 0) return { nextCursor: encodeCursor(state), complete: false };
30805
+ if (state.historyCursor) {
30806
+ return {
30807
+ nextCursor: encodeCursor({
30808
+ version: 1,
30809
+ phase: "history",
30810
+ channelId: state.channelId,
30811
+ includeThreads: true,
30812
+ historyCursor: state.historyCursor
30813
+ }),
30814
+ complete: false
30815
+ };
30816
+ }
30817
+ return { nextCursor: null, complete: true };
30818
+ }
30819
+ async function exportSlackChannelPage(input, dependencies) {
30820
+ const channelId = input.scope?.slack?.channelId?.trim();
30821
+ if (!channelId) throw new Error("slack_export_channel_required");
30822
+ const includeThreads = input.scope?.slack?.includeThreads !== false;
30823
+ const state = decodeCursor(input.cursor, channelId, includeThreads);
30824
+ const pageSize = Math.max(1, Math.min(200, Math.floor(input.pageSize)));
30825
+ if (state.phase === "history") {
30826
+ const result = await callSlackToolWithRetry(dependencies, {
30827
+ tool: "get-conversation-history",
30828
+ input: {
30829
+ channel_id: channelId,
30830
+ limit: pageSize,
30831
+ oldest: slackBoundaryTimestamp(input.from, -1),
30832
+ latest: slackBoundaryTimestamp(input.to, 0),
30833
+ inclusive: false,
30834
+ ...state.historyCursor ? { cursor: state.historyCursor } : {}
30835
+ },
30836
+ requestId: `slack-export:${input.connectionId}:${channelId}:history:${state.historyCursor ?? "start"}`
30837
+ });
30838
+ const messages = messagesFromToolResult(result).slice(0, pageSize);
30839
+ const historyCursor = nextCursorFromToolResult(result);
30840
+ const records2 = messages.map((message) => normalizeSlackMessage(message, channelId, null));
30841
+ const threadIds = includeThreads ? [...new Set(messages.flatMap((message) => {
30842
+ const replyCount = typeof message.reply_count === "number" ? message.reply_count : 0;
30843
+ const timestamp = cleanString2(message.ts, 64);
30844
+ return replyCount > 0 && timestamp ? [timestamp] : [];
30845
+ }))].slice(0, MAX_PENDING_THREADS) : [];
30846
+ if (threadIds.length > 0) {
30847
+ return {
30848
+ providerConfigKey: "slack",
30849
+ dataset: "slack_channel_messages",
30850
+ records: records2,
30851
+ nextCursor: encodeCursor({
30852
+ version: 1,
30853
+ phase: "threads",
30854
+ channelId,
30855
+ includeThreads: true,
30856
+ historyCursor,
30857
+ historyMessageTs: messages.flatMap((message) => cleanString2(message.ts, 64) ?? []),
30858
+ threads: threadIds.map((threadTs) => ({ threadTs, cursor: null }))
30859
+ }),
30860
+ complete: false,
30861
+ counts: { listed: messages.length, exported: records2.length, failed: 0 },
30862
+ warnings: threadIds.length === MAX_PENDING_THREADS ? ["A history page reached the per-page threaded-parent safety limit; narrow the export range if threads are missing."] : [],
30863
+ untrustedContent: true
30864
+ };
30865
+ }
30866
+ return {
30867
+ providerConfigKey: "slack",
30868
+ dataset: "slack_channel_messages",
30869
+ records: records2,
30870
+ nextCursor: historyCursor ? encodeCursor({ ...state, historyCursor }) : null,
30871
+ complete: historyCursor === null,
30872
+ counts: { listed: messages.length, exported: records2.length, failed: 0 },
30873
+ warnings: [],
30874
+ untrustedContent: true
30875
+ };
30876
+ }
30877
+ const batchSize = Math.min(THREAD_CONCURRENCY, state.threads.length, pageSize);
30878
+ const batch = state.threads.slice(0, batchSize);
30879
+ const perThreadLimit = Math.max(1, Math.min(100, Math.floor(pageSize / batchSize)));
30880
+ const results = await Promise.all(batch.map((thread) => callSlackToolWithRetry(dependencies, {
30881
+ tool: "get-thread-replies",
30882
+ input: {
30883
+ channel_id: channelId,
30884
+ thread_ts: thread.threadTs,
30885
+ limit: perThreadLimit,
30886
+ ...thread.cursor ? { cursor: thread.cursor } : {}
30887
+ },
30888
+ requestId: `slack-export:${input.connectionId}:${channelId}:thread:${thread.threadTs}:${thread.cursor ?? "start"}`
30889
+ })));
30890
+ const records = [];
30891
+ const stillPending = [];
30892
+ let listed = 0;
30893
+ for (let index = 0; index < results.length; index += 1) {
30894
+ const thread = batch[index];
30895
+ const messages = messagesFromToolResult(results[index]);
30896
+ listed += messages.length;
30897
+ records.push(...messages.filter((message) => {
30898
+ const timestamp = cleanString2(message.ts, 64);
30899
+ return timestamp !== thread.threadTs && !state.historyMessageTs.includes(timestamp ?? "");
30900
+ }).map((message) => normalizeSlackMessage(message, channelId, thread.threadTs)));
30901
+ const cursor = nextCursorFromToolResult(results[index]);
30902
+ if (cursor) stillPending.push({ threadTs: thread.threadTs, cursor });
30903
+ }
30904
+ const nextState = {
30905
+ ...state,
30906
+ threads: [...stillPending, ...state.threads.slice(batchSize)]
30907
+ };
30908
+ const next = nextAfterThreads(nextState);
30909
+ return {
30910
+ providerConfigKey: "slack",
30911
+ dataset: "slack_channel_messages",
30912
+ records,
30913
+ nextCursor: next.nextCursor,
30914
+ complete: next.complete,
30915
+ counts: { listed, exported: records.length, failed: 0 },
30916
+ warnings: [],
30917
+ untrustedContent: true
30918
+ };
30919
+ }
30920
+
30383
30921
  // src/api/main-nango-transport.ts
30384
30922
  import { createHash as createHash13, randomUUID as randomUUID16 } from "crypto";
30385
30923
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
@@ -30656,12 +31194,14 @@ var MainNangoTransportError = class extends Error {
30656
31194
  status;
30657
31195
  code;
30658
31196
  retryable;
30659
- constructor(message, status, code, retryable = status === 429 || status >= 500) {
31197
+ retryAfterMs;
31198
+ constructor(message, status, code, retryable = status === 429 || status >= 500, retryAfterMs = null) {
30660
31199
  super(message);
30661
31200
  this.name = "MainNangoTransportError";
30662
31201
  this.status = status;
30663
31202
  this.code = code;
30664
31203
  this.retryable = retryable;
31204
+ this.retryAfterMs = retryAfterMs;
30665
31205
  }
30666
31206
  };
30667
31207
  function nangoClientErrorText(error) {
@@ -30673,6 +31213,13 @@ function nangoClientErrorText(error) {
30673
31213
  return "";
30674
31214
  }
30675
31215
  }
31216
+ function retryAfterMsFromText(value) {
31217
+ const milliseconds = value.match(/retry[_ -]?after[_ -]?ms[^0-9]{0,20}(\d{1,7})/i);
31218
+ if (milliseconds) return Math.min(Math.max(Number(milliseconds[1]), 1e3), 6e4);
31219
+ const seconds = value.match(/retry[_ -]?after[^0-9]{0,20}(\d{1,4})/i);
31220
+ if (seconds) return Math.min(Math.max(Number(seconds[1]), 1) * 1e3, 6e4);
31221
+ return null;
31222
+ }
30676
31223
  function normalizeNangoClientError(error) {
30677
31224
  if (error instanceof MainNangoTransportError) return error;
30678
31225
  const detail = nangoClientErrorText(error);
@@ -30693,7 +31240,13 @@ function normalizeNangoClientError(error) {
30693
31240
  );
30694
31241
  }
30695
31242
  if (/RESOURCE_EXHAUSTED|rate.?limit|status["']?\s*:\s*429/i.test(detail)) {
30696
- return new MainNangoTransportError("The connected provider is temporarily rate limited.", 429, "upstream_rate_limited", true);
31243
+ return new MainNangoTransportError(
31244
+ "The connected provider is temporarily rate limited.",
31245
+ 429,
31246
+ "upstream_rate_limited",
31247
+ true,
31248
+ retryAfterMsFromText(detail)
31249
+ );
30697
31250
  }
30698
31251
  if (/UNAUTHENTICATED|invalid_grant|status["']?\s*:\s*401/i.test(detail)) {
30699
31252
  return new MainNangoTransportError("The service connection requires reconnection.", 409, "connection_inactive", false);
@@ -30758,7 +31311,9 @@ async function nangoRequest(path5, options = {}) {
30758
31311
  throw new MainNangoTransportError(
30759
31312
  status === 429 ? "The connected provider is temporarily rate limited." : "Nango request failed.",
30760
31313
  status,
30761
- status === 429 ? "upstream_rate_limited" : status === 404 ? "connection_not_found" : "connection_transport_unavailable"
31314
+ status === 429 ? "upstream_rate_limited" : status === 404 ? "connection_not_found" : "connection_transport_unavailable",
31315
+ status === 429 || status >= 500,
31316
+ status === 429 ? retryAfterMsFromText(`retry-after: ${response.headers.get("retry-after") ?? ""}`) : null
30762
31317
  );
30763
31318
  }
30764
31319
  return payload;
@@ -31206,37 +31761,37 @@ function controlSecret() {
31206
31761
  if (!secret2) throw new NangoControlError("Scheduled service connections are not configured.", 503);
31207
31762
  return secret2;
31208
31763
  }
31209
- function isRecord2(value) {
31764
+ function isRecord4(value) {
31210
31765
  return !!value && typeof value === "object" && !Array.isArray(value);
31211
31766
  }
31212
31767
  function unwrapData(value) {
31213
- if (isRecord2(value) && isRecord2(value.data)) return value.data;
31768
+ if (isRecord4(value) && isRecord4(value.data)) return value.data;
31214
31769
  return value;
31215
31770
  }
31216
- function cleanString(value, max = 300) {
31771
+ function cleanString3(value, max = 300) {
31217
31772
  if (typeof value !== "string") return null;
31218
31773
  const trimmed = value.trim();
31219
31774
  return trimmed ? trimmed.slice(0, max) : null;
31220
31775
  }
31221
31776
  function firstString(record, keys, max = 300) {
31222
31777
  for (const key of keys) {
31223
- const value = cleanString(record[key], max);
31778
+ const value = cleanString3(record[key], max);
31224
31779
  if (value) return value;
31225
31780
  }
31226
31781
  return null;
31227
31782
  }
31228
31783
  function cleanTools(value) {
31229
31784
  if (!Array.isArray(value)) return [];
31230
- const tools = value.map((tool) => cleanString(tool, 200)).filter((tool) => !!tool);
31785
+ const tools = value.map((tool) => cleanString3(tool, 200)).filter((tool) => !!tool);
31231
31786
  return [...new Set(tools)].slice(0, 200);
31232
31787
  }
31233
31788
  function cleanStringArray(value, maxItems = 20, maxLength = 100) {
31234
31789
  if (!Array.isArray(value)) return [];
31235
- const items = value.map((item) => cleanString(item, maxLength)).filter((item) => !!item);
31790
+ const items = value.map((item) => cleanString3(item, maxLength)).filter((item) => !!item);
31236
31791
  return [...new Set(items)].slice(0, maxItems);
31237
31792
  }
31238
31793
  function cleanHttpsUrl(value) {
31239
- const candidate = cleanString(value, 2e3);
31794
+ const candidate = cleanString3(value, 2e3);
31240
31795
  if (!candidate) return null;
31241
31796
  try {
31242
31797
  const url = new URL(candidate);
@@ -31248,7 +31803,7 @@ function cleanHttpsUrl(value) {
31248
31803
  function arrayFromPayload(value, keys) {
31249
31804
  const unwrapped = unwrapData(value);
31250
31805
  if (Array.isArray(unwrapped)) return unwrapped;
31251
- if (!isRecord2(unwrapped)) return [];
31806
+ if (!isRecord4(unwrapped)) return [];
31252
31807
  for (const key of keys) {
31253
31808
  if (Array.isArray(unwrapped[key])) return unwrapped[key];
31254
31809
  }
@@ -31329,7 +31884,7 @@ function defaultControlErrorMessage(status) {
31329
31884
  function safeControlErrorPayload(body, responseStatus) {
31330
31885
  const status = controlErrorStatus(responseStatus);
31331
31886
  const unwrapped = unwrapData(body);
31332
- const record = isRecord2(unwrapped) ? unwrapped : isRecord2(body) ? body : null;
31887
+ const record = isRecord4(unwrapped) ? unwrapped : isRecord4(body) ? body : null;
31333
31888
  const candidateCode = record ? firstString(record, ["code", "errorCode", "error_code"], 100) : null;
31334
31889
  const normalizedCandidateCode = candidateCode ? CONTROL_ERROR_CODE_ALIASES.get(candidateCode) ?? candidateCode : null;
31335
31890
  const code = normalizedCandidateCode && SAFE_CONTROL_ERROR_CODES.has(normalizedCandidateCode) ? normalizedCandidateCode : defaultControlErrorCode(status);
@@ -31378,7 +31933,7 @@ function sanitizeScheduleConnectionSelections(value) {
31378
31933
  const selections = [];
31379
31934
  const seen = /* @__PURE__ */ new Set();
31380
31935
  for (const item of value) {
31381
- if (!isRecord2(item)) throw new ScheduleConnectionValidationError("Each service connection must be an object.");
31936
+ if (!isRecord4(item)) throw new ScheduleConnectionValidationError("Each service connection must be an object.");
31382
31937
  const connectionId = firstString(item, ["connectionId", "connection_id"]);
31383
31938
  const providerConfigKey = firstString(item, ["providerConfigKey", "provider_config_key", "integrationId", "integration_id"]);
31384
31939
  const allowedTools = cleanTools(item.allowedTools ?? item.allowed_tools ?? item.allowedCapabilities ?? item.allowed_capabilities);
@@ -31398,7 +31953,7 @@ async function getNangoCatalog() {
31398
31953
  const rows = arrayFromPayload(body, ["providers", "catalog", "integrations", "services"]);
31399
31954
  const result = [];
31400
31955
  for (const row of rows) {
31401
- if (!isRecord2(row)) continue;
31956
+ if (!isRecord4(row)) continue;
31402
31957
  const providerConfigKey = firstString(row, ["providerConfigKey", "provider_config_key", "integrationId", "integration_id", "id"]);
31403
31958
  if (!providerConfigKey) continue;
31404
31959
  const provider = firstString(row, ["provider"]);
@@ -31418,18 +31973,18 @@ async function getNangoCatalog() {
31418
31973
  ).filter((tool) => !disabledTools?.has(tool));
31419
31974
  const requiredPermissionsByTool = {};
31420
31975
  const rawRequiredPermissions = row.requiredPermissionsByTool ?? row.required_permissions_by_tool;
31421
- if (isRecord2(rawRequiredPermissions)) {
31976
+ if (isRecord4(rawRequiredPermissions)) {
31422
31977
  for (const [tool, permissions] of Object.entries(rawRequiredPermissions).slice(0, 500)) {
31423
- const cleanTool = cleanString(tool, 200);
31978
+ const cleanTool = cleanString3(tool, 200);
31424
31979
  if (!cleanTool || !safeDefaultAllowedTools.includes(cleanTool) && !actionTools.includes(cleanTool)) continue;
31425
31980
  requiredPermissionsByTool[cleanTool] = cleanStringArray(permissions, 32, 200);
31426
31981
  }
31427
31982
  }
31428
31983
  const requiredFeaturesByTool = {};
31429
31984
  const rawRequiredFeatures = row.requiredFeaturesByTool ?? row.required_features_by_tool;
31430
- if (isRecord2(rawRequiredFeatures)) {
31985
+ if (isRecord4(rawRequiredFeatures)) {
31431
31986
  for (const [tool, features] of Object.entries(rawRequiredFeatures).slice(0, 500)) {
31432
- const cleanTool = cleanString(tool, 200);
31987
+ const cleanTool = cleanString3(tool, 200);
31433
31988
  if (!cleanTool) continue;
31434
31989
  requiredFeaturesByTool[cleanTool] = cleanStringArray(features, 32, 200);
31435
31990
  }
@@ -31554,7 +32109,7 @@ async function getNangoConnections(identity, options = {}) {
31554
32109
  const rows = arrayFromPayload(body, ["connections"]);
31555
32110
  const result = [];
31556
32111
  for (const row of rows) {
31557
- if (!isRecord2(row)) continue;
32112
+ if (!isRecord4(row)) continue;
31558
32113
  const connectionId = firstString(row, ["connectionId", "connection_id", "id"]);
31559
32114
  const providerConfigKey = firstString(row, ["providerConfigKey", "provider_config_key", "integrationId", "integration_id", "provider"]);
31560
32115
  if (!connectionId || !providerConfigKey) continue;
@@ -31573,8 +32128,8 @@ async function getNangoConnections(identity, options = {}) {
31573
32128
  const rawToolCapabilities = row.toolCapabilities ?? row.tool_capabilities;
31574
32129
  if (Array.isArray(rawToolCapabilities)) {
31575
32130
  for (const value of rawToolCapabilities.slice(0, 500)) {
31576
- if (!isRecord2(value)) continue;
31577
- const name = cleanString(value.name, 200);
32131
+ if (!isRecord4(value)) continue;
32132
+ const name = cleanString3(value.name, 200);
31578
32133
  const classification = value.classification;
31579
32134
  const blockedValue = Object.prototype.hasOwnProperty.call(value, "blockedReason") ? value.blockedReason : value.blocked_reason;
31580
32135
  const blockedReason = blockedValue === null ? null : blockedValue === "missing_permission" || blockedValue === "permission_policy_missing" || blockedValue === "permission_verification_unavailable" || blockedValue === "missing_app_feature" ? blockedValue : void 0;
@@ -31641,7 +32196,7 @@ async function deleteNangoConnection(identity, connectionId) {
31641
32196
  body: JSON.stringify({ identity, connectionId })
31642
32197
  });
31643
32198
  const data = unwrapData(body);
31644
- if (!isRecord2(data) || data.ok !== true) {
32199
+ if (!isRecord4(data) || data.ok !== true) {
31645
32200
  throw new NangoControlError("The connection service returned an invalid deletion response.");
31646
32201
  }
31647
32202
  const remaining = Number(data.remainingConnections);
@@ -31649,7 +32204,7 @@ async function deleteNangoConnection(identity, connectionId) {
31649
32204
  }
31650
32205
  function sanitizeConnectSession(body) {
31651
32206
  const data = unwrapData(body);
31652
- if (!isRecord2(data)) throw new NangoControlError("The connection service returned an invalid connect session.");
32207
+ if (!isRecord4(data)) throw new NangoControlError("The connection service returned an invalid connect session.");
31653
32208
  const connectLink = firstString(data, ["connectLink", "connect_link"], 2e3);
31654
32209
  if (!connectLink) throw new NangoControlError("The connection service did not return a connect link.");
31655
32210
  return {
@@ -31691,14 +32246,14 @@ async function createNangoReconnectSession(identity, connectionId) {
31691
32246
  function sanitizeBindingRows(body, defaultScheduleActionId) {
31692
32247
  const data = unwrapData(body);
31693
32248
  const flattened = [];
31694
- if (isRecord2(data) && isRecord2(data.bindings) && !Array.isArray(data.bindings)) {
32249
+ if (isRecord4(data) && isRecord4(data.bindings) && !Array.isArray(data.bindings)) {
31695
32250
  for (const [scheduleActionId, value] of Object.entries(data.bindings)) {
31696
32251
  if (Array.isArray(value)) value.forEach((row) => flattened.push({ row, scheduleActionId }));
31697
32252
  }
31698
32253
  } else {
31699
32254
  const rows = arrayFromPayload(data, ["bindings", "connections"]);
31700
32255
  for (const row of rows) {
31701
- if (isRecord2(row) && Array.isArray(row.connections)) {
32256
+ if (isRecord4(row) && Array.isArray(row.connections)) {
31702
32257
  const scheduleActionId = firstString(row, ["scheduleActionId", "schedule_action_id", "scheduleId", "schedule_id"]) || defaultScheduleActionId;
31703
32258
  row.connections.forEach((connection) => flattened.push({ row: connection, scheduleActionId }));
31704
32259
  } else {
@@ -31708,7 +32263,7 @@ function sanitizeBindingRows(body, defaultScheduleActionId) {
31708
32263
  }
31709
32264
  const result = [];
31710
32265
  for (const item of flattened) {
31711
- if (!isRecord2(item.row)) continue;
32266
+ if (!isRecord4(item.row)) continue;
31712
32267
  const connectionId = firstString(item.row, ["connectionId", "connection_id"]);
31713
32268
  const providerConfigKey = firstString(item.row, ["providerConfigKey", "provider_config_key", "integrationId", "integration_id", "provider"]);
31714
32269
  if (!connectionId || !providerConfigKey) continue;
@@ -31776,7 +32331,7 @@ async function setScheduleConnectionActionsEnabled(identity, connectionId, enabl
31776
32331
  body: JSON.stringify({ identity, connectionId, enabled })
31777
32332
  });
31778
32333
  const data = unwrapData(body);
31779
- if (!isRecord2(data) || !isRecord2(data.connection)) return enabled;
32334
+ if (!isRecord4(data) || !isRecord4(data.connection)) return enabled;
31780
32335
  return data.connection.actionsEnabled === true;
31781
32336
  }
31782
32337
  async function callScheduleConnectionAction(identity, connectionId, input, tool, idempotencyKey2) {
@@ -31826,7 +32381,7 @@ async function callScheduleConnectionAction(identity, connectionId, input, tool,
31826
32381
  body: JSON.stringify({ identity, connectionId, ...tool ? { tool } : {}, input })
31827
32382
  });
31828
32383
  const data = unwrapData(body);
31829
- return isRecord2(data) ? data.result ?? data : data;
32384
+ return isRecord4(data) ? data.result ?? data : data;
31830
32385
  }
31831
32386
  async function callScheduleConnectionRead(identity, connectionId, tool, args, idempotencyKey2) {
31832
32387
  if (mainOwnsIntegrations()) {
@@ -31849,7 +32404,7 @@ async function callScheduleConnectionRead(identity, connectionId, tool, args, id
31849
32404
  body: JSON.stringify({ identity, connectionId, tool, args: args ?? {} })
31850
32405
  });
31851
32406
  const data = unwrapData(body);
31852
- return isRecord2(data) ? data.result ?? data : data;
32407
+ return isRecord4(data) ? data.result ?? data : data;
31853
32408
  }
31854
32409
  async function testNangoConnection(identity, connectionId) {
31855
32410
  if (!mainOwnsIntegrations()) {
@@ -31869,20 +32424,20 @@ async function testNangoConnection(identity, connectionId) {
31869
32424
  }
31870
32425
  }
31871
32426
  function sanitizeToolSchema(value) {
31872
- if (!isRecord2(value) || value.type !== "object") return null;
32427
+ if (!isRecord4(value) || value.type !== "object") return null;
31873
32428
  try {
31874
32429
  const serialized = JSON.stringify(value);
31875
32430
  if (Buffer.byteLength(serialized, "utf8") > 256 * 1024) return null;
31876
32431
  const cloned = JSON.parse(serialized);
31877
- return isRecord2(cloned) ? cloned : null;
32432
+ return isRecord4(cloned) ? cloned : null;
31878
32433
  } catch {
31879
32434
  return null;
31880
32435
  }
31881
32436
  }
31882
32437
  function sanitizeToolAnnotations(value) {
31883
- if (!isRecord2(value)) return void 0;
32438
+ if (!isRecord4(value)) return void 0;
31884
32439
  const annotations = {};
31885
- const title = cleanString(value.title, 200);
32440
+ const title = cleanString3(value.title, 200);
31886
32441
  if (title) annotations.title = title;
31887
32442
  for (const key of ["readOnlyHint", "destructiveHint", "idempotentHint", "openWorldHint"]) {
31888
32443
  if (typeof value[key] === "boolean") annotations[key] = value[key];
@@ -31890,7 +32445,7 @@ function sanitizeToolAnnotations(value) {
31890
32445
  return Object.keys(annotations).length > 0 ? annotations : void 0;
31891
32446
  }
31892
32447
  function sanitizeToolIconSource(value) {
31893
- const candidate = cleanString(value, 1e4);
32448
+ const candidate = cleanString3(value, 1e4);
31894
32449
  if (!candidate) return null;
31895
32450
  if (/^data:image\/(?:png|jpeg|webp|gif|svg\+xml);base64,[a-z0-9+/=]+$/i.test(candidate)) return candidate;
31896
32451
  return cleanHttpsUrl(candidate);
@@ -31899,10 +32454,10 @@ function sanitizeToolIcons(value) {
31899
32454
  if (!Array.isArray(value)) return void 0;
31900
32455
  const icons = [];
31901
32456
  for (const item of value.slice(0, 16)) {
31902
- if (!isRecord2(item)) continue;
32457
+ if (!isRecord4(item)) continue;
31903
32458
  const src = sanitizeToolIconSource(item.src);
31904
32459
  if (!src) continue;
31905
- const mimeType = cleanString(item.mimeType ?? item.mime_type, 100);
32460
+ const mimeType = cleanString3(item.mimeType ?? item.mime_type, 100);
31906
32461
  const sizes = cleanStringArray(item.sizes, 16, 32);
31907
32462
  const theme = item.theme === "light" || item.theme === "dark" ? item.theme : void 0;
31908
32463
  icons.push({
@@ -31916,7 +32471,7 @@ function sanitizeToolIcons(value) {
31916
32471
  }
31917
32472
  function canonicalJson2(value) {
31918
32473
  if (Array.isArray(value)) return `[${value.map(canonicalJson2).join(",")}]`;
31919
- if (isRecord2(value)) {
32474
+ if (isRecord4(value)) {
31920
32475
  return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson2(value[key])}`).join(",")}}`;
31921
32476
  }
31922
32477
  return JSON.stringify(value);
@@ -31947,8 +32502,8 @@ async function describeNangoTool(identity, connectionId, tool, fresh) {
31947
32502
  }
31948
32503
  function sanitizeNangoToolDescription(body, tool) {
31949
32504
  const data = unwrapData(body);
31950
- const rawTool = isRecord2(data) && isRecord2(data.tool) ? data.tool : data;
31951
- if (!isRecord2(rawTool)) {
32505
+ const rawTool = isRecord4(data) && isRecord4(data.tool) ? data.tool : data;
32506
+ if (!isRecord4(rawTool)) {
31952
32507
  throw new NangoControlError(
31953
32508
  "The connection service returned an invalid live tool description.",
31954
32509
  502,
@@ -31961,13 +32516,13 @@ function sanitizeNangoToolDescription(body, tool) {
31961
32516
  serializedBytes = Buffer.byteLength(JSON.stringify(rawTool), "utf8");
31962
32517
  } catch {
31963
32518
  }
31964
- const name = cleanString(rawTool.name, 200);
31965
- const classification = cleanString(rawTool.classification, 20);
31966
- const transport = cleanString(rawTool.transport, 20);
31967
- const providerConfigKey = cleanString(rawTool.providerConfigKey ?? rawTool.provider_config_key, 200);
31968
- const schemaSource = cleanString(rawTool.schemaSource ?? rawTool.schema_source, 50);
31969
- const upstreamSchemaHash = cleanString(rawTool.schemaHash ?? rawTool.schema_hash, 200);
31970
- const fetchedAt = cleanString(rawTool.fetchedAt ?? rawTool.fetched_at, 100);
32519
+ const name = cleanString3(rawTool.name, 200);
32520
+ const classification = cleanString3(rawTool.classification, 20);
32521
+ const transport = cleanString3(rawTool.transport, 20);
32522
+ const providerConfigKey = cleanString3(rawTool.providerConfigKey ?? rawTool.provider_config_key, 200);
32523
+ const schemaSource = cleanString3(rawTool.schemaSource ?? rawTool.schema_source, 50);
32524
+ const upstreamSchemaHash = cleanString3(rawTool.schemaHash ?? rawTool.schema_hash, 200);
32525
+ const fetchedAt = cleanString3(rawTool.fetchedAt ?? rawTool.fetched_at, 100);
31971
32526
  const inputSchema = sanitizeToolSchema(rawTool.inputSchema ?? rawTool.input_schema);
31972
32527
  const outputSchemaValue = rawTool.outputSchema ?? rawTool.output_schema;
31973
32528
  const outputSchema = outputSchemaValue === void 0 ? void 0 : sanitizeToolSchema(outputSchemaValue);
@@ -31981,11 +32536,11 @@ function sanitizeNangoToolDescription(body, tool) {
31981
32536
  true
31982
32537
  );
31983
32538
  }
31984
- const protocolVersion = rawTool.protocolVersion === null || rawTool.protocol_version === null ? null : cleanString(rawTool.protocolVersion ?? rawTool.protocol_version, 100);
32539
+ const protocolVersion = rawTool.protocolVersion === null || rawTool.protocol_version === null ? null : cleanString3(rawTool.protocolVersion ?? rawTool.protocol_version, 100);
31985
32540
  const executionValue = rawTool.execution;
31986
- const taskSupport = isRecord2(executionValue) && (executionValue.taskSupport === "forbidden" || executionValue.taskSupport === "optional" || executionValue.taskSupport === "required") ? executionValue.taskSupport : null;
31987
- const title = cleanString(rawTool.title, 200);
31988
- const description = cleanString(rawTool.description, 12e3);
32541
+ const taskSupport = isRecord4(executionValue) && (executionValue.taskSupport === "forbidden" || executionValue.taskSupport === "optional" || executionValue.taskSupport === "required") ? executionValue.taskSupport : null;
32542
+ const title = cleanString3(rawTool.title, 200);
32543
+ const description = cleanString3(rawTool.description, 12e3);
31989
32544
  const annotations = sanitizeToolAnnotations(rawTool.annotations);
31990
32545
  const icons = sanitizeToolIcons(rawTool.icons);
31991
32546
  const execution = taskSupport ? { taskSupport } : void 0;
@@ -32028,10 +32583,13 @@ function sanitizeNangoToolDescription(body, tool) {
32028
32583
  };
32029
32584
  }
32030
32585
  async function callScheduleConnectionExportPage(identity, input) {
32031
- if (mainOwnsIntegrations() && input.dataset === "search_console_performance") {
32586
+ if (mainOwnsIntegrations() && (input.dataset === "search_console_performance" || input.dataset === "slack_channel_messages")) {
32032
32587
  try {
32033
32588
  return await callMainOwnedExportPage(identity, input);
32034
32589
  } catch (error) {
32590
+ if (error instanceof Error && /^slack_export_(?:channel_required|cursor_invalid|range_invalid)$/.test(error.message)) {
32591
+ throw new NangoControlError("The Slack channel export request or continuation is invalid.", 400, "invalid_request", false);
32592
+ }
32035
32593
  throw asNangoControlError(error);
32036
32594
  }
32037
32595
  }
@@ -32040,11 +32598,11 @@ async function callScheduleConnectionExportPage(identity, input) {
32040
32598
  body: JSON.stringify({ identity, ...input })
32041
32599
  }, 9e4);
32042
32600
  const data = unwrapData(body);
32043
- if (!isRecord2(data) || data.ok !== true) {
32601
+ if (!isRecord4(data) || data.ok !== true) {
32044
32602
  throw new NangoControlError("Connected-data export returned an invalid page response.");
32045
32603
  }
32046
- const providerConfigKey = cleanString(data.providerConfigKey, 128);
32047
- const dataset = cleanString(data.dataset, 64);
32604
+ const providerConfigKey = cleanString3(data.providerConfigKey, 128);
32605
+ const dataset = cleanString3(data.dataset, 64);
32048
32606
  if (!providerConfigKey || dataset === "auto" || !CONNECTED_DATA_DATASETS.includes(dataset)) {
32049
32607
  throw new NangoControlError("Connected-data export returned invalid provider metadata.");
32050
32608
  }
@@ -32054,7 +32612,7 @@ async function callScheduleConnectionExportPage(identity, input) {
32054
32612
  if (data.nextCursor !== void 0 && data.nextCursor !== null && typeof data.nextCursor !== "string") {
32055
32613
  throw new NangoControlError("Connected-data export returned an invalid continuation cursor.");
32056
32614
  }
32057
- const rawCounts = isRecord2(data.counts) ? data.counts : {};
32615
+ const rawCounts = isRecord4(data.counts) ? data.counts : {};
32058
32616
  const numberOrZero = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
32059
32617
  return {
32060
32618
  providerConfigKey,
@@ -32072,11 +32630,11 @@ async function callScheduleConnectionExportPage(identity, input) {
32072
32630
  };
32073
32631
  }
32074
32632
  function unwrapMcpToolJson(value) {
32075
- if (!isRecord2(value)) return value;
32076
- if (isRecord2(value.structuredContent)) return value.structuredContent;
32633
+ if (!isRecord4(value)) return value;
32634
+ if (isRecord4(value.structuredContent)) return value.structuredContent;
32077
32635
  if (Array.isArray(value.content)) {
32078
- const textPart = value.content.find((part) => isRecord2(part) && part.type === "text" && typeof part.text === "string");
32079
- if (isRecord2(textPart) && typeof textPart.text === "string") {
32636
+ const textPart = value.content.find((part) => isRecord4(part) && part.type === "text" && typeof part.text === "string");
32637
+ if (isRecord4(textPart) && typeof textPart.text === "string") {
32080
32638
  try {
32081
32639
  return JSON.parse(textPart.text);
32082
32640
  } catch {
@@ -32089,13 +32647,26 @@ function unwrapMcpToolJson(value) {
32089
32647
  function rowsFromToolResult(value, keys) {
32090
32648
  const unwrapped = unwrapMcpToolJson(value);
32091
32649
  if (Array.isArray(unwrapped)) return unwrapped;
32092
- if (!isRecord2(unwrapped)) return [];
32650
+ if (!isRecord4(unwrapped)) return [];
32093
32651
  for (const key of keys) if (Array.isArray(unwrapped[key])) return unwrapped[key];
32094
32652
  return [];
32095
32653
  }
32096
32654
  async function callMainOwnedExportPage(identity, input) {
32097
32655
  const connection = await getOwnedServiceConnection(identity, input.connectionId);
32098
32656
  if (!connection) throw new NangoControlError("The service connection was not found.", 404, "connection_not_found", false);
32657
+ if (connection.providerConfigKey === "slack" && input.dataset === "slack_channel_messages") {
32658
+ return exportSlackChannelPage(input, {
32659
+ callTool: async (args) => callNangoToolDirect({
32660
+ identity,
32661
+ connectionId: input.connectionId,
32662
+ tool: args.tool,
32663
+ input: args.input,
32664
+ classification: "read",
32665
+ requestId: args.requestId,
32666
+ operationKind: "export"
32667
+ })
32668
+ });
32669
+ }
32099
32670
  if (connection.providerConfigKey !== "google-search-console" || input.dataset !== "search_console_performance") {
32100
32671
  throw new NangoControlError("This dataset has not moved to main-MCP export execution yet.", 400, "invalid_request", false);
32101
32672
  }
@@ -32113,8 +32684,8 @@ async function callMainOwnedExportPage(identity, input) {
32113
32684
  operationKind: "export"
32114
32685
  });
32115
32686
  const sites = rowsFromToolResult(listed, ["siteEntry", "sites", "items"]).flatMap((value) => {
32116
- const row = isRecord2(value) ? value : null;
32117
- const siteUrl2 = row ? cleanString(row.siteUrl ?? row.site_url, 2e3) : null;
32687
+ const row = isRecord4(value) ? value : null;
32688
+ const siteUrl2 = row ? cleanString3(row.siteUrl ?? row.site_url, 2e3) : null;
32118
32689
  return siteUrl2 ? [siteUrl2] : [];
32119
32690
  }).sort().slice(0, 1e4);
32120
32691
  if (siteIndex >= sites.length) {
@@ -32151,7 +32722,7 @@ async function callMainOwnedExportPage(identity, input) {
32151
32722
  });
32152
32723
  const rows = rowsFromToolResult(performance2, ["rows"]).slice(0, input.pageSize);
32153
32724
  const records = rows.map((value, index) => {
32154
- const row = isRecord2(value) ? value : {};
32725
+ const row = isRecord4(value) ? value : {};
32155
32726
  const keys = Array.isArray(row.keys) ? row.keys.map((item) => String(item ?? "")) : [];
32156
32727
  const date = keys[0] || null;
32157
32728
  return {
@@ -32228,34 +32799,34 @@ function controlSecret2() {
32228
32799
  if (!secret2) throw new ResendControlError("Resend connections are not configured.", 503);
32229
32800
  return secret2;
32230
32801
  }
32231
- function isRecord3(value) {
32802
+ function isRecord5(value) {
32232
32803
  return !!value && typeof value === "object" && !Array.isArray(value);
32233
32804
  }
32234
32805
  function unwrapData2(value) {
32235
- return isRecord3(value) && isRecord3(value.data) ? value.data : value;
32806
+ return isRecord5(value) && isRecord5(value.data) ? value.data : value;
32236
32807
  }
32237
- function cleanString2(value, max = 300) {
32808
+ function cleanString4(value, max = 300) {
32238
32809
  if (typeof value !== "string") return null;
32239
32810
  const trimmed = value.trim();
32240
32811
  return trimmed ? trimmed.slice(0, max) : null;
32241
32812
  }
32242
32813
  function firstString2(record, keys, max = 300) {
32243
32814
  for (const key of keys) {
32244
- const value = cleanString2(record[key], max);
32815
+ const value = cleanString4(record[key], max);
32245
32816
  if (value) return value;
32246
32817
  }
32247
32818
  return null;
32248
32819
  }
32249
32820
  function cleanTools2(value) {
32250
32821
  if (!Array.isArray(value)) return [];
32251
- return [...new Set(value.map((tool) => cleanString2(tool, 200)).filter((tool) => !!tool))].slice(0, 200);
32822
+ return [...new Set(value.map((tool) => cleanString4(tool, 200)).filter((tool) => !!tool))].slice(0, 200);
32252
32823
  }
32253
32824
  function cleanStringArray2(value, maxItems = 50, maxLength = 500) {
32254
32825
  if (!Array.isArray(value)) return [];
32255
- return [...new Set(value.map((item) => cleanString2(item, maxLength)).filter((item) => !!item))].slice(0, maxItems);
32826
+ return [...new Set(value.map((item) => cleanString4(item, maxLength)).filter((item) => !!item))].slice(0, maxItems);
32256
32827
  }
32257
32828
  function cleanHttpsUrl2(value) {
32258
- const candidate = cleanString2(value, 2e3);
32829
+ const candidate = cleanString4(value, 2e3);
32259
32830
  if (!candidate) return null;
32260
32831
  try {
32261
32832
  const url = new URL(candidate);
@@ -32273,7 +32844,7 @@ function cleanAuthorizationUrl(value) {
32273
32844
  function arrayFromPayload2(value, keys) {
32274
32845
  const unwrapped = unwrapData2(value);
32275
32846
  if (Array.isArray(unwrapped)) return unwrapped;
32276
- if (!isRecord3(unwrapped)) return [];
32847
+ if (!isRecord5(unwrapped)) return [];
32277
32848
  for (const key of keys) {
32278
32849
  if (Array.isArray(unwrapped[key])) return unwrapped[key];
32279
32850
  }
@@ -32300,7 +32871,7 @@ async function controlRequest2(path5, init, timeoutMs = 2e4) {
32300
32871
  body = {};
32301
32872
  }
32302
32873
  if (!response.ok) {
32303
- const upstream = isRecord3(body) ? cleanString2(body.error, 300) : null;
32874
+ const upstream = isRecord5(body) ? cleanString4(body.error, 300) : null;
32304
32875
  throw new ResendControlError(upstream || `Resend connection control failed (${response.status}).`);
32305
32876
  }
32306
32877
  return body;
@@ -32310,7 +32881,7 @@ async function getResendCatalog() {
32310
32881
  const rows = arrayFromPayload2(body, ["providers", "catalog", "integrations", "services"]);
32311
32882
  const result = [];
32312
32883
  for (const row of rows) {
32313
- if (!isRecord3(row)) continue;
32884
+ if (!isRecord5(row)) continue;
32314
32885
  const id = firstString2(row, ["providerConfigKey", "provider_config_key", "id"]);
32315
32886
  if (id !== RESEND_PROVIDER_CONFIG_KEY) continue;
32316
32887
  const safeDefaultAllowedTools = cleanTools2(
@@ -32346,7 +32917,7 @@ async function getResendConnections(identity) {
32346
32917
  const rows = arrayFromPayload2(body, ["connections"]);
32347
32918
  const result = [];
32348
32919
  for (const row of rows) {
32349
- if (!isRecord3(row)) continue;
32920
+ if (!isRecord5(row)) continue;
32350
32921
  const connectionId = firstString2(row, ["connectionId", "connection_id", "id"]);
32351
32922
  const providerConfigKey = firstString2(row, ["providerConfigKey", "provider_config_key", "provider"]) || RESEND_PROVIDER_CONFIG_KEY;
32352
32923
  if (!connectionId || providerConfigKey !== RESEND_PROVIDER_CONFIG_KEY) continue;
@@ -32390,7 +32961,7 @@ async function getResendConnections(identity) {
32390
32961
  }
32391
32962
  function sanitizeConnectSession2(body) {
32392
32963
  const data = unwrapData2(body);
32393
- if (!isRecord3(data)) throw new ResendControlError("The Resend connection service returned an invalid connect session.");
32964
+ if (!isRecord5(data)) throw new ResendControlError("The Resend connection service returned an invalid connect session.");
32394
32965
  const connectLink = cleanAuthorizationUrl(data.authorizationUrl ?? data.authorization_url ?? data.connectLink ?? data.connect_link);
32395
32966
  if (!connectLink) throw new ResendControlError("The Resend connection service did not return a trusted authorization link.");
32396
32967
  return {
@@ -32431,7 +33002,7 @@ async function setResendActionsEnabled(identity, connectionId, enabled) {
32431
33002
  body: JSON.stringify({ identity, connectionId, enabled })
32432
33003
  });
32433
33004
  const data = unwrapData2(body);
32434
- if (!isRecord3(data) || !isRecord3(data.connection)) return enabled;
33005
+ if (!isRecord5(data) || !isRecord5(data.connection)) return enabled;
32435
33006
  return data.connection.actionsEnabled === true || data.connection.actions_enabled === true;
32436
33007
  }
32437
33008
  async function callResendRead(identity, connectionId, tool, args) {
@@ -32440,7 +33011,7 @@ async function callResendRead(identity, connectionId, tool, args) {
32440
33011
  body: JSON.stringify({ identity, connectionId, tool, input: args ?? {} })
32441
33012
  });
32442
33013
  const data = unwrapData2(body);
32443
- return isRecord3(data) ? data.result ?? data : data;
33014
+ return isRecord5(data) ? data.result ?? data : data;
32444
33015
  }
32445
33016
  async function callResendAction(identity, connectionId, tool, input) {
32446
33017
  const body = await controlRequest2("/api/internal/resend/actions/call", {
@@ -32448,7 +33019,7 @@ async function callResendAction(identity, connectionId, tool, input) {
32448
33019
  body: JSON.stringify({ identity, connectionId, tool, input })
32449
33020
  });
32450
33021
  const data = unwrapData2(body);
32451
- return isRecord3(data) ? data.result ?? data : data;
33022
+ return isRecord5(data) ? data.result ?? data : data;
32452
33023
  }
32453
33024
  async function describeResendTool(identity, connectionId, tool) {
32454
33025
  const body = await controlRequest2("/api/internal/resend/describe", {
@@ -32456,12 +33027,12 @@ async function describeResendTool(identity, connectionId, tool) {
32456
33027
  body: JSON.stringify({ identity, connectionId, tool })
32457
33028
  });
32458
33029
  const data = unwrapData2(body);
32459
- const rawTool = isRecord3(data) && isRecord3(data.tool) ? data.tool : data;
32460
- if (!isRecord3(rawTool)) throw new ResendControlError("Resend returned an invalid tool description.");
33030
+ const rawTool = isRecord5(data) && isRecord5(data.tool) ? data.tool : data;
33031
+ if (!isRecord5(rawTool)) throw new ResendControlError("Resend returned an invalid tool description.");
32461
33032
  const name = firstString2(rawTool, ["name"], 200);
32462
33033
  const classification = firstString2(rawTool, ["classification", "kind"], 20);
32463
33034
  const inputSchema = rawTool.inputSchema ?? rawTool.input_schema;
32464
- if (!name || name !== tool || classification !== "read" && classification !== "action" || !isRecord3(inputSchema)) {
33035
+ if (!name || name !== tool || classification !== "read" && classification !== "action" || !isRecord5(inputSchema)) {
32465
33036
  throw new ResendControlError("Resend returned an invalid tool description.");
32466
33037
  }
32467
33038
  return {
@@ -32478,10 +33049,10 @@ async function callResendExportPage(identity, input) {
32478
33049
  body: JSON.stringify({ identity, ...input })
32479
33050
  }, 9e4);
32480
33051
  const data = unwrapData2(body);
32481
- if (!isRecord3(data) || data.ok !== true || data.providerConfigKey !== RESEND_PROVIDER_CONFIG_KEY) {
33052
+ if (!isRecord5(data) || data.ok !== true || data.providerConfigKey !== RESEND_PROVIDER_CONFIG_KEY) {
32482
33053
  throw new ResendControlError("Resend export returned an invalid page response.");
32483
33054
  }
32484
- const dataset = cleanString2(data.dataset, 64);
33055
+ const dataset = cleanString4(data.dataset, 64);
32485
33056
  if (!dataset || dataset === "auto" || !CONNECTED_DATA_DATASETS.includes(dataset)) {
32486
33057
  throw new ResendControlError("Resend export returned invalid dataset metadata.");
32487
33058
  }
@@ -32489,7 +33060,7 @@ async function callResendExportPage(identity, input) {
32489
33060
  if (data.nextCursor !== void 0 && data.nextCursor !== null && typeof data.nextCursor !== "string") {
32490
33061
  throw new ResendControlError("Resend export returned an invalid continuation cursor.");
32491
33062
  }
32492
- const rawCounts = isRecord3(data.counts) ? data.counts : {};
33063
+ const rawCounts = isRecord5(data.counts) ? data.counts : {};
32493
33064
  const numberOrZero = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
32494
33065
  return {
32495
33066
  providerConfigKey: RESEND_PROVIDER_CONFIG_KEY,
@@ -33104,6 +33675,16 @@ app.get("/memory/note", auth2, async (c) => {
33104
33675
  return c.json({ ok: false, error: err instanceof Error ? err.message : "could not load note" }, 502);
33105
33676
  }
33106
33677
  });
33678
+ app.get("/memory/universe", auth2, async (c) => {
33679
+ const user = c.get("user");
33680
+ const includeConnections = c.req.query("includeConnections") === "1";
33681
+ try {
33682
+ const graph = await dbUniverse(resolveMemoryIdentity(user), includeConnections);
33683
+ return c.json({ ok: true, ...graph });
33684
+ } catch (err) {
33685
+ return c.json({ ok: false, error: err instanceof Error ? err.message : "could not build universe" }, 502);
33686
+ }
33687
+ });
33107
33688
  app.put("/memory/note", auth2, async (c) => {
33108
33689
  const { key, error } = await getOrCreateUserMemoryKey(c.get("user"));
33109
33690
  if (!key) return c.json({ error: error ?? "memory unavailable" }, 502);
@@ -33845,7 +34426,18 @@ app.post("/schedule-connections/actions/export", auth2, requireIntegrationsTier,
33845
34426
  const body = await c.req.json().catch(() => ({}));
33846
34427
  const connectionId = providerConfigKeyFrom(body.connectionId);
33847
34428
  if (!connectionId) return c.json({ ok: false, error: "connectionId is required." }, 400);
33848
- const requestedDataset = typeof body.dataset === "string" ? body.dataset.trim() : "auto";
34429
+ const channelId = typeof body.channelId === "string" ? body.channelId.trim() : "";
34430
+ if (body.channelId !== void 0 && !channelId) {
34431
+ return c.json({ ok: false, error: "channelId must be a non-empty Slack conversation ID." }, 400);
34432
+ }
34433
+ if (body.includeThreads !== void 0 && typeof body.includeThreads !== "boolean") {
34434
+ return c.json({ ok: false, error: "includeThreads must be a boolean when provided." }, 400);
34435
+ }
34436
+ if (body.allTime !== void 0 && typeof body.allTime !== "boolean") {
34437
+ return c.json({ ok: false, error: "allTime must be a boolean when provided." }, 400);
34438
+ }
34439
+ const rawRequestedDataset = typeof body.dataset === "string" ? body.dataset.trim() : "auto";
34440
+ const requestedDataset = rawRequestedDataset === "auto" && channelId ? "slack_channel_messages" : rawRequestedDataset;
33849
34441
  const datasets = [...CONNECTED_DATA_DATASETS];
33850
34442
  if (!datasets.includes(requestedDataset)) {
33851
34443
  return c.json({ ok: false, error: `dataset must be one of: ${datasets.join(", ")}.` }, 400);
@@ -33868,21 +34460,37 @@ app.post("/schedule-connections/actions/export", auth2, requireIntegrationsTier,
33868
34460
  if (body.continuation !== void 0 && !continuation) {
33869
34461
  return c.json({ ok: false, error: "continuation must be the complete object returned by a prior export." }, 400);
33870
34462
  }
34463
+ if (body.allTime === true && requestedDataset !== "slack_channel_messages") {
34464
+ return c.json({ ok: false, error: "allTime is supported only for Slack channel exports." }, 400);
34465
+ }
34466
+ if (body.allTime === true && (body.from !== void 0 || body.lastDays !== void 0 || continuation)) {
34467
+ return c.json({ ok: false, error: "Do not pass allTime with from, lastDays, or continuation." }, 400);
34468
+ }
34469
+ if (requestedDataset === "slack_channel_messages" && !channelId && !continuation) {
34470
+ return c.json({ ok: false, error: "channelId is required for a Slack channel export." }, 400);
34471
+ }
33871
34472
  try {
33872
34473
  const useResend = await isResendConnection(user.email, connectionId);
33873
34474
  const resolved = resolveConnectedDataExportRequest({
33874
34475
  requestedDataset,
33875
- from: typeof body.from === "string" ? body.from : void 0,
34476
+ from: body.allTime === true ? "1970-01-01T00:00:00.000Z" : typeof body.from === "string" ? body.from : void 0,
33876
34477
  to: typeof body.to === "string" ? body.to : void 0,
33877
34478
  lastDays,
33878
34479
  cursor: typeof body.cursor === "string" ? body.cursor : void 0,
33879
- continuation
34480
+ continuation,
34481
+ scope: channelId ? {
34482
+ slack: {
34483
+ channelId,
34484
+ includeThreads: body.includeThreads !== false
34485
+ }
34486
+ } : void 0
33880
34487
  });
33881
34488
  const result = await collectConnectedDataExport({
33882
34489
  ownerId: sha256Hex(user.api_key).slice(0, 24),
33883
34490
  connectionId,
33884
34491
  dataset: resolved.dataset,
33885
34492
  range: resolved.range,
34493
+ ...resolved.scope ? { scope: resolved.scope } : {},
33886
34494
  maxItems,
33887
34495
  forceArtifact: body.delivery === "artifact",
33888
34496
  ...resolved.cursor ? { cursor: resolved.cursor } : {},
@@ -35713,4 +36321,4 @@ app.get("/blog/:slug/", (c) => {
35713
36321
  export {
35714
36322
  app
35715
36323
  };
35716
- //# sourceMappingURL=server-W76RUYD3.js.map
36324
+ //# sourceMappingURL=server-2JJPCZH4.js.map