mcp-scraper 0.40.3 → 0.41.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -67,7 +67,7 @@ import {
67
67
  renewConnectedDataArtifactDownload,
68
68
  renewDirectoryArtifactDownload,
69
69
  transcribeMediaUrl
70
- } from "./chunk-6SZ52BQ5.js";
70
+ } from "./chunk-VE5T5UPM.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-R3EME5HH.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,377 @@ 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 oldest = slackBoundaryTimestamp(input.from, -1);
30827
+ const latest = slackBoundaryTimestamp(input.to, 0);
30828
+ const result = await callSlackToolWithRetry(dependencies, {
30829
+ tool: "get-conversation-history",
30830
+ input: {
30831
+ channel_id: channelId,
30832
+ limit: pageSize,
30833
+ oldest,
30834
+ latest,
30835
+ inclusive: false,
30836
+ ...state.historyCursor ? { cursor: state.historyCursor } : {}
30837
+ },
30838
+ requestId: `slack-export:${input.connectionId}:${channelId}:history:${oldest}:${latest}:${pageSize}:${state.historyCursor ?? "start"}`
30839
+ });
30840
+ const messages = messagesFromToolResult(result).slice(0, pageSize);
30841
+ const historyCursor = nextCursorFromToolResult(result);
30842
+ const records2 = messages.map((message) => normalizeSlackMessage(message, channelId, null));
30843
+ const threadIds = includeThreads ? [...new Set(messages.flatMap((message) => {
30844
+ const replyCount = typeof message.reply_count === "number" ? message.reply_count : 0;
30845
+ const timestamp = cleanString2(message.ts, 64);
30846
+ return replyCount > 0 && timestamp ? [timestamp] : [];
30847
+ }))].slice(0, MAX_PENDING_THREADS) : [];
30848
+ if (threadIds.length > 0) {
30849
+ return {
30850
+ providerConfigKey: "slack",
30851
+ dataset: "slack_channel_messages",
30852
+ records: records2,
30853
+ nextCursor: encodeCursor({
30854
+ version: 1,
30855
+ phase: "threads",
30856
+ channelId,
30857
+ includeThreads: true,
30858
+ historyCursor,
30859
+ historyMessageTs: messages.flatMap((message) => cleanString2(message.ts, 64) ?? []),
30860
+ threads: threadIds.map((threadTs) => ({ threadTs, cursor: null }))
30861
+ }),
30862
+ complete: false,
30863
+ counts: { listed: messages.length, exported: records2.length, failed: 0 },
30864
+ 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."] : [],
30865
+ untrustedContent: true
30866
+ };
30867
+ }
30868
+ return {
30869
+ providerConfigKey: "slack",
30870
+ dataset: "slack_channel_messages",
30871
+ records: records2,
30872
+ nextCursor: historyCursor ? encodeCursor({ ...state, historyCursor }) : null,
30873
+ complete: historyCursor === null,
30874
+ counts: { listed: messages.length, exported: records2.length, failed: 0 },
30875
+ warnings: [],
30876
+ untrustedContent: true
30877
+ };
30878
+ }
30879
+ const batchSize = Math.min(THREAD_CONCURRENCY, state.threads.length, pageSize);
30880
+ const batch = state.threads.slice(0, batchSize);
30881
+ const perThreadLimit = Math.max(1, Math.min(100, Math.floor(pageSize / batchSize)));
30882
+ const results = await Promise.all(batch.map((thread) => callSlackToolWithRetry(dependencies, {
30883
+ tool: "get-thread-replies",
30884
+ input: {
30885
+ channel_id: channelId,
30886
+ thread_ts: thread.threadTs,
30887
+ limit: perThreadLimit,
30888
+ ...thread.cursor ? { cursor: thread.cursor } : {}
30889
+ },
30890
+ requestId: `slack-export:${input.connectionId}:${channelId}:thread:${thread.threadTs}:${perThreadLimit}:${thread.cursor ?? "start"}`
30891
+ })));
30892
+ const records = [];
30893
+ const stillPending = [];
30894
+ let listed = 0;
30895
+ for (let index = 0; index < results.length; index += 1) {
30896
+ const thread = batch[index];
30897
+ const messages = messagesFromToolResult(results[index]);
30898
+ listed += messages.length;
30899
+ records.push(...messages.filter((message) => {
30900
+ const timestamp = cleanString2(message.ts, 64);
30901
+ return timestamp !== thread.threadTs && !state.historyMessageTs.includes(timestamp ?? "");
30902
+ }).map((message) => normalizeSlackMessage(message, channelId, thread.threadTs)));
30903
+ const cursor = nextCursorFromToolResult(results[index]);
30904
+ if (cursor) stillPending.push({ threadTs: thread.threadTs, cursor });
30905
+ }
30906
+ const nextState = {
30907
+ ...state,
30908
+ threads: [...stillPending, ...state.threads.slice(batchSize)]
30909
+ };
30910
+ const next = nextAfterThreads(nextState);
30911
+ return {
30912
+ providerConfigKey: "slack",
30913
+ dataset: "slack_channel_messages",
30914
+ records,
30915
+ nextCursor: next.nextCursor,
30916
+ complete: next.complete,
30917
+ counts: { listed, exported: records.length, failed: 0 },
30918
+ warnings: [],
30919
+ untrustedContent: true
30920
+ };
30921
+ }
30922
+
30383
30923
  // src/api/main-nango-transport.ts
30384
30924
  import { createHash as createHash13, randomUUID as randomUUID16 } from "crypto";
30385
30925
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
@@ -30656,12 +31196,14 @@ var MainNangoTransportError = class extends Error {
30656
31196
  status;
30657
31197
  code;
30658
31198
  retryable;
30659
- constructor(message, status, code, retryable = status === 429 || status >= 500) {
31199
+ retryAfterMs;
31200
+ constructor(message, status, code, retryable = status === 429 || status >= 500, retryAfterMs = null) {
30660
31201
  super(message);
30661
31202
  this.name = "MainNangoTransportError";
30662
31203
  this.status = status;
30663
31204
  this.code = code;
30664
31205
  this.retryable = retryable;
31206
+ this.retryAfterMs = retryAfterMs;
30665
31207
  }
30666
31208
  };
30667
31209
  function nangoClientErrorText(error) {
@@ -30673,6 +31215,13 @@ function nangoClientErrorText(error) {
30673
31215
  return "";
30674
31216
  }
30675
31217
  }
31218
+ function retryAfterMsFromText(value) {
31219
+ const milliseconds = value.match(/retry[_ -]?after[_ -]?ms[^0-9]{0,20}(\d{1,7})/i);
31220
+ if (milliseconds) return Math.min(Math.max(Number(milliseconds[1]), 1e3), 6e4);
31221
+ const seconds = value.match(/retry[_ -]?after[^0-9]{0,20}(\d{1,4})/i);
31222
+ if (seconds) return Math.min(Math.max(Number(seconds[1]), 1) * 1e3, 6e4);
31223
+ return null;
31224
+ }
30676
31225
  function normalizeNangoClientError(error) {
30677
31226
  if (error instanceof MainNangoTransportError) return error;
30678
31227
  const detail = nangoClientErrorText(error);
@@ -30693,7 +31242,13 @@ function normalizeNangoClientError(error) {
30693
31242
  );
30694
31243
  }
30695
31244
  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);
31245
+ return new MainNangoTransportError(
31246
+ "The connected provider is temporarily rate limited.",
31247
+ 429,
31248
+ "upstream_rate_limited",
31249
+ true,
31250
+ retryAfterMsFromText(detail)
31251
+ );
30697
31252
  }
30698
31253
  if (/UNAUTHENTICATED|invalid_grant|status["']?\s*:\s*401/i.test(detail)) {
30699
31254
  return new MainNangoTransportError("The service connection requires reconnection.", 409, "connection_inactive", false);
@@ -30758,7 +31313,9 @@ async function nangoRequest(path5, options = {}) {
30758
31313
  throw new MainNangoTransportError(
30759
31314
  status === 429 ? "The connected provider is temporarily rate limited." : "Nango request failed.",
30760
31315
  status,
30761
- status === 429 ? "upstream_rate_limited" : status === 404 ? "connection_not_found" : "connection_transport_unavailable"
31316
+ status === 429 ? "upstream_rate_limited" : status === 404 ? "connection_not_found" : "connection_transport_unavailable",
31317
+ status === 429 || status >= 500,
31318
+ status === 429 ? retryAfterMsFromText(`retry-after: ${response.headers.get("retry-after") ?? ""}`) : null
30762
31319
  );
30763
31320
  }
30764
31321
  return payload;
@@ -31206,37 +31763,37 @@ function controlSecret() {
31206
31763
  if (!secret2) throw new NangoControlError("Scheduled service connections are not configured.", 503);
31207
31764
  return secret2;
31208
31765
  }
31209
- function isRecord2(value) {
31766
+ function isRecord4(value) {
31210
31767
  return !!value && typeof value === "object" && !Array.isArray(value);
31211
31768
  }
31212
31769
  function unwrapData(value) {
31213
- if (isRecord2(value) && isRecord2(value.data)) return value.data;
31770
+ if (isRecord4(value) && isRecord4(value.data)) return value.data;
31214
31771
  return value;
31215
31772
  }
31216
- function cleanString(value, max = 300) {
31773
+ function cleanString3(value, max = 300) {
31217
31774
  if (typeof value !== "string") return null;
31218
31775
  const trimmed = value.trim();
31219
31776
  return trimmed ? trimmed.slice(0, max) : null;
31220
31777
  }
31221
31778
  function firstString(record, keys, max = 300) {
31222
31779
  for (const key of keys) {
31223
- const value = cleanString(record[key], max);
31780
+ const value = cleanString3(record[key], max);
31224
31781
  if (value) return value;
31225
31782
  }
31226
31783
  return null;
31227
31784
  }
31228
31785
  function cleanTools(value) {
31229
31786
  if (!Array.isArray(value)) return [];
31230
- const tools = value.map((tool) => cleanString(tool, 200)).filter((tool) => !!tool);
31787
+ const tools = value.map((tool) => cleanString3(tool, 200)).filter((tool) => !!tool);
31231
31788
  return [...new Set(tools)].slice(0, 200);
31232
31789
  }
31233
31790
  function cleanStringArray(value, maxItems = 20, maxLength = 100) {
31234
31791
  if (!Array.isArray(value)) return [];
31235
- const items = value.map((item) => cleanString(item, maxLength)).filter((item) => !!item);
31792
+ const items = value.map((item) => cleanString3(item, maxLength)).filter((item) => !!item);
31236
31793
  return [...new Set(items)].slice(0, maxItems);
31237
31794
  }
31238
31795
  function cleanHttpsUrl(value) {
31239
- const candidate = cleanString(value, 2e3);
31796
+ const candidate = cleanString3(value, 2e3);
31240
31797
  if (!candidate) return null;
31241
31798
  try {
31242
31799
  const url = new URL(candidate);
@@ -31248,7 +31805,7 @@ function cleanHttpsUrl(value) {
31248
31805
  function arrayFromPayload(value, keys) {
31249
31806
  const unwrapped = unwrapData(value);
31250
31807
  if (Array.isArray(unwrapped)) return unwrapped;
31251
- if (!isRecord2(unwrapped)) return [];
31808
+ if (!isRecord4(unwrapped)) return [];
31252
31809
  for (const key of keys) {
31253
31810
  if (Array.isArray(unwrapped[key])) return unwrapped[key];
31254
31811
  }
@@ -31329,7 +31886,7 @@ function defaultControlErrorMessage(status) {
31329
31886
  function safeControlErrorPayload(body, responseStatus) {
31330
31887
  const status = controlErrorStatus(responseStatus);
31331
31888
  const unwrapped = unwrapData(body);
31332
- const record = isRecord2(unwrapped) ? unwrapped : isRecord2(body) ? body : null;
31889
+ const record = isRecord4(unwrapped) ? unwrapped : isRecord4(body) ? body : null;
31333
31890
  const candidateCode = record ? firstString(record, ["code", "errorCode", "error_code"], 100) : null;
31334
31891
  const normalizedCandidateCode = candidateCode ? CONTROL_ERROR_CODE_ALIASES.get(candidateCode) ?? candidateCode : null;
31335
31892
  const code = normalizedCandidateCode && SAFE_CONTROL_ERROR_CODES.has(normalizedCandidateCode) ? normalizedCandidateCode : defaultControlErrorCode(status);
@@ -31378,7 +31935,7 @@ function sanitizeScheduleConnectionSelections(value) {
31378
31935
  const selections = [];
31379
31936
  const seen = /* @__PURE__ */ new Set();
31380
31937
  for (const item of value) {
31381
- if (!isRecord2(item)) throw new ScheduleConnectionValidationError("Each service connection must be an object.");
31938
+ if (!isRecord4(item)) throw new ScheduleConnectionValidationError("Each service connection must be an object.");
31382
31939
  const connectionId = firstString(item, ["connectionId", "connection_id"]);
31383
31940
  const providerConfigKey = firstString(item, ["providerConfigKey", "provider_config_key", "integrationId", "integration_id"]);
31384
31941
  const allowedTools = cleanTools(item.allowedTools ?? item.allowed_tools ?? item.allowedCapabilities ?? item.allowed_capabilities);
@@ -31398,7 +31955,7 @@ async function getNangoCatalog() {
31398
31955
  const rows = arrayFromPayload(body, ["providers", "catalog", "integrations", "services"]);
31399
31956
  const result = [];
31400
31957
  for (const row of rows) {
31401
- if (!isRecord2(row)) continue;
31958
+ if (!isRecord4(row)) continue;
31402
31959
  const providerConfigKey = firstString(row, ["providerConfigKey", "provider_config_key", "integrationId", "integration_id", "id"]);
31403
31960
  if (!providerConfigKey) continue;
31404
31961
  const provider = firstString(row, ["provider"]);
@@ -31418,18 +31975,18 @@ async function getNangoCatalog() {
31418
31975
  ).filter((tool) => !disabledTools?.has(tool));
31419
31976
  const requiredPermissionsByTool = {};
31420
31977
  const rawRequiredPermissions = row.requiredPermissionsByTool ?? row.required_permissions_by_tool;
31421
- if (isRecord2(rawRequiredPermissions)) {
31978
+ if (isRecord4(rawRequiredPermissions)) {
31422
31979
  for (const [tool, permissions] of Object.entries(rawRequiredPermissions).slice(0, 500)) {
31423
- const cleanTool = cleanString(tool, 200);
31980
+ const cleanTool = cleanString3(tool, 200);
31424
31981
  if (!cleanTool || !safeDefaultAllowedTools.includes(cleanTool) && !actionTools.includes(cleanTool)) continue;
31425
31982
  requiredPermissionsByTool[cleanTool] = cleanStringArray(permissions, 32, 200);
31426
31983
  }
31427
31984
  }
31428
31985
  const requiredFeaturesByTool = {};
31429
31986
  const rawRequiredFeatures = row.requiredFeaturesByTool ?? row.required_features_by_tool;
31430
- if (isRecord2(rawRequiredFeatures)) {
31987
+ if (isRecord4(rawRequiredFeatures)) {
31431
31988
  for (const [tool, features] of Object.entries(rawRequiredFeatures).slice(0, 500)) {
31432
- const cleanTool = cleanString(tool, 200);
31989
+ const cleanTool = cleanString3(tool, 200);
31433
31990
  if (!cleanTool) continue;
31434
31991
  requiredFeaturesByTool[cleanTool] = cleanStringArray(features, 32, 200);
31435
31992
  }
@@ -31554,7 +32111,7 @@ async function getNangoConnections(identity, options = {}) {
31554
32111
  const rows = arrayFromPayload(body, ["connections"]);
31555
32112
  const result = [];
31556
32113
  for (const row of rows) {
31557
- if (!isRecord2(row)) continue;
32114
+ if (!isRecord4(row)) continue;
31558
32115
  const connectionId = firstString(row, ["connectionId", "connection_id", "id"]);
31559
32116
  const providerConfigKey = firstString(row, ["providerConfigKey", "provider_config_key", "integrationId", "integration_id", "provider"]);
31560
32117
  if (!connectionId || !providerConfigKey) continue;
@@ -31573,8 +32130,8 @@ async function getNangoConnections(identity, options = {}) {
31573
32130
  const rawToolCapabilities = row.toolCapabilities ?? row.tool_capabilities;
31574
32131
  if (Array.isArray(rawToolCapabilities)) {
31575
32132
  for (const value of rawToolCapabilities.slice(0, 500)) {
31576
- if (!isRecord2(value)) continue;
31577
- const name = cleanString(value.name, 200);
32133
+ if (!isRecord4(value)) continue;
32134
+ const name = cleanString3(value.name, 200);
31578
32135
  const classification = value.classification;
31579
32136
  const blockedValue = Object.prototype.hasOwnProperty.call(value, "blockedReason") ? value.blockedReason : value.blocked_reason;
31580
32137
  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 +32198,7 @@ async function deleteNangoConnection(identity, connectionId) {
31641
32198
  body: JSON.stringify({ identity, connectionId })
31642
32199
  });
31643
32200
  const data = unwrapData(body);
31644
- if (!isRecord2(data) || data.ok !== true) {
32201
+ if (!isRecord4(data) || data.ok !== true) {
31645
32202
  throw new NangoControlError("The connection service returned an invalid deletion response.");
31646
32203
  }
31647
32204
  const remaining = Number(data.remainingConnections);
@@ -31649,7 +32206,7 @@ async function deleteNangoConnection(identity, connectionId) {
31649
32206
  }
31650
32207
  function sanitizeConnectSession(body) {
31651
32208
  const data = unwrapData(body);
31652
- if (!isRecord2(data)) throw new NangoControlError("The connection service returned an invalid connect session.");
32209
+ if (!isRecord4(data)) throw new NangoControlError("The connection service returned an invalid connect session.");
31653
32210
  const connectLink = firstString(data, ["connectLink", "connect_link"], 2e3);
31654
32211
  if (!connectLink) throw new NangoControlError("The connection service did not return a connect link.");
31655
32212
  return {
@@ -31691,14 +32248,14 @@ async function createNangoReconnectSession(identity, connectionId) {
31691
32248
  function sanitizeBindingRows(body, defaultScheduleActionId) {
31692
32249
  const data = unwrapData(body);
31693
32250
  const flattened = [];
31694
- if (isRecord2(data) && isRecord2(data.bindings) && !Array.isArray(data.bindings)) {
32251
+ if (isRecord4(data) && isRecord4(data.bindings) && !Array.isArray(data.bindings)) {
31695
32252
  for (const [scheduleActionId, value] of Object.entries(data.bindings)) {
31696
32253
  if (Array.isArray(value)) value.forEach((row) => flattened.push({ row, scheduleActionId }));
31697
32254
  }
31698
32255
  } else {
31699
32256
  const rows = arrayFromPayload(data, ["bindings", "connections"]);
31700
32257
  for (const row of rows) {
31701
- if (isRecord2(row) && Array.isArray(row.connections)) {
32258
+ if (isRecord4(row) && Array.isArray(row.connections)) {
31702
32259
  const scheduleActionId = firstString(row, ["scheduleActionId", "schedule_action_id", "scheduleId", "schedule_id"]) || defaultScheduleActionId;
31703
32260
  row.connections.forEach((connection) => flattened.push({ row: connection, scheduleActionId }));
31704
32261
  } else {
@@ -31708,7 +32265,7 @@ function sanitizeBindingRows(body, defaultScheduleActionId) {
31708
32265
  }
31709
32266
  const result = [];
31710
32267
  for (const item of flattened) {
31711
- if (!isRecord2(item.row)) continue;
32268
+ if (!isRecord4(item.row)) continue;
31712
32269
  const connectionId = firstString(item.row, ["connectionId", "connection_id"]);
31713
32270
  const providerConfigKey = firstString(item.row, ["providerConfigKey", "provider_config_key", "integrationId", "integration_id", "provider"]);
31714
32271
  if (!connectionId || !providerConfigKey) continue;
@@ -31776,7 +32333,7 @@ async function setScheduleConnectionActionsEnabled(identity, connectionId, enabl
31776
32333
  body: JSON.stringify({ identity, connectionId, enabled })
31777
32334
  });
31778
32335
  const data = unwrapData(body);
31779
- if (!isRecord2(data) || !isRecord2(data.connection)) return enabled;
32336
+ if (!isRecord4(data) || !isRecord4(data.connection)) return enabled;
31780
32337
  return data.connection.actionsEnabled === true;
31781
32338
  }
31782
32339
  async function callScheduleConnectionAction(identity, connectionId, input, tool, idempotencyKey2) {
@@ -31826,7 +32383,7 @@ async function callScheduleConnectionAction(identity, connectionId, input, tool,
31826
32383
  body: JSON.stringify({ identity, connectionId, ...tool ? { tool } : {}, input })
31827
32384
  });
31828
32385
  const data = unwrapData(body);
31829
- return isRecord2(data) ? data.result ?? data : data;
32386
+ return isRecord4(data) ? data.result ?? data : data;
31830
32387
  }
31831
32388
  async function callScheduleConnectionRead(identity, connectionId, tool, args, idempotencyKey2) {
31832
32389
  if (mainOwnsIntegrations()) {
@@ -31849,7 +32406,7 @@ async function callScheduleConnectionRead(identity, connectionId, tool, args, id
31849
32406
  body: JSON.stringify({ identity, connectionId, tool, args: args ?? {} })
31850
32407
  });
31851
32408
  const data = unwrapData(body);
31852
- return isRecord2(data) ? data.result ?? data : data;
32409
+ return isRecord4(data) ? data.result ?? data : data;
31853
32410
  }
31854
32411
  async function testNangoConnection(identity, connectionId) {
31855
32412
  if (!mainOwnsIntegrations()) {
@@ -31869,20 +32426,20 @@ async function testNangoConnection(identity, connectionId) {
31869
32426
  }
31870
32427
  }
31871
32428
  function sanitizeToolSchema(value) {
31872
- if (!isRecord2(value) || value.type !== "object") return null;
32429
+ if (!isRecord4(value) || value.type !== "object") return null;
31873
32430
  try {
31874
32431
  const serialized = JSON.stringify(value);
31875
32432
  if (Buffer.byteLength(serialized, "utf8") > 256 * 1024) return null;
31876
32433
  const cloned = JSON.parse(serialized);
31877
- return isRecord2(cloned) ? cloned : null;
32434
+ return isRecord4(cloned) ? cloned : null;
31878
32435
  } catch {
31879
32436
  return null;
31880
32437
  }
31881
32438
  }
31882
32439
  function sanitizeToolAnnotations(value) {
31883
- if (!isRecord2(value)) return void 0;
32440
+ if (!isRecord4(value)) return void 0;
31884
32441
  const annotations = {};
31885
- const title = cleanString(value.title, 200);
32442
+ const title = cleanString3(value.title, 200);
31886
32443
  if (title) annotations.title = title;
31887
32444
  for (const key of ["readOnlyHint", "destructiveHint", "idempotentHint", "openWorldHint"]) {
31888
32445
  if (typeof value[key] === "boolean") annotations[key] = value[key];
@@ -31890,7 +32447,7 @@ function sanitizeToolAnnotations(value) {
31890
32447
  return Object.keys(annotations).length > 0 ? annotations : void 0;
31891
32448
  }
31892
32449
  function sanitizeToolIconSource(value) {
31893
- const candidate = cleanString(value, 1e4);
32450
+ const candidate = cleanString3(value, 1e4);
31894
32451
  if (!candidate) return null;
31895
32452
  if (/^data:image\/(?:png|jpeg|webp|gif|svg\+xml);base64,[a-z0-9+/=]+$/i.test(candidate)) return candidate;
31896
32453
  return cleanHttpsUrl(candidate);
@@ -31899,10 +32456,10 @@ function sanitizeToolIcons(value) {
31899
32456
  if (!Array.isArray(value)) return void 0;
31900
32457
  const icons = [];
31901
32458
  for (const item of value.slice(0, 16)) {
31902
- if (!isRecord2(item)) continue;
32459
+ if (!isRecord4(item)) continue;
31903
32460
  const src = sanitizeToolIconSource(item.src);
31904
32461
  if (!src) continue;
31905
- const mimeType = cleanString(item.mimeType ?? item.mime_type, 100);
32462
+ const mimeType = cleanString3(item.mimeType ?? item.mime_type, 100);
31906
32463
  const sizes = cleanStringArray(item.sizes, 16, 32);
31907
32464
  const theme = item.theme === "light" || item.theme === "dark" ? item.theme : void 0;
31908
32465
  icons.push({
@@ -31916,7 +32473,7 @@ function sanitizeToolIcons(value) {
31916
32473
  }
31917
32474
  function canonicalJson2(value) {
31918
32475
  if (Array.isArray(value)) return `[${value.map(canonicalJson2).join(",")}]`;
31919
- if (isRecord2(value)) {
32476
+ if (isRecord4(value)) {
31920
32477
  return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonicalJson2(value[key])}`).join(",")}}`;
31921
32478
  }
31922
32479
  return JSON.stringify(value);
@@ -31947,8 +32504,8 @@ async function describeNangoTool(identity, connectionId, tool, fresh) {
31947
32504
  }
31948
32505
  function sanitizeNangoToolDescription(body, tool) {
31949
32506
  const data = unwrapData(body);
31950
- const rawTool = isRecord2(data) && isRecord2(data.tool) ? data.tool : data;
31951
- if (!isRecord2(rawTool)) {
32507
+ const rawTool = isRecord4(data) && isRecord4(data.tool) ? data.tool : data;
32508
+ if (!isRecord4(rawTool)) {
31952
32509
  throw new NangoControlError(
31953
32510
  "The connection service returned an invalid live tool description.",
31954
32511
  502,
@@ -31961,13 +32518,13 @@ function sanitizeNangoToolDescription(body, tool) {
31961
32518
  serializedBytes = Buffer.byteLength(JSON.stringify(rawTool), "utf8");
31962
32519
  } catch {
31963
32520
  }
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);
32521
+ const name = cleanString3(rawTool.name, 200);
32522
+ const classification = cleanString3(rawTool.classification, 20);
32523
+ const transport = cleanString3(rawTool.transport, 20);
32524
+ const providerConfigKey = cleanString3(rawTool.providerConfigKey ?? rawTool.provider_config_key, 200);
32525
+ const schemaSource = cleanString3(rawTool.schemaSource ?? rawTool.schema_source, 50);
32526
+ const upstreamSchemaHash = cleanString3(rawTool.schemaHash ?? rawTool.schema_hash, 200);
32527
+ const fetchedAt = cleanString3(rawTool.fetchedAt ?? rawTool.fetched_at, 100);
31971
32528
  const inputSchema = sanitizeToolSchema(rawTool.inputSchema ?? rawTool.input_schema);
31972
32529
  const outputSchemaValue = rawTool.outputSchema ?? rawTool.output_schema;
31973
32530
  const outputSchema = outputSchemaValue === void 0 ? void 0 : sanitizeToolSchema(outputSchemaValue);
@@ -31981,11 +32538,11 @@ function sanitizeNangoToolDescription(body, tool) {
31981
32538
  true
31982
32539
  );
31983
32540
  }
31984
- const protocolVersion = rawTool.protocolVersion === null || rawTool.protocol_version === null ? null : cleanString(rawTool.protocolVersion ?? rawTool.protocol_version, 100);
32541
+ const protocolVersion = rawTool.protocolVersion === null || rawTool.protocol_version === null ? null : cleanString3(rawTool.protocolVersion ?? rawTool.protocol_version, 100);
31985
32542
  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);
32543
+ const taskSupport = isRecord4(executionValue) && (executionValue.taskSupport === "forbidden" || executionValue.taskSupport === "optional" || executionValue.taskSupport === "required") ? executionValue.taskSupport : null;
32544
+ const title = cleanString3(rawTool.title, 200);
32545
+ const description = cleanString3(rawTool.description, 12e3);
31989
32546
  const annotations = sanitizeToolAnnotations(rawTool.annotations);
31990
32547
  const icons = sanitizeToolIcons(rawTool.icons);
31991
32548
  const execution = taskSupport ? { taskSupport } : void 0;
@@ -32028,10 +32585,13 @@ function sanitizeNangoToolDescription(body, tool) {
32028
32585
  };
32029
32586
  }
32030
32587
  async function callScheduleConnectionExportPage(identity, input) {
32031
- if (mainOwnsIntegrations() && input.dataset === "search_console_performance") {
32588
+ if (mainOwnsIntegrations() && (input.dataset === "search_console_performance" || input.dataset === "slack_channel_messages")) {
32032
32589
  try {
32033
32590
  return await callMainOwnedExportPage(identity, input);
32034
32591
  } catch (error) {
32592
+ if (error instanceof Error && /^slack_export_(?:channel_required|cursor_invalid|range_invalid)$/.test(error.message)) {
32593
+ throw new NangoControlError("The Slack channel export request or continuation is invalid.", 400, "invalid_request", false);
32594
+ }
32035
32595
  throw asNangoControlError(error);
32036
32596
  }
32037
32597
  }
@@ -32040,11 +32600,11 @@ async function callScheduleConnectionExportPage(identity, input) {
32040
32600
  body: JSON.stringify({ identity, ...input })
32041
32601
  }, 9e4);
32042
32602
  const data = unwrapData(body);
32043
- if (!isRecord2(data) || data.ok !== true) {
32603
+ if (!isRecord4(data) || data.ok !== true) {
32044
32604
  throw new NangoControlError("Connected-data export returned an invalid page response.");
32045
32605
  }
32046
- const providerConfigKey = cleanString(data.providerConfigKey, 128);
32047
- const dataset = cleanString(data.dataset, 64);
32606
+ const providerConfigKey = cleanString3(data.providerConfigKey, 128);
32607
+ const dataset = cleanString3(data.dataset, 64);
32048
32608
  if (!providerConfigKey || dataset === "auto" || !CONNECTED_DATA_DATASETS.includes(dataset)) {
32049
32609
  throw new NangoControlError("Connected-data export returned invalid provider metadata.");
32050
32610
  }
@@ -32054,7 +32614,7 @@ async function callScheduleConnectionExportPage(identity, input) {
32054
32614
  if (data.nextCursor !== void 0 && data.nextCursor !== null && typeof data.nextCursor !== "string") {
32055
32615
  throw new NangoControlError("Connected-data export returned an invalid continuation cursor.");
32056
32616
  }
32057
- const rawCounts = isRecord2(data.counts) ? data.counts : {};
32617
+ const rawCounts = isRecord4(data.counts) ? data.counts : {};
32058
32618
  const numberOrZero = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
32059
32619
  return {
32060
32620
  providerConfigKey,
@@ -32072,11 +32632,11 @@ async function callScheduleConnectionExportPage(identity, input) {
32072
32632
  };
32073
32633
  }
32074
32634
  function unwrapMcpToolJson(value) {
32075
- if (!isRecord2(value)) return value;
32076
- if (isRecord2(value.structuredContent)) return value.structuredContent;
32635
+ if (!isRecord4(value)) return value;
32636
+ if (isRecord4(value.structuredContent)) return value.structuredContent;
32077
32637
  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") {
32638
+ const textPart = value.content.find((part) => isRecord4(part) && part.type === "text" && typeof part.text === "string");
32639
+ if (isRecord4(textPart) && typeof textPart.text === "string") {
32080
32640
  try {
32081
32641
  return JSON.parse(textPart.text);
32082
32642
  } catch {
@@ -32089,13 +32649,26 @@ function unwrapMcpToolJson(value) {
32089
32649
  function rowsFromToolResult(value, keys) {
32090
32650
  const unwrapped = unwrapMcpToolJson(value);
32091
32651
  if (Array.isArray(unwrapped)) return unwrapped;
32092
- if (!isRecord2(unwrapped)) return [];
32652
+ if (!isRecord4(unwrapped)) return [];
32093
32653
  for (const key of keys) if (Array.isArray(unwrapped[key])) return unwrapped[key];
32094
32654
  return [];
32095
32655
  }
32096
32656
  async function callMainOwnedExportPage(identity, input) {
32097
32657
  const connection = await getOwnedServiceConnection(identity, input.connectionId);
32098
32658
  if (!connection) throw new NangoControlError("The service connection was not found.", 404, "connection_not_found", false);
32659
+ if (connection.providerConfigKey === "slack" && input.dataset === "slack_channel_messages") {
32660
+ return exportSlackChannelPage(input, {
32661
+ callTool: async (args) => callNangoToolDirect({
32662
+ identity,
32663
+ connectionId: input.connectionId,
32664
+ tool: args.tool,
32665
+ input: args.input,
32666
+ classification: "read",
32667
+ requestId: args.requestId,
32668
+ operationKind: "export"
32669
+ })
32670
+ });
32671
+ }
32099
32672
  if (connection.providerConfigKey !== "google-search-console" || input.dataset !== "search_console_performance") {
32100
32673
  throw new NangoControlError("This dataset has not moved to main-MCP export execution yet.", 400, "invalid_request", false);
32101
32674
  }
@@ -32113,8 +32686,8 @@ async function callMainOwnedExportPage(identity, input) {
32113
32686
  operationKind: "export"
32114
32687
  });
32115
32688
  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;
32689
+ const row = isRecord4(value) ? value : null;
32690
+ const siteUrl2 = row ? cleanString3(row.siteUrl ?? row.site_url, 2e3) : null;
32118
32691
  return siteUrl2 ? [siteUrl2] : [];
32119
32692
  }).sort().slice(0, 1e4);
32120
32693
  if (siteIndex >= sites.length) {
@@ -32151,7 +32724,7 @@ async function callMainOwnedExportPage(identity, input) {
32151
32724
  });
32152
32725
  const rows = rowsFromToolResult(performance2, ["rows"]).slice(0, input.pageSize);
32153
32726
  const records = rows.map((value, index) => {
32154
- const row = isRecord2(value) ? value : {};
32727
+ const row = isRecord4(value) ? value : {};
32155
32728
  const keys = Array.isArray(row.keys) ? row.keys.map((item) => String(item ?? "")) : [];
32156
32729
  const date = keys[0] || null;
32157
32730
  return {
@@ -32228,34 +32801,34 @@ function controlSecret2() {
32228
32801
  if (!secret2) throw new ResendControlError("Resend connections are not configured.", 503);
32229
32802
  return secret2;
32230
32803
  }
32231
- function isRecord3(value) {
32804
+ function isRecord5(value) {
32232
32805
  return !!value && typeof value === "object" && !Array.isArray(value);
32233
32806
  }
32234
32807
  function unwrapData2(value) {
32235
- return isRecord3(value) && isRecord3(value.data) ? value.data : value;
32808
+ return isRecord5(value) && isRecord5(value.data) ? value.data : value;
32236
32809
  }
32237
- function cleanString2(value, max = 300) {
32810
+ function cleanString4(value, max = 300) {
32238
32811
  if (typeof value !== "string") return null;
32239
32812
  const trimmed = value.trim();
32240
32813
  return trimmed ? trimmed.slice(0, max) : null;
32241
32814
  }
32242
32815
  function firstString2(record, keys, max = 300) {
32243
32816
  for (const key of keys) {
32244
- const value = cleanString2(record[key], max);
32817
+ const value = cleanString4(record[key], max);
32245
32818
  if (value) return value;
32246
32819
  }
32247
32820
  return null;
32248
32821
  }
32249
32822
  function cleanTools2(value) {
32250
32823
  if (!Array.isArray(value)) return [];
32251
- return [...new Set(value.map((tool) => cleanString2(tool, 200)).filter((tool) => !!tool))].slice(0, 200);
32824
+ return [...new Set(value.map((tool) => cleanString4(tool, 200)).filter((tool) => !!tool))].slice(0, 200);
32252
32825
  }
32253
32826
  function cleanStringArray2(value, maxItems = 50, maxLength = 500) {
32254
32827
  if (!Array.isArray(value)) return [];
32255
- return [...new Set(value.map((item) => cleanString2(item, maxLength)).filter((item) => !!item))].slice(0, maxItems);
32828
+ return [...new Set(value.map((item) => cleanString4(item, maxLength)).filter((item) => !!item))].slice(0, maxItems);
32256
32829
  }
32257
32830
  function cleanHttpsUrl2(value) {
32258
- const candidate = cleanString2(value, 2e3);
32831
+ const candidate = cleanString4(value, 2e3);
32259
32832
  if (!candidate) return null;
32260
32833
  try {
32261
32834
  const url = new URL(candidate);
@@ -32273,7 +32846,7 @@ function cleanAuthorizationUrl(value) {
32273
32846
  function arrayFromPayload2(value, keys) {
32274
32847
  const unwrapped = unwrapData2(value);
32275
32848
  if (Array.isArray(unwrapped)) return unwrapped;
32276
- if (!isRecord3(unwrapped)) return [];
32849
+ if (!isRecord5(unwrapped)) return [];
32277
32850
  for (const key of keys) {
32278
32851
  if (Array.isArray(unwrapped[key])) return unwrapped[key];
32279
32852
  }
@@ -32300,7 +32873,7 @@ async function controlRequest2(path5, init, timeoutMs = 2e4) {
32300
32873
  body = {};
32301
32874
  }
32302
32875
  if (!response.ok) {
32303
- const upstream = isRecord3(body) ? cleanString2(body.error, 300) : null;
32876
+ const upstream = isRecord5(body) ? cleanString4(body.error, 300) : null;
32304
32877
  throw new ResendControlError(upstream || `Resend connection control failed (${response.status}).`);
32305
32878
  }
32306
32879
  return body;
@@ -32310,7 +32883,7 @@ async function getResendCatalog() {
32310
32883
  const rows = arrayFromPayload2(body, ["providers", "catalog", "integrations", "services"]);
32311
32884
  const result = [];
32312
32885
  for (const row of rows) {
32313
- if (!isRecord3(row)) continue;
32886
+ if (!isRecord5(row)) continue;
32314
32887
  const id = firstString2(row, ["providerConfigKey", "provider_config_key", "id"]);
32315
32888
  if (id !== RESEND_PROVIDER_CONFIG_KEY) continue;
32316
32889
  const safeDefaultAllowedTools = cleanTools2(
@@ -32346,7 +32919,7 @@ async function getResendConnections(identity) {
32346
32919
  const rows = arrayFromPayload2(body, ["connections"]);
32347
32920
  const result = [];
32348
32921
  for (const row of rows) {
32349
- if (!isRecord3(row)) continue;
32922
+ if (!isRecord5(row)) continue;
32350
32923
  const connectionId = firstString2(row, ["connectionId", "connection_id", "id"]);
32351
32924
  const providerConfigKey = firstString2(row, ["providerConfigKey", "provider_config_key", "provider"]) || RESEND_PROVIDER_CONFIG_KEY;
32352
32925
  if (!connectionId || providerConfigKey !== RESEND_PROVIDER_CONFIG_KEY) continue;
@@ -32390,7 +32963,7 @@ async function getResendConnections(identity) {
32390
32963
  }
32391
32964
  function sanitizeConnectSession2(body) {
32392
32965
  const data = unwrapData2(body);
32393
- if (!isRecord3(data)) throw new ResendControlError("The Resend connection service returned an invalid connect session.");
32966
+ if (!isRecord5(data)) throw new ResendControlError("The Resend connection service returned an invalid connect session.");
32394
32967
  const connectLink = cleanAuthorizationUrl(data.authorizationUrl ?? data.authorization_url ?? data.connectLink ?? data.connect_link);
32395
32968
  if (!connectLink) throw new ResendControlError("The Resend connection service did not return a trusted authorization link.");
32396
32969
  return {
@@ -32431,7 +33004,7 @@ async function setResendActionsEnabled(identity, connectionId, enabled) {
32431
33004
  body: JSON.stringify({ identity, connectionId, enabled })
32432
33005
  });
32433
33006
  const data = unwrapData2(body);
32434
- if (!isRecord3(data) || !isRecord3(data.connection)) return enabled;
33007
+ if (!isRecord5(data) || !isRecord5(data.connection)) return enabled;
32435
33008
  return data.connection.actionsEnabled === true || data.connection.actions_enabled === true;
32436
33009
  }
32437
33010
  async function callResendRead(identity, connectionId, tool, args) {
@@ -32440,7 +33013,7 @@ async function callResendRead(identity, connectionId, tool, args) {
32440
33013
  body: JSON.stringify({ identity, connectionId, tool, input: args ?? {} })
32441
33014
  });
32442
33015
  const data = unwrapData2(body);
32443
- return isRecord3(data) ? data.result ?? data : data;
33016
+ return isRecord5(data) ? data.result ?? data : data;
32444
33017
  }
32445
33018
  async function callResendAction(identity, connectionId, tool, input) {
32446
33019
  const body = await controlRequest2("/api/internal/resend/actions/call", {
@@ -32448,7 +33021,7 @@ async function callResendAction(identity, connectionId, tool, input) {
32448
33021
  body: JSON.stringify({ identity, connectionId, tool, input })
32449
33022
  });
32450
33023
  const data = unwrapData2(body);
32451
- return isRecord3(data) ? data.result ?? data : data;
33024
+ return isRecord5(data) ? data.result ?? data : data;
32452
33025
  }
32453
33026
  async function describeResendTool(identity, connectionId, tool) {
32454
33027
  const body = await controlRequest2("/api/internal/resend/describe", {
@@ -32456,12 +33029,12 @@ async function describeResendTool(identity, connectionId, tool) {
32456
33029
  body: JSON.stringify({ identity, connectionId, tool })
32457
33030
  });
32458
33031
  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.");
33032
+ const rawTool = isRecord5(data) && isRecord5(data.tool) ? data.tool : data;
33033
+ if (!isRecord5(rawTool)) throw new ResendControlError("Resend returned an invalid tool description.");
32461
33034
  const name = firstString2(rawTool, ["name"], 200);
32462
33035
  const classification = firstString2(rawTool, ["classification", "kind"], 20);
32463
33036
  const inputSchema = rawTool.inputSchema ?? rawTool.input_schema;
32464
- if (!name || name !== tool || classification !== "read" && classification !== "action" || !isRecord3(inputSchema)) {
33037
+ if (!name || name !== tool || classification !== "read" && classification !== "action" || !isRecord5(inputSchema)) {
32465
33038
  throw new ResendControlError("Resend returned an invalid tool description.");
32466
33039
  }
32467
33040
  return {
@@ -32478,10 +33051,10 @@ async function callResendExportPage(identity, input) {
32478
33051
  body: JSON.stringify({ identity, ...input })
32479
33052
  }, 9e4);
32480
33053
  const data = unwrapData2(body);
32481
- if (!isRecord3(data) || data.ok !== true || data.providerConfigKey !== RESEND_PROVIDER_CONFIG_KEY) {
33054
+ if (!isRecord5(data) || data.ok !== true || data.providerConfigKey !== RESEND_PROVIDER_CONFIG_KEY) {
32482
33055
  throw new ResendControlError("Resend export returned an invalid page response.");
32483
33056
  }
32484
- const dataset = cleanString2(data.dataset, 64);
33057
+ const dataset = cleanString4(data.dataset, 64);
32485
33058
  if (!dataset || dataset === "auto" || !CONNECTED_DATA_DATASETS.includes(dataset)) {
32486
33059
  throw new ResendControlError("Resend export returned invalid dataset metadata.");
32487
33060
  }
@@ -32489,7 +33062,7 @@ async function callResendExportPage(identity, input) {
32489
33062
  if (data.nextCursor !== void 0 && data.nextCursor !== null && typeof data.nextCursor !== "string") {
32490
33063
  throw new ResendControlError("Resend export returned an invalid continuation cursor.");
32491
33064
  }
32492
- const rawCounts = isRecord3(data.counts) ? data.counts : {};
33065
+ const rawCounts = isRecord5(data.counts) ? data.counts : {};
32493
33066
  const numberOrZero = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
32494
33067
  return {
32495
33068
  providerConfigKey: RESEND_PROVIDER_CONFIG_KEY,
@@ -33104,6 +33677,16 @@ app.get("/memory/note", auth2, async (c) => {
33104
33677
  return c.json({ ok: false, error: err instanceof Error ? err.message : "could not load note" }, 502);
33105
33678
  }
33106
33679
  });
33680
+ app.get("/memory/universe", auth2, async (c) => {
33681
+ const user = c.get("user");
33682
+ const includeConnections = c.req.query("includeConnections") === "1";
33683
+ try {
33684
+ const graph = await dbUniverse(resolveMemoryIdentity(user), includeConnections);
33685
+ return c.json({ ok: true, ...graph });
33686
+ } catch (err) {
33687
+ return c.json({ ok: false, error: err instanceof Error ? err.message : "could not build universe" }, 502);
33688
+ }
33689
+ });
33107
33690
  app.put("/memory/note", auth2, async (c) => {
33108
33691
  const { key, error } = await getOrCreateUserMemoryKey(c.get("user"));
33109
33692
  if (!key) return c.json({ error: error ?? "memory unavailable" }, 502);
@@ -33845,7 +34428,18 @@ app.post("/schedule-connections/actions/export", auth2, requireIntegrationsTier,
33845
34428
  const body = await c.req.json().catch(() => ({}));
33846
34429
  const connectionId = providerConfigKeyFrom(body.connectionId);
33847
34430
  if (!connectionId) return c.json({ ok: false, error: "connectionId is required." }, 400);
33848
- const requestedDataset = typeof body.dataset === "string" ? body.dataset.trim() : "auto";
34431
+ const channelId = typeof body.channelId === "string" ? body.channelId.trim() : "";
34432
+ if (body.channelId !== void 0 && !channelId) {
34433
+ return c.json({ ok: false, error: "channelId must be a non-empty Slack conversation ID." }, 400);
34434
+ }
34435
+ if (body.includeThreads !== void 0 && typeof body.includeThreads !== "boolean") {
34436
+ return c.json({ ok: false, error: "includeThreads must be a boolean when provided." }, 400);
34437
+ }
34438
+ if (body.allTime !== void 0 && typeof body.allTime !== "boolean") {
34439
+ return c.json({ ok: false, error: "allTime must be a boolean when provided." }, 400);
34440
+ }
34441
+ const rawRequestedDataset = typeof body.dataset === "string" ? body.dataset.trim() : "auto";
34442
+ const requestedDataset = rawRequestedDataset === "auto" && channelId ? "slack_channel_messages" : rawRequestedDataset;
33849
34443
  const datasets = [...CONNECTED_DATA_DATASETS];
33850
34444
  if (!datasets.includes(requestedDataset)) {
33851
34445
  return c.json({ ok: false, error: `dataset must be one of: ${datasets.join(", ")}.` }, 400);
@@ -33868,21 +34462,37 @@ app.post("/schedule-connections/actions/export", auth2, requireIntegrationsTier,
33868
34462
  if (body.continuation !== void 0 && !continuation) {
33869
34463
  return c.json({ ok: false, error: "continuation must be the complete object returned by a prior export." }, 400);
33870
34464
  }
34465
+ if (body.allTime === true && requestedDataset !== "slack_channel_messages") {
34466
+ return c.json({ ok: false, error: "allTime is supported only for Slack channel exports." }, 400);
34467
+ }
34468
+ if (body.allTime === true && (body.from !== void 0 || body.lastDays !== void 0 || continuation)) {
34469
+ return c.json({ ok: false, error: "Do not pass allTime with from, lastDays, or continuation." }, 400);
34470
+ }
34471
+ if (requestedDataset === "slack_channel_messages" && !channelId && !continuation) {
34472
+ return c.json({ ok: false, error: "channelId is required for a Slack channel export." }, 400);
34473
+ }
33871
34474
  try {
33872
34475
  const useResend = await isResendConnection(user.email, connectionId);
33873
34476
  const resolved = resolveConnectedDataExportRequest({
33874
34477
  requestedDataset,
33875
- from: typeof body.from === "string" ? body.from : void 0,
34478
+ from: body.allTime === true ? "1970-01-01T00:00:00.000Z" : typeof body.from === "string" ? body.from : void 0,
33876
34479
  to: typeof body.to === "string" ? body.to : void 0,
33877
34480
  lastDays,
33878
34481
  cursor: typeof body.cursor === "string" ? body.cursor : void 0,
33879
- continuation
34482
+ continuation,
34483
+ scope: channelId ? {
34484
+ slack: {
34485
+ channelId,
34486
+ includeThreads: body.includeThreads !== false
34487
+ }
34488
+ } : void 0
33880
34489
  });
33881
34490
  const result = await collectConnectedDataExport({
33882
34491
  ownerId: sha256Hex(user.api_key).slice(0, 24),
33883
34492
  connectionId,
33884
34493
  dataset: resolved.dataset,
33885
34494
  range: resolved.range,
34495
+ ...resolved.scope ? { scope: resolved.scope } : {},
33886
34496
  maxItems,
33887
34497
  forceArtifact: body.delivery === "artifact",
33888
34498
  ...resolved.cursor ? { cursor: resolved.cursor } : {},
@@ -35713,4 +36323,4 @@ app.get("/blog/:slug/", (c) => {
35713
36323
  export {
35714
36324
  app
35715
36325
  };
35716
- //# sourceMappingURL=server-W76RUYD3.js.map
36326
+ //# sourceMappingURL=server-PKTDPCB2.js.map